vona-module-a-socket 5.0.0 → 5.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/cli/socketConnection/boilerplate/{{sceneName}}.{{beanName}}.ts_ +20 -0
- package/cli/socketNamespace/boilerplate/{{sceneName}}.{{beanName}}.ts_ +17 -0
- package/cli/socketPacket/boilerplate/{{sceneName}}.{{beanName}}.ts_ +15 -0
- package/dist/.metadata/index.d.ts +260 -0
- package/dist/.metadata/index.d.ts.map +1 -0
- package/dist/.metadata/this.d.ts +3 -0
- package/dist/.metadata/this.d.ts.map +1 -0
- package/dist/bean/bean.socket.d.ts +16 -0
- package/dist/bean/bean.socket.d.ts.map +1 -0
- package/dist/bean/broadcast.broadcast.d.ts +14 -0
- package/dist/bean/broadcast.broadcast.d.ts.map +1 -0
- package/dist/bean/broadcast.send.d.ts +13 -0
- package/dist/bean/broadcast.send.d.ts.map +1 -0
- package/dist/bean/hmr.socketConnection.d.ts +7 -0
- package/dist/bean/hmr.socketConnection.d.ts.map +1 -0
- package/dist/bean/hmr.socketPacket.d.ts +7 -0
- package/dist/bean/hmr.socketPacket.d.ts.map +1 -0
- package/dist/bean/socketConnection.alive.d.ts +14 -0
- package/dist/bean/socketConnection.alive.d.ts.map +1 -0
- package/dist/bean/socketConnection.app.d.ts +11 -0
- package/dist/bean/socketConnection.app.d.ts.map +1 -0
- package/dist/bean/socketConnection.cors.d.ts +11 -0
- package/dist/bean/socketConnection.cors.d.ts.map +1 -0
- package/dist/bean/socketConnection.event.d.ts +11 -0
- package/dist/bean/socketConnection.event.d.ts.map +1 -0
- package/dist/bean/socketConnection.instance.d.ts +11 -0
- package/dist/bean/socketConnection.instance.d.ts.map +1 -0
- package/dist/bean/socketConnection.passport.d.ts +12 -0
- package/dist/bean/socketConnection.passport.d.ts.map +1 -0
- package/dist/bean/socketConnection.ready.d.ts +11 -0
- package/dist/bean/socketConnection.ready.d.ts.map +1 -0
- package/dist/bean/socketPacket.event.d.ts +10 -0
- package/dist/bean/socketPacket.event.d.ts.map +1 -0
- package/dist/bean/socketPacket.performAction.d.ts +11 -0
- package/dist/bean/socketPacket.performAction.d.ts.map +1 -0
- package/dist/config/config.d.ts +15 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +632 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/beanSocketNamespaceBase.d.ts +7 -0
- package/dist/lib/beanSocketNamespaceBase.d.ts.map +1 -0
- package/dist/lib/const.d.ts +8 -0
- package/dist/lib/const.d.ts.map +1 -0
- package/dist/lib/index.d.ts +6 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/socketConnection.d.ts +3 -0
- package/dist/lib/socketConnection.d.ts.map +1 -0
- package/dist/lib/socketNamespace.d.ts +4 -0
- package/dist/lib/socketNamespace.d.ts.map +1 -0
- package/dist/lib/socketPacket.d.ts +3 -0
- package/dist/lib/socketPacket.d.ts.map +1 -0
- package/dist/monkey.d.ts +7 -0
- package/dist/monkey.d.ts.map +1 -0
- package/dist/service/socket.d.ts +13 -0
- package/dist/service/socket.d.ts.map +1 -0
- package/dist/service/socketEvent.d.ts +10 -0
- package/dist/service/socketEvent.d.ts.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/socket.d.ts +18 -0
- package/dist/types/socket.d.ts.map +1 -0
- package/dist/types/socketConnection.d.ts +34 -0
- package/dist/types/socketConnection.d.ts.map +1 -0
- package/dist/types/socketEvent.d.ts +46 -0
- package/dist/types/socketEvent.d.ts.map +1 -0
- package/dist/types/socketNamespace.d.ts +23 -0
- package/dist/types/socketNamespace.d.ts.map +1 -0
- package/dist/types/socketPacket.d.ts +25 -0
- package/dist/types/socketPacket.d.ts.map +1 -0
- package/package.json +58 -22
- package/src/.metadata/index.ts +359 -0
- package/src/.metadata/this.ts +2 -0
- package/src/bean/bean.socket.ts +60 -0
- package/src/bean/broadcast.broadcast.ts +23 -0
- package/src/bean/broadcast.send.ts +22 -0
- package/src/bean/hmr.socketConnection.ts +14 -0
- package/src/bean/hmr.socketPacket.ts +14 -0
- package/src/bean/socketConnection.alive.ts +48 -0
- package/src/bean/socketConnection.app.ts +26 -0
- package/src/bean/socketConnection.cors.ts +27 -0
- package/src/bean/socketConnection.event.ts +34 -0
- package/src/bean/socketConnection.instance.ts +23 -0
- package/src/bean/socketConnection.passport.ts +37 -0
- package/src/bean/socketConnection.ready.ts +21 -0
- package/src/bean/socketPacket.event.ts +29 -0
- package/src/bean/socketPacket.performAction.ts +37 -0
- package/src/config/config.ts +19 -0
- package/src/index.ts +3 -0
- package/src/lib/beanSocketNamespaceBase.ts +15 -0
- package/src/lib/const.ts +22 -0
- package/src/lib/index.ts +5 -0
- package/src/lib/socketConnection.ts +7 -0
- package/src/lib/socketNamespace.ts +9 -0
- package/src/lib/socketPacket.ts +7 -0
- package/src/monkey.ts +15 -0
- package/src/service/socket.ts +180 -0
- package/src/service/socketEvent.ts +30 -0
- package/src/types/index.ts +5 -0
- package/src/types/socket.ts +21 -0
- package/src/types/socketConnection.ts +41 -0
- package/src/types/socketEvent.ts +55 -0
- package/src/types/socketNamespace.ts +27 -0
- package/src/types/socketPacket.ts +30 -0
- package/README.md +0 -1
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Next } from 'vona';
|
|
2
|
+
import type { IDecoratorSocketConnectionOptions, ISocketConnectionExecute } from 'vona-module-a-socket';
|
|
3
|
+
import type { WebSocket } from 'ws';
|
|
4
|
+
|
|
5
|
+
import { BeanBase } from 'vona';
|
|
6
|
+
import { SocketConnection } from 'vona-module-a-socket';
|
|
7
|
+
|
|
8
|
+
export interface ISocketConnectionOptionsPassport extends IDecoratorSocketConnectionOptions {}
|
|
9
|
+
|
|
10
|
+
@SocketConnection<ISocketConnectionOptionsPassport>({ dependencies: 'a-socket:event' })
|
|
11
|
+
export class SocketConnectionPassport extends BeanBase implements ISocketConnectionExecute {
|
|
12
|
+
async enter(ws: WebSocket, _options: ISocketConnectionOptionsPassport, next: Next): Promise<void> {
|
|
13
|
+
// checkOauthCode
|
|
14
|
+
await this._checkOauthCode(ws);
|
|
15
|
+
// next
|
|
16
|
+
return next();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async exit(_ws: WebSocket, _options: ISocketConnectionOptionsPassport, next: Next): Promise<void> {
|
|
20
|
+
// next
|
|
21
|
+
return next();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private async _checkOauthCode(_ws: WebSocket) {
|
|
25
|
+
// auth token
|
|
26
|
+
if (!this.bean.passport.current) {
|
|
27
|
+
const code = this.ctx.query[this.scope.config.queryKey.passportCode] as string | undefined;
|
|
28
|
+
if (code) {
|
|
29
|
+
await this.bean.passport.checkAuthToken(code, 'access', { path: this.ctx.path });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// check current
|
|
33
|
+
if (!this.bean.passport.current) {
|
|
34
|
+
await this.bean.passport.signinWithAnonymous();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Next } from 'vona';
|
|
2
|
+
import type { IDecoratorSocketConnectionOptions, ISocketConnectionExecute } from 'vona-module-a-socket';
|
|
3
|
+
import type { WebSocket } from 'ws';
|
|
4
|
+
|
|
5
|
+
import { BeanBase } from 'vona';
|
|
6
|
+
import { SocketConnection } from 'vona-module-a-socket';
|
|
7
|
+
|
|
8
|
+
export interface ISocketConnectionOptionsReady extends IDecoratorSocketConnectionOptions {}
|
|
9
|
+
|
|
10
|
+
@SocketConnection<ISocketConnectionOptionsReady>({ dependencies: 'a-socket:passport' })
|
|
11
|
+
export class SocketConnectionReady extends BeanBase implements ISocketConnectionExecute {
|
|
12
|
+
async enter(_ws: WebSocket, _options: ISocketConnectionOptionsReady, next: Next): Promise<void> {
|
|
13
|
+
// next
|
|
14
|
+
return next();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async exit(_ws: WebSocket, _options: ISocketConnectionOptionsReady, next: Next): Promise<void> {
|
|
18
|
+
// next
|
|
19
|
+
return next();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Next } from 'vona';
|
|
2
|
+
import type { WebSocket } from 'ws';
|
|
3
|
+
|
|
4
|
+
import { BeanBase } from 'vona';
|
|
5
|
+
|
|
6
|
+
import type { TypeSocketPacketEvent } from '../types/socketEvent.ts';
|
|
7
|
+
import type { IDecoratorSocketPacketOptions, ISocketPacketExecute } from '../types/socketPacket.ts';
|
|
8
|
+
|
|
9
|
+
import { SocketPacket } from '../lib/socketPacket.ts';
|
|
10
|
+
import { socketEventRecordReverse } from '../types/socketEvent.ts';
|
|
11
|
+
|
|
12
|
+
export interface ISocketPacketOptionsEvent extends IDecoratorSocketPacketOptions {}
|
|
13
|
+
|
|
14
|
+
@SocketPacket<ISocketPacketOptionsEvent>()
|
|
15
|
+
export class SocketPacketEvent extends BeanBase implements ISocketPacketExecute {
|
|
16
|
+
async execute(data: any, _ws: WebSocket, _options: ISocketPacketOptionsEvent, next: Next): Promise<void> {
|
|
17
|
+
let packet: TypeSocketPacketEvent;
|
|
18
|
+
const eventPrefix = this.scope.config.eventPrefix;
|
|
19
|
+
if (typeof data === 'string' && data.startsWith(eventPrefix)) {
|
|
20
|
+
const packetInner = JSON.parse(data.substring(eventPrefix.length));
|
|
21
|
+
const eventName = socketEventRecordReverse[packetInner[0]] ?? packetInner[0];
|
|
22
|
+
packet = [eventName, packetInner[1]];
|
|
23
|
+
} else {
|
|
24
|
+
packet = [undefined, data];
|
|
25
|
+
}
|
|
26
|
+
// next
|
|
27
|
+
return next(packet);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Next } from 'vona';
|
|
2
|
+
import type { WebSocket } from 'ws';
|
|
3
|
+
|
|
4
|
+
import { BeanBase } from 'vona';
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
ISocketEventPerformActionOptionsInner,
|
|
8
|
+
ISocketEventRecord,
|
|
9
|
+
ISocketEventRecordSystem,
|
|
10
|
+
TypeSocketPacketEvent,
|
|
11
|
+
} from '../types/socketEvent.ts';
|
|
12
|
+
import type { IDecoratorSocketPacketOptions, ISocketPacketExecute } from '../types/socketPacket.ts';
|
|
13
|
+
|
|
14
|
+
import { SocketPacket } from '../lib/socketPacket.ts';
|
|
15
|
+
|
|
16
|
+
export interface ISocketPacketOptionsPerformAction extends IDecoratorSocketPacketOptions {}
|
|
17
|
+
|
|
18
|
+
@SocketPacket<ISocketPacketOptionsPerformAction>({ dependencies: 'a-socket:event' })
|
|
19
|
+
export class SocketPacketPerformAction extends BeanBase implements ISocketPacketExecute {
|
|
20
|
+
async execute(packet: TypeSocketPacketEvent, ws: WebSocket, _options: ISocketPacketOptionsPerformAction, next: Next): Promise<void> {
|
|
21
|
+
const eventName: keyof ISocketEventRecord | undefined = packet[0];
|
|
22
|
+
const data: ISocketEventPerformActionOptionsInner = packet[1];
|
|
23
|
+
if (eventName !== ('sysPerformAction' satisfies keyof ISocketEventRecordSystem)) return next();
|
|
24
|
+
try {
|
|
25
|
+
// performAction
|
|
26
|
+
const res = await this.$scope.executor.service.executor.performActionInner(data.m, data.p as never, {
|
|
27
|
+
innerAccess: false,
|
|
28
|
+
query: data.q,
|
|
29
|
+
body: data.b,
|
|
30
|
+
headers: data.h,
|
|
31
|
+
});
|
|
32
|
+
ws.sendEvent('sysPerformActionBack' satisfies keyof ISocketEventRecordSystem as never, { i: data.i, c: 0, d: res } as never);
|
|
33
|
+
} catch (err: any) {
|
|
34
|
+
ws.sendEvent('sysPerformActionBack' satisfies keyof ISocketEventRecordSystem as never, { i: data.i, c: err.code, m: err.message } as never);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { VonaApplication } from 'vona';
|
|
2
|
+
|
|
3
|
+
import { $customKey } from 'vona';
|
|
4
|
+
|
|
5
|
+
export function config(_app: VonaApplication) {
|
|
6
|
+
return {
|
|
7
|
+
eventPrefix: '_:',
|
|
8
|
+
globalPrefix: '/ws',
|
|
9
|
+
queryKey: {
|
|
10
|
+
passportCode: $customKey('x-vona-passport-code'),
|
|
11
|
+
instanceName: $customKey('x-vona-instance-name'),
|
|
12
|
+
locale: $customKey('x-vona-locale'),
|
|
13
|
+
tz: $customKey('x-vona-tz'),
|
|
14
|
+
},
|
|
15
|
+
timeout: {
|
|
16
|
+
ping: 20000,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BeanBase, cast } from 'vona';
|
|
2
|
+
|
|
3
|
+
import type { ISendEventOptions } from '../types/socketEvent.ts';
|
|
4
|
+
import type { IDecoratorSocketNamespaceOptions } from '../types/socketNamespace.ts';
|
|
5
|
+
|
|
6
|
+
export class BeanSocketNamespaceBase<EVENTS extends {}> extends BeanBase {
|
|
7
|
+
send<K extends keyof EVENTS>(id: string, eventName: K, data?: EVENTS[K], options?: ISendEventOptions) {
|
|
8
|
+
this.$scope.socket.service.socketEvent.send(id, eventName, data, options);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
broadcast<K extends keyof EVENTS>(eventName: K, data?: EVENTS[K], options?: ISendEventOptions) {
|
|
12
|
+
const namespace = cast<IDecoratorSocketNamespaceOptions>(this.$onionOptions).namespace;
|
|
13
|
+
this.$scope.socket.service.socketEvent.broadcast(namespace, eventName, data, options);
|
|
14
|
+
}
|
|
15
|
+
}
|
package/src/lib/const.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { VonaApplication } from 'vona';
|
|
2
|
+
|
|
3
|
+
export const SymbolCacheSocketConnections = Symbol('SymbolCacheSocketConnections');
|
|
4
|
+
export const SymbolCacheSocketPackets = Symbol('SymbolCacheSocketPackets');
|
|
5
|
+
|
|
6
|
+
export function getCacheSocketConnections(app: VonaApplication) {
|
|
7
|
+
if (!app.meta[SymbolCacheSocketConnections]) app.meta[SymbolCacheSocketConnections] = {};
|
|
8
|
+
return app.meta[SymbolCacheSocketConnections];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function clearAllCacheSocketConnections(app: VonaApplication) {
|
|
12
|
+
delete app.meta[SymbolCacheSocketConnections];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getCacheSocketPackets(app: VonaApplication) {
|
|
16
|
+
if (!app.meta[SymbolCacheSocketPackets]) app.meta[SymbolCacheSocketPackets] = {};
|
|
17
|
+
return app.meta[SymbolCacheSocketPackets];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function clearAllCacheSocketPackets(app: VonaApplication) {
|
|
21
|
+
delete app.meta[SymbolCacheSocketPackets];
|
|
22
|
+
}
|
package/src/lib/index.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { createBeanDecorator } from 'vona';
|
|
2
|
+
|
|
3
|
+
import type { IDecoratorSocketConnectionOptions } from '../types/socketConnection.ts';
|
|
4
|
+
|
|
5
|
+
export function SocketConnection<T extends IDecoratorSocketConnectionOptions>(options?: T): ClassDecorator {
|
|
6
|
+
return createBeanDecorator('socketConnection', options);
|
|
7
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PowerPartial } from 'vona';
|
|
2
|
+
|
|
3
|
+
import { createBeanDecorator } from 'vona';
|
|
4
|
+
|
|
5
|
+
import type { IDecoratorSocketNamespaceOptions } from '../types/socketNamespace.ts';
|
|
6
|
+
|
|
7
|
+
export function SocketNamespace<T extends IDecoratorSocketNamespaceOptions>(options?: PowerPartial<T>): ClassDecorator {
|
|
8
|
+
return createBeanDecorator('socketNamespace', options);
|
|
9
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { createBeanDecorator } from 'vona';
|
|
2
|
+
|
|
3
|
+
import type { IDecoratorSocketPacketOptions } from '../types/socketPacket.ts';
|
|
4
|
+
|
|
5
|
+
export function SocketPacket<T extends IDecoratorSocketPacketOptions>(options?: T): ClassDecorator {
|
|
6
|
+
return createBeanDecorator('socketPacket', options);
|
|
7
|
+
}
|
package/src/monkey.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { IMonkeyAppClose, IMonkeyAppReady } from 'vona';
|
|
2
|
+
|
|
3
|
+
import { BeanSimple } from 'vona';
|
|
4
|
+
|
|
5
|
+
import { __ThisModule__ } from './.metadata/this.ts';
|
|
6
|
+
|
|
7
|
+
export class Monkey extends BeanSimple implements IMonkeyAppReady, IMonkeyAppClose {
|
|
8
|
+
async appReady() {
|
|
9
|
+
await this.app.scope(__ThisModule__).service.socket.appReady();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async appClose() {
|
|
13
|
+
await this.app.scope(__ThisModule__).service.socket.appClose();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import type { IncomingMessage } from 'node:http';
|
|
2
|
+
import type { IInstanceRecord, ILocaleRecord, Next } from 'vona';
|
|
3
|
+
import type { IOnionSlice } from 'vona-module-a-onion';
|
|
4
|
+
|
|
5
|
+
import { compose } from '@cabloy/compose';
|
|
6
|
+
import { AsyncResource } from 'node:async_hooks';
|
|
7
|
+
import { URL } from 'node:url';
|
|
8
|
+
import { BeanBase, uuidv4 } from 'vona';
|
|
9
|
+
import { Service } from 'vona-module-a-bean';
|
|
10
|
+
import { WebSocket, WebSocketServer } from 'ws';
|
|
11
|
+
|
|
12
|
+
import type {
|
|
13
|
+
ISocketConnectionComposeData,
|
|
14
|
+
ISocketConnectionExecute,
|
|
15
|
+
ISocketConnectionRecord,
|
|
16
|
+
ISocketPacketComposeData,
|
|
17
|
+
} from '../types/socketConnection.ts';
|
|
18
|
+
import type { ISocketEventRecordSystem } from '../types/socketEvent.ts';
|
|
19
|
+
import type { ISocketNamespaceRecord } from '../types/socketNamespace.ts';
|
|
20
|
+
import type { ISocketPacketExecute, ISocketPacketRecord } from '../types/socketPacket.ts';
|
|
21
|
+
|
|
22
|
+
import { getCacheSocketConnections, getCacheSocketPackets } from '../lib/const.ts';
|
|
23
|
+
|
|
24
|
+
@Service()
|
|
25
|
+
export class ServiceSocket extends BeanBase {
|
|
26
|
+
async appReady() {
|
|
27
|
+
// maybe running in play
|
|
28
|
+
if (!this.app.server) return;
|
|
29
|
+
// wss
|
|
30
|
+
this.app.wss = new WebSocketServer({ server: this.app.server });
|
|
31
|
+
this.app.wss.on('connection', (ws, req) => {
|
|
32
|
+
this._onConnection(ws, req);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async appClose() {
|
|
37
|
+
this.app.wss.close();
|
|
38
|
+
this.bean.socket.close();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private async _onConnection(ws: WebSocket, req: IncomingMessage) {
|
|
42
|
+
if (this.app.meta.appClose) {
|
|
43
|
+
// terminate
|
|
44
|
+
ws.terminate();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const url = URL.parse(req.url ?? '', `https://${req.headers.host}`); // req.headers.host 'http://localhost'
|
|
48
|
+
const instanceName = (url?.searchParams.get(this.scope.config.queryKey.instanceName) || undefined) as keyof IInstanceRecord | undefined;
|
|
49
|
+
const locale = (url?.searchParams.get(this.scope.config.queryKey.locale) || undefined) as keyof ILocaleRecord | undefined;
|
|
50
|
+
const tz = (url?.searchParams.get(this.scope.config.queryKey.tz) || undefined) as string | undefined;
|
|
51
|
+
// enter
|
|
52
|
+
return await this.app.bean.executor.newCtx(
|
|
53
|
+
async () => {
|
|
54
|
+
const ctx = this.app.ctx;
|
|
55
|
+
// ws
|
|
56
|
+
Object.defineProperty(ctx, 'ws', {
|
|
57
|
+
get() {
|
|
58
|
+
return ws;
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
// enter
|
|
62
|
+
try {
|
|
63
|
+
// namespace
|
|
64
|
+
ws.namespace = this.getNamespace();
|
|
65
|
+
// id
|
|
66
|
+
ws.id = uuidv4();
|
|
67
|
+
// add
|
|
68
|
+
this.bean.socket.addClient(ws);
|
|
69
|
+
// compose
|
|
70
|
+
await this._getComposeSocketConnections(ws.namespace)({ method: 'enter', ws });
|
|
71
|
+
} catch (err: any) {
|
|
72
|
+
this.app.handleError(err);
|
|
73
|
+
// terminate
|
|
74
|
+
ws.terminate();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
// promise
|
|
78
|
+
return new Promise(resolve => {
|
|
79
|
+
// exit
|
|
80
|
+
ws.onclose = AsyncResource.bind(async () => {
|
|
81
|
+
try {
|
|
82
|
+
// compose
|
|
83
|
+
await this._getComposeSocketConnections(ws.namespace)({ method: 'exit', ws });
|
|
84
|
+
} catch (err: any) {
|
|
85
|
+
this.app.handleError(err);
|
|
86
|
+
} finally {
|
|
87
|
+
// remove
|
|
88
|
+
this.bean.socket.removeClient(ws);
|
|
89
|
+
resolve(undefined);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
// message
|
|
93
|
+
ws.onmessage = AsyncResource.bind(async event => {
|
|
94
|
+
try {
|
|
95
|
+
await this._getComposeSocketPackets(ws.namespace)({ data: event.data, ws });
|
|
96
|
+
} catch (err: any) {
|
|
97
|
+
this.app.handleError(err);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
// error
|
|
101
|
+
ws.onerror = AsyncResource.bind(event => {
|
|
102
|
+
this.$logger.error(event.error);
|
|
103
|
+
});
|
|
104
|
+
// onReady
|
|
105
|
+
ws.sendEvent('sysReady' satisfies keyof ISocketEventRecordSystem as never);
|
|
106
|
+
});
|
|
107
|
+
},
|
|
108
|
+
{ dbInfo: { level: 0 }, innerAccess: false, req, instanceName, locale, tz },
|
|
109
|
+
); // not set instance: true
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public getNamespace(): keyof ISocketNamespaceRecord {
|
|
113
|
+
const globalPrefix = this.scope.config.globalPrefix;
|
|
114
|
+
let path = this.ctx.path;
|
|
115
|
+
if (path.startsWith(globalPrefix)) {
|
|
116
|
+
path = path.substring(globalPrefix.length);
|
|
117
|
+
}
|
|
118
|
+
if (!path) path = '/';
|
|
119
|
+
return path as keyof ISocketNamespaceRecord;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private _getComposeSocketConnections(namespace: keyof ISocketNamespaceRecord) {
|
|
123
|
+
const cacheSocketConnections = getCacheSocketConnections(this.app);
|
|
124
|
+
if (!cacheSocketConnections[namespace]) {
|
|
125
|
+
const connections = this.bean.onion.socketConnection.getOnionsEnabledWrapped(item => {
|
|
126
|
+
return this._wrapOnionConnection(item);
|
|
127
|
+
});
|
|
128
|
+
cacheSocketConnections[namespace] = compose(connections);
|
|
129
|
+
}
|
|
130
|
+
return cacheSocketConnections[namespace];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private _getComposeSocketPackets(namespace: keyof ISocketNamespaceRecord) {
|
|
134
|
+
const cacheSocketPackets = getCacheSocketPackets(this.app);
|
|
135
|
+
if (!cacheSocketPackets[namespace]) {
|
|
136
|
+
const packets = this.bean.onion.socketPacket.getOnionsEnabledWrapped(item => {
|
|
137
|
+
return this._wrapOnionPacket(item);
|
|
138
|
+
});
|
|
139
|
+
cacheSocketPackets[namespace] = compose(packets);
|
|
140
|
+
}
|
|
141
|
+
return cacheSocketPackets[namespace];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private _wrapOnionConnection<T extends keyof ISocketConnectionRecord>(item: IOnionSlice<ISocketConnectionRecord, T>) {
|
|
145
|
+
const fn = (data: ISocketConnectionComposeData, next: Next) => {
|
|
146
|
+
const options = item.beanOptions.options!;
|
|
147
|
+
// execute
|
|
148
|
+
const beanFullName = item.beanOptions.beanFullName;
|
|
149
|
+
const beanInstance = this.app.bean._getBean<ISocketConnectionExecute>(beanFullName as any);
|
|
150
|
+
if (!beanInstance) {
|
|
151
|
+
throw new Error(`socketConnection bean not found: ${beanFullName}`);
|
|
152
|
+
}
|
|
153
|
+
return beanInstance[data.method](data.ws, options, next);
|
|
154
|
+
};
|
|
155
|
+
fn._name = item.name;
|
|
156
|
+
return fn;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private _wrapOnionPacket<T extends keyof ISocketPacketRecord>(item: IOnionSlice<ISocketPacketRecord, T>) {
|
|
160
|
+
const fn = (data: ISocketPacketComposeData, next: Next) => {
|
|
161
|
+
const options = item.beanOptions.options!;
|
|
162
|
+
// execute
|
|
163
|
+
const beanFullName = item.beanOptions.beanFullName;
|
|
164
|
+
const beanInstance = this.app.bean._getBean<ISocketPacketExecute>(beanFullName as any);
|
|
165
|
+
if (!beanInstance) {
|
|
166
|
+
throw new Error(`socketPacket bean not found: ${beanFullName}`);
|
|
167
|
+
}
|
|
168
|
+
return beanInstance.execute(data.data, data.ws, options, _patchPacketNext(data, next));
|
|
169
|
+
};
|
|
170
|
+
fn._name = item.name;
|
|
171
|
+
return fn;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function _patchPacketNext(data: ISocketPacketComposeData, next) {
|
|
176
|
+
return (...args) => {
|
|
177
|
+
const context = args.length === 0 ? data.data : args[0];
|
|
178
|
+
return next({ data: context, ws: data.ws });
|
|
179
|
+
};
|
|
180
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { BeanBase } from 'vona';
|
|
2
|
+
import { Service } from 'vona-module-a-bean';
|
|
3
|
+
|
|
4
|
+
import type { ISendEventOptions } from '../types/socketEvent.ts';
|
|
5
|
+
import type { ISocketNamespaceRecord } from '../types/socketNamespace.ts';
|
|
6
|
+
|
|
7
|
+
@Service()
|
|
8
|
+
export class ServiceSocketEvent extends BeanBase {
|
|
9
|
+
send(id: string, eventName: PropertyKey, data?: unknown, options?: ISendEventOptions) {
|
|
10
|
+
this.sendWorker(id, eventName, data, options);
|
|
11
|
+
this.scope.broadcast.send.emit({ id, eventName, data, options });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
sendWorker(id: string, eventName: PropertyKey, data?: unknown, options?: ISendEventOptions) {
|
|
15
|
+
this.bean.socket.clients[id]?.sendEvent(eventName, data, options);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
broadcast(namespace: keyof ISocketNamespaceRecord, eventName: PropertyKey, data?: unknown, options?: ISendEventOptions) {
|
|
19
|
+
this.broadcastWorker(namespace, eventName, data, options);
|
|
20
|
+
this.scope.broadcast.broadcast.emit({ namespace, eventName, data, options });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
broadcastWorker(namespace: keyof ISocketNamespaceRecord, eventName: PropertyKey, data?: unknown, options?: ISendEventOptions) {
|
|
24
|
+
const ids = this.bean.socket.clientsNamespace[namespace];
|
|
25
|
+
if (!ids) return;
|
|
26
|
+
for (const id of ids) {
|
|
27
|
+
this.bean.socket.clients[id]?.sendEvent(eventName, data, options);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { WebSocket, WebSocketServer } from 'ws';
|
|
2
|
+
|
|
3
|
+
import type { ISocketNamespaceRecord } from './socketNamespace.ts';
|
|
4
|
+
|
|
5
|
+
declare module 'vona' {
|
|
6
|
+
export interface VonaApplication {
|
|
7
|
+
wss: WebSocketServer;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface VonaContext {
|
|
11
|
+
get ws(): WebSocket;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
declare module 'ws' {
|
|
16
|
+
export interface WebSocket {
|
|
17
|
+
namespace: keyof ISocketNamespaceRecord;
|
|
18
|
+
id: string;
|
|
19
|
+
isAlive: boolean;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Next, OmitNever } from 'vona';
|
|
2
|
+
import type { IOnionOptionsDeps, ServiceOnion, TypeOnionOptionsBaseSimple } from 'vona-module-a-onion';
|
|
3
|
+
import type { WebSocket } from 'ws';
|
|
4
|
+
|
|
5
|
+
import type { ISocketNamespaceRecord } from './socketNamespace.ts';
|
|
6
|
+
|
|
7
|
+
export interface ISocketConnectionRecord {}
|
|
8
|
+
|
|
9
|
+
export interface ISocketConnectionExecute {
|
|
10
|
+
enter: (ws: WebSocket, options: IDecoratorSocketConnectionOptions, next: Next) => Promise<void>;
|
|
11
|
+
exit: (ws: WebSocket, options: IDecoratorSocketConnectionOptions, next: Next) => Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface IDecoratorSocketConnectionOptions
|
|
15
|
+
extends TypeOnionOptionsBaseSimple<keyof ISocketNamespaceRecord>, IOnionOptionsDeps<keyof ISocketConnectionRecord> {}
|
|
16
|
+
|
|
17
|
+
export interface ISocketConnectionComposeData {
|
|
18
|
+
method: 'enter' | 'exit';
|
|
19
|
+
ws: WebSocket;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ISocketPacketComposeData {
|
|
23
|
+
data: any;
|
|
24
|
+
ws: WebSocket;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
declare module 'vona-module-a-onion' {
|
|
28
|
+
export interface BeanOnion {
|
|
29
|
+
socketConnection: ServiceOnion<ISocketConnectionRecord>;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
declare module 'vona' {
|
|
34
|
+
export interface ConfigOnions {
|
|
35
|
+
socketConnection: OmitNever<ISocketConnectionRecord>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface IBeanSceneRecord {
|
|
39
|
+
socketConnection: never;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export interface ISocketEventRecordSystem {
|
|
2
|
+
sysReady: never;
|
|
3
|
+
sysPerformAction: never;
|
|
4
|
+
sysPerformActionBack: never;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface ISocketEventRecord {}
|
|
8
|
+
|
|
9
|
+
export const socketEventRecord = {
|
|
10
|
+
sysReady: '_a',
|
|
11
|
+
sysPerformAction: '_b',
|
|
12
|
+
sysPerformActionBack: '_c',
|
|
13
|
+
};
|
|
14
|
+
export const socketEventRecordReverse = {
|
|
15
|
+
_a: 'sysReady',
|
|
16
|
+
_b: 'sysPerformAction',
|
|
17
|
+
_c: 'sysPerformActionBack',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type TypeSocketEventPerformActionMethod = 'get' | 'post' | 'delete' | 'put' | 'patch';
|
|
21
|
+
export interface ISocketEventPerformActionOptions {
|
|
22
|
+
method: TypeSocketEventPerformActionMethod;
|
|
23
|
+
path: string;
|
|
24
|
+
body?: any;
|
|
25
|
+
headers?: object;
|
|
26
|
+
}
|
|
27
|
+
export interface ISocketEventPerformActionOptionsInner {
|
|
28
|
+
i: number;
|
|
29
|
+
m: TypeSocketEventPerformActionMethod;
|
|
30
|
+
q?: object;
|
|
31
|
+
p: string;
|
|
32
|
+
b?: any;
|
|
33
|
+
h?: object;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type TypeSocketPacketEvent<K extends keyof ISocketEventRecord = never> = [K | undefined, ISocketEventRecord[K] | any];
|
|
37
|
+
|
|
38
|
+
export interface ISendEventOptions {
|
|
39
|
+
mask?: boolean | undefined;
|
|
40
|
+
binary?: boolean | undefined;
|
|
41
|
+
compress?: boolean | undefined;
|
|
42
|
+
fin?: boolean | undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
declare module 'ws' {
|
|
46
|
+
export interface WebSocket {
|
|
47
|
+
sendEvent<K extends keyof ISocketEventRecord>(
|
|
48
|
+
eventName: K | PropertyKey,
|
|
49
|
+
data?: ISocketEventRecord[K] | any,
|
|
50
|
+
options?: ISendEventOptions,
|
|
51
|
+
cb?: (err?: Error) => void,
|
|
52
|
+
): void;
|
|
53
|
+
sendEvent<K extends keyof ISocketEventRecord>(eventName: K | PropertyKey, data?: ISocketEventRecord[K] | any, cb?: (err?: Error) => void): void;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { OmitNever } from 'vona';
|
|
2
|
+
import type { ServiceOnion } from 'vona-module-a-onion';
|
|
3
|
+
|
|
4
|
+
export interface ISocketNamespaceRecord {
|
|
5
|
+
'/': never;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface IDecoratorSocketNamespaceOptions<Events extends {} = {}> {
|
|
9
|
+
namespace: keyof ISocketNamespaceRecord;
|
|
10
|
+
events: Events;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
declare module 'vona-module-a-onion' {
|
|
14
|
+
export interface BeanOnion {
|
|
15
|
+
socketNamespace: ServiceOnion<ISocketNamespaceRecord>;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
declare module 'vona' {
|
|
20
|
+
export interface ConfigOnions {
|
|
21
|
+
socketNamespace: OmitNever<ISocketNamespaceRecord>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface IBeanSceneRecord {
|
|
25
|
+
socketNamespace: never;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Next, OmitNever } from 'vona';
|
|
2
|
+
import type { IOnionOptionsDeps, ServiceOnion, TypeOnionOptionsBaseSimple } from 'vona-module-a-onion';
|
|
3
|
+
import type { WebSocket } from 'ws';
|
|
4
|
+
|
|
5
|
+
import type { ISocketNamespaceRecord } from './socketNamespace.ts';
|
|
6
|
+
|
|
7
|
+
export interface ISocketPacketRecord {}
|
|
8
|
+
|
|
9
|
+
export interface ISocketPacketExecute {
|
|
10
|
+
execute: (data: any, ws: WebSocket, options: IDecoratorSocketPacketOptions, next: Next) => Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface IDecoratorSocketPacketOptions
|
|
14
|
+
extends TypeOnionOptionsBaseSimple<keyof ISocketNamespaceRecord>, IOnionOptionsDeps<keyof ISocketPacketRecord> {}
|
|
15
|
+
|
|
16
|
+
declare module 'vona-module-a-onion' {
|
|
17
|
+
export interface BeanOnion {
|
|
18
|
+
socketPacket: ServiceOnion<ISocketPacketRecord>;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
declare module 'vona' {
|
|
23
|
+
export interface ConfigOnions {
|
|
24
|
+
socketPacket: OmitNever<ISocketPacketRecord>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface IBeanSceneRecord {
|
|
28
|
+
socketPacket: never;
|
|
29
|
+
}
|
|
30
|
+
}
|
package/README.md
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
# @cabloy/set
|