Compare commits
11 Commits
00702c4aea
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7729af631a | ||
|
|
2e4debefe9 | ||
|
|
b983278a17 | ||
|
|
e88d063184 | ||
|
|
a33572c240 | ||
|
|
cbd5f5feb1 | ||
|
|
7c58dfacbb | ||
|
|
e9e7b2a63e | ||
|
|
3529789787 | ||
|
|
6ba3fb1ea8 | ||
|
|
b37fdb40a2 |
30
.gitignore
vendored
30
.gitignore
vendored
@@ -5,14 +5,25 @@
|
||||
# https://github.com/SlideWave/gitignore-include?tab=readme-ov-file#examples
|
||||
# https://gitignore.io
|
||||
|
||||
### .gitignore_global ###
|
||||
### .gitignore.global.txt ###
|
||||
|
||||
# Self defined extension to ignore all files/folders containing .gitignore
|
||||
*.gitignore.*
|
||||
*.gitignore.*/
|
||||
*.gitignore
|
||||
*.gitignore/
|
||||
# Self defined pattern to ignore
|
||||
?*.gitignore
|
||||
?*.gitignore/
|
||||
?*.gitignore.*
|
||||
?*.gitignore.*/
|
||||
*.gitomit
|
||||
*.gitomit.*
|
||||
*.gitomit/
|
||||
*.gitomit.*/
|
||||
*.nogit
|
||||
*.nogit.*
|
||||
*.nogit/
|
||||
*.nogit.*/
|
||||
# 保留
|
||||
!.gitignore
|
||||
!.gitignore.*
|
||||
!.gitkeep
|
||||
|
||||
# 通用
|
||||
.svn/
|
||||
@@ -23,7 +34,9 @@
|
||||
/test/unit/coverage/
|
||||
/test/e2e/reports/
|
||||
node_modules/
|
||||
*.aab
|
||||
*.apk
|
||||
*.ipa
|
||||
*.min.js
|
||||
*.min.css
|
||||
*.min.html
|
||||
@@ -96,8 +109,5 @@ _desktop.ini
|
||||
package-lock.json
|
||||
pages4loader.json5
|
||||
|
||||
# 保留
|
||||
!.gitkeep
|
||||
|
||||
### .gitignore_local ###
|
||||
### .gitignore.local.txt ###
|
||||
|
||||
|
||||
138
basesocket.js
138
basesocket.js
@@ -1,78 +1,109 @@
|
||||
const ws = require('ws')
|
||||
const webtoken = require('wo-base-webtoken')
|
||||
const crypto = require('crypto')
|
||||
|
||||
const my = {
|
||||
wsServer: undefined,
|
||||
socketPool: {},
|
||||
// socketPool: {},
|
||||
listeners: {},
|
||||
heartbeatInterval: 30000,
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
initSocket (webServer) {
|
||||
initSocket ({ webServer, heartbeat, heartbeatInterval }) {
|
||||
my.wsServer = new ws.Server({ server: webServer })
|
||||
console.info({ _at: new Date().toJSON(), _from: 'Socket:initSocket', _type: 'CLOG', about: 'Base Socket Server is initialized.' }, '\n,')
|
||||
//globalThis.wo?.ccinfo?.({ _from: 'Socket:initSocket', _type: 'CLOG', about: 'Base Socket Server is initialized.' })
|
||||
|
||||
my.wsServer.on('connection', (socket, req) => {
|
||||
//console.info({_at:new Date().toJSON(), _from: 'Socket:onConnection', _type:'CLOG', about: `A socket is connecting from ${req.connection.remoteAddress}:${req.connection.remotePort}.`},'\n,')
|
||||
globalThis.wo?.ccinfo?.({
|
||||
_from: 'basesocket:onConnection',
|
||||
_type: 'CLOG',
|
||||
about: `A socket is connecting from ${req.connection.remoteAddress}:${req.connection.remotePort}.`,
|
||||
})
|
||||
|
||||
// socket.isAlive = true
|
||||
// socket.on('pong', function() { this.isAlive = true })
|
||||
// socket.on('ping', () => { socket.isAlive = true }) // Most WebSocket server implementations, including the ws library, automatically respond to ping frames with pong frames. However, the server can listen for ping events using socket.on('ping', ...) if it wants to perform additional actions.
|
||||
|
||||
socket.on('message', (data) => {
|
||||
// 在这里统一分发消息
|
||||
let dataObj
|
||||
try {
|
||||
dataObj = JSON.parse(data)
|
||||
if (dataObj?.skevent === 'PING') {
|
||||
// socket.isAlive = true
|
||||
return
|
||||
}
|
||||
console.log({ _at: new Date().toJSON(), _from: 'Socket:onMessage', _type: 'CLOG', usid: socket.usid, dataObj }, '\n,')
|
||||
//globalThis.wo?.cclog?.({ _from: 'basesocket:onMessage', _type: 'CLOG', usid: socket.usid, skevent: dataObj?.skevent })
|
||||
} catch (exception) {
|
||||
console.error({ _at: new Date().toJSON(), _from: 'Socket:onMessage', _type: 'CERROR', about: 'Unable to parse socket message', data }, '\n,')
|
||||
globalThis.wo?.ccerror?.({ _from: 'basesocket:onMessage', _type: 'CERROR', about: 'Unable to parse socket message', data })
|
||||
return
|
||||
}
|
||||
globalThis.wo?.cclog?.({ _from: 'Socket:onMessage:dataObj', dataObj })
|
||||
if (['SOCKET_OWNER', 'SOCKET_OWNER_RECONNECT'].includes(dataObj.skevent)) {
|
||||
// 前端断线重连时,并不会自动再次提供 _passtoken。在前端的initSocket时,应当把_passtoken送过来,而后台则对_passtoken做验证后再加socketPool。
|
||||
dataObj._passtokenSource = webtoken.verifyToken(dataObj._passtoken)
|
||||
if (typeof dataObj._passtokenSource?.usid === 'string') {
|
||||
my.socketPool[dataObj._passtokenSource.usid] = socket
|
||||
socket.usid = dataObj._passtokenSource.usid
|
||||
console.log({
|
||||
_at: new Date().toJSON(), _from: 'Socket:onMessage', _type: 'CLOG',
|
||||
// 前端断线重连时,并不会自动再次提供 _passtoken。在前端的initSocket时,应当把_passtoken送过来。
|
||||
const _passtokenSource = webtoken.verifyToken(dataObj._passtoken)
|
||||
globalThis.wo?.cclog?.({ _from: 'Socket:onMessge:_passtokenSource', _passtokenSource })
|
||||
if (typeof _passtokenSource?.usid === 'string') {
|
||||
socket.appkey = _passtokenSource.appkey
|
||||
socket.usid = _passtokenSource.usid
|
||||
socket.commid = _passtokenSource.commid
|
||||
socket.skid = _passtokenSource.clid || socket.skid || 'skid' + crypto.randomBytes(16).toString('hex') // 注意,skid 这个名字 仅限在本文件内使用,在外部都使用 clid (client id)
|
||||
// my.socketPool[socket.skid] = socket
|
||||
globalThis.wo?.cclog?.({
|
||||
_from: 'basesocket:onMessage',
|
||||
_type: 'CLOG',
|
||||
skevent: dataObj.skevent,
|
||||
usid: dataObj._passtokenSource.usid,
|
||||
socketPool: Object.keys(my.socketPool)?.length,
|
||||
socketClients: my.wsServer.clients.size
|
||||
}, '\n,')
|
||||
usid: socket.usid,
|
||||
skid: socket.skid,
|
||||
// socketPool: Object.keys(my.socketPool),
|
||||
clientsSize: my.wsServer.clients.size,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
const listeners = my.listeners[dataObj.skevent] || []
|
||||
for (const listener of listeners) {
|
||||
listener(dataObj)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// const heartbeat = setInterval(() => {
|
||||
// my.wsServer.clients.forEach((socket) => {
|
||||
// if (socket.isAlive === false) return socket.terminate()
|
||||
// socket.isAlive = false
|
||||
// socket.ping(function() { wo.cclog('👉 ASS: sent Ping') })
|
||||
// })
|
||||
// }, 60000)
|
||||
|
||||
socket.on('close', () => {
|
||||
//console.log({_at:new Date().toJSON(), _from: 'Socket:onClose', _type:'CLOG', usid: socket?.usid},'\n,') // don't know why, but this output happens too often without usid.
|
||||
delete my.socketPool[socket?.usid]
|
||||
// clearInterval(heartbeat)
|
||||
globalThis.wo?.cclog?.({ _from: 'basesocket:onClose', _type: 'CLOG', usid: socket?.usid, commid: socket?.commid, skid: socket?.skid }) // don't know why, but this output happens too often without usid.
|
||||
//delete my.socketPool[socket?.usid] // 20240917 恍然大悟,同一个用户可以多次登录,多个socket会互相覆盖。如果仅仅根据 usid 来删除,其他尚在连接的socket也会被删了。
|
||||
// delete my.socketPool[socket?.skid]
|
||||
})
|
||||
})
|
||||
|
||||
my.wsServer.on('close', () => {})
|
||||
|
||||
// 一个全局的 heartbeat,不必给每个 socket 一个 heartbeat
|
||||
if (heartbeat) {
|
||||
globalThis.wo?.cclog?.({ _msg: 'WebSocket_heartbeat: starting...' })
|
||||
setInterval(() => {
|
||||
globalThis.wo?.cclog?.({
|
||||
_msg: 'WebSocket_heartbeat: clientsSize = ' + my.wsServer.clients.size,
|
||||
})
|
||||
my.wsServer.clients.forEach((socket) => {
|
||||
globalThis.wo?.cclog?.({ appkey: socket.appkey, usid: socket.usid, commid: socket.commid, skid: socket.skid, readyState: socket.readyState })
|
||||
if (socket.readyState !== ws.OPEN) {
|
||||
//socket.isAlive = false
|
||||
} else {
|
||||
//socket.ping()
|
||||
}
|
||||
})
|
||||
}, heartbeatInterval || my.heartbeatInterval)
|
||||
}
|
||||
|
||||
return this
|
||||
},
|
||||
|
||||
removeUserSocket (usid) {
|
||||
delete my.socketPool[usid]
|
||||
removeUserSocket ({ clid } = {}) {
|
||||
my.wsServer.clients.forEach((socket) => {
|
||||
if (clid && socket.skid === clid) {
|
||||
delete socket.usid
|
||||
delete socket.commid
|
||||
// 要不要清除 socket.?clid 似乎不清除也没问题
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
addListener (skevent, listener) {
|
||||
@@ -85,23 +116,46 @@ module.exports = {
|
||||
},
|
||||
|
||||
sendToAll (dataObj) {
|
||||
globalThis.wo?.cclog?.({ _from: 'Socket:sendToAll:dataObj', dataObj })
|
||||
my.wsServer.clients.forEach((socket) => {
|
||||
if (socket.readyState === socket.OPEN) {
|
||||
try {
|
||||
globalThis.wo?.cclog?.({ _from: 'Socket:sendToAll:socket', usid: socket.usid, skid: socket.skid })
|
||||
if (socket.readyState === ws.OPEN) {
|
||||
socket.send(typeof dataObj !== 'string' ? JSON.stringify(dataObj) : dataObj)
|
||||
} else {
|
||||
console.error({ _at: new Date().toJSON(), _from: 'Socket:sendToAll', _type: 'CWARN', msg: 'sendToAll: Failed sending to unconnected socket', dataObj, usid: socket.usid }, '\n,')
|
||||
delete my.socketPool[socket.usid]
|
||||
globalThis.wo?.ccwarn?.({ _from: 'Socket:sendToAll:socket', _type: 'CWARN', _msg: 'sendToOne: socket not open', dataObj })
|
||||
}
|
||||
} catch (expt) {
|
||||
globalThis.wo?.ccerror?.({
|
||||
_from: 'Socket:sendToAll',
|
||||
_type: 'CERROR',
|
||||
msg: 'sendToAll: Failed sending to unconnected socket',
|
||||
dataObj,
|
||||
usid: socket.usid,
|
||||
skid: socket.skid,
|
||||
})
|
||||
// delete my.socketPool[socket.skid]
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
sendToOne (dataObj, usid) {
|
||||
const socket = my.socketPool[usid]
|
||||
if (socket && socket.readyState === socket.OPEN) {
|
||||
sendToOne (dataObj = {}) {
|
||||
globalThis.wo?.cclog?.({ _from: 'Socket:sendToOne:dataObj', dataObj })
|
||||
my.wsServer.clients.forEach((socket) => {
|
||||
if ((dataObj.appkey && dataObj.appkey === socket.appkey) || !dataObj.appkey) {
|
||||
if ((dataObj.clid && socket.skid === dataObj.clid) || (!dataObj.clid && dataObj.usid && socket.usid === dataObj.usid)) {
|
||||
try {
|
||||
if (socket.readyState === ws.OPEN) {
|
||||
socket.send(typeof dataObj !== 'string' ? JSON.stringify(dataObj) : dataObj)
|
||||
} else {
|
||||
console.error({ _at: new Date().toJSON(), _from: 'Socket:sendToOne', _type: 'CWARN', msg: 'sendToOne: Failed sending to unconnected socket', dataObj, usid }, '\n,')
|
||||
delete my.socketPool[usid]
|
||||
globalThis.wo?.ccwarn?.({ _from: 'Socket:sendToOne', _type: 'CWARN', msg: 'sendToOne: socket not open', dataObj })
|
||||
}
|
||||
} catch (expt) {
|
||||
globalThis.wo?.ccerror?.({ _from: 'Socket:sendToOne', _type: 'CERROR', msg: 'sendToOne: Failed sending to socket', dataObj })
|
||||
// delete my.socketPool[socket.skid]
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"main": "basesocket.js",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"wo-base-webtoken": "git+https://git.faronear.org/npm/wo-base-webtoken",
|
||||
"wo-base-webtoken": "git+https://git.tic.cc/open/wo-base-webtoken",
|
||||
"ws": "^7.3.0"
|
||||
},
|
||||
"devDependencies": {},
|
||||
|
||||
@@ -6,13 +6,27 @@
|
||||
# 文件在服务器端的后续更改会被同步到客户端,如果客户端也同时修改了这些文件,系统会生成冲突文件。
|
||||
# seafile-ignore.txt 只能忽略还没有被同步的文件。对于已经被同步的文件,如果后来把它添加到 seafile-ignore.txt 中,系统只会忽略后续更改,已经上传的版本不会受影响。
|
||||
|
||||
### seafile-ignore_global ###
|
||||
### seafile-ignore.global.txt ###
|
||||
|
||||
# 自定义的后缀名,凡有 sfignore 后缀的都不进行同步
|
||||
*.sfignore
|
||||
*.sfignore.*
|
||||
*.sfignore/
|
||||
*.sfignore.*
|
||||
*.sfignore.*/
|
||||
*.sfomit
|
||||
*.sfomit.*
|
||||
*.sfomit/
|
||||
*.sfomit.*/
|
||||
*.nosf
|
||||
*.nosf.*
|
||||
*.nosf/
|
||||
*.nosf.*/
|
||||
|
||||
## everything 'git pull or fetch' will update `.git/FETCH_HEAD`, even if the content doesn't change. To avoid too many useless updates of this file in Seafile history:
|
||||
FETCH_HEAD
|
||||
*/FETCH_HEAD
|
||||
|
||||
.Trash/
|
||||
|
||||
.DS_Store
|
||||
*/.DS_Store
|
||||
@@ -40,12 +54,18 @@ _desktop.ini
|
||||
node_modules/
|
||||
*/node_modules/
|
||||
package-lock.json
|
||||
*/package-lock.json
|
||||
|
||||
pages4loader.json5
|
||||
*/pages4loader.json5
|
||||
|
||||
.deploy_git/
|
||||
*/.deploy_git/
|
||||
|
||||
# next.js 项目
|
||||
.next/
|
||||
*/.next/
|
||||
|
||||
# HBuilder 目录
|
||||
unpackage/
|
||||
*/unpackage/
|
||||
@@ -53,5 +73,12 @@ unpackage/
|
||||
Icon
|
||||
OneDrive/Icon
|
||||
|
||||
### seafile-ignore_local ###
|
||||
# wrangler project
|
||||
|
||||
.dev.vars*
|
||||
*/.dev.vars*
|
||||
.wrangler/
|
||||
*/.wrangler/
|
||||
|
||||
### seafile-ignore.local.txt ###
|
||||
|
||||
|
||||
Reference in New Issue
Block a user