@push.rocks/smartsocket 2.0.27 → 3.0.0
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/dist_bundle/bundle.js +11562 -7527
- package/dist_bundle/bundle.js.map +4 -4
- package/dist_ts/00_commitinfo_data.d.ts +1 -1
- package/dist_ts/00_commitinfo_data.js +3 -3
- package/dist_ts/interfaces/index.d.ts +1 -0
- package/dist_ts/interfaces/index.js +2 -1
- package/dist_ts/interfaces/message.d.ts +55 -0
- package/dist_ts/interfaces/message.js +2 -0
- package/dist_ts/smartsocket.classes.smartsocket.d.ts +10 -6
- package/dist_ts/smartsocket.classes.smartsocket.js +43 -39
- package/dist_ts/smartsocket.classes.smartsocketclient.d.ts +15 -0
- package/dist_ts/smartsocket.classes.smartsocketclient.js +148 -74
- package/dist_ts/smartsocket.classes.socketconnection.d.ts +15 -4
- package/dist_ts/smartsocket.classes.socketconnection.js +139 -62
- package/dist_ts/smartsocket.classes.socketrequest.js +18 -12
- package/dist_ts/smartsocket.classes.socketserver.d.ts +15 -15
- package/dist_ts/smartsocket.classes.socketserver.js +152 -45
- package/dist_ts/smartsocket.pluginstyped.d.ts +31 -12
- package/package.json +26 -27
- package/readme.md +216 -74
- package/ts/00_commitinfo_data.ts +2 -2
- package/ts/interfaces/index.ts +1 -0
- package/ts/interfaces/message.ts +67 -0
- package/ts/smartsocket.classes.smartsocket.ts +47 -40
- package/ts/smartsocket.classes.smartsocketclient.ts +183 -80
- package/ts/smartsocket.classes.socketconnection.ts +161 -74
- package/ts/smartsocket.classes.socketrequest.ts +18 -11
- package/ts/smartsocket.classes.socketserver.ts +161 -52
- package/ts/smartsocket.pluginstyped.ts +35 -12
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as plugins from './smartsocket.plugins.js';
|
|
2
|
+
import * as interfaces from './interfaces/index.js';
|
|
2
3
|
|
|
3
4
|
// import interfaces
|
|
4
5
|
import {
|
|
@@ -78,11 +79,15 @@ export class SocketRequest<T extends plugins.typedrequestInterfaces.ITypedReques
|
|
|
78
79
|
* dispatches a socketrequest from the requesting to the receiving side
|
|
79
80
|
*/
|
|
80
81
|
public dispatch(): Promise<ISocketFunctionCallDataResponse<T>> {
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
const message: interfaces.ISocketMessage<interfaces.IFunctionCallPayload> = {
|
|
83
|
+
type: 'function',
|
|
84
|
+
id: this.shortid,
|
|
85
|
+
payload: {
|
|
86
|
+
funcName: this.funcCallData.funcName,
|
|
87
|
+
funcData: this.funcCallData.funcDataArg,
|
|
88
|
+
},
|
|
84
89
|
};
|
|
85
|
-
this.originSocketConnection.
|
|
90
|
+
this.originSocketConnection.sendMessage(message);
|
|
86
91
|
return this.done.promise;
|
|
87
92
|
}
|
|
88
93
|
|
|
@@ -90,7 +95,6 @@ export class SocketRequest<T extends plugins.typedrequestInterfaces.ITypedReques
|
|
|
90
95
|
* handles the response that is received by the requesting side
|
|
91
96
|
*/
|
|
92
97
|
public async handleResponse(responseDataArg: ISocketRequestDataObject<T>) {
|
|
93
|
-
// logger.log('info', 'handling response!');
|
|
94
98
|
this.done.resolve(responseDataArg.funcCallData);
|
|
95
99
|
this.smartsocketRef.socketRequests.remove(this);
|
|
96
100
|
}
|
|
@@ -110,16 +114,19 @@ export class SocketRequest<T extends plugins.typedrequestInterfaces.ITypedReques
|
|
|
110
114
|
logger.log('error', `There is no SocketFunction defined for ${this.funcCallData.funcName}`);
|
|
111
115
|
return;
|
|
112
116
|
}
|
|
113
|
-
|
|
117
|
+
|
|
114
118
|
targetSocketFunction
|
|
115
119
|
.invoke(this.funcCallData, this.originSocketConnection)
|
|
116
120
|
.then((resultData) => {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
+
const message: interfaces.ISocketMessage<interfaces.IFunctionCallPayload> = {
|
|
122
|
+
type: 'functionResponse',
|
|
123
|
+
id: this.shortid,
|
|
124
|
+
payload: {
|
|
125
|
+
funcName: resultData.funcName,
|
|
126
|
+
funcData: resultData.funcDataArg,
|
|
127
|
+
},
|
|
121
128
|
};
|
|
122
|
-
this.originSocketConnection.
|
|
129
|
+
this.originSocketConnection.sendMessage(message);
|
|
123
130
|
this.smartsocketRef.socketRequests.remove(this);
|
|
124
131
|
});
|
|
125
132
|
}
|
|
@@ -6,16 +6,16 @@ import { Smartsocket } from './smartsocket.classes.smartsocket.js';
|
|
|
6
6
|
import { logger } from './smartsocket.logging.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* class
|
|
10
|
-
* handles the
|
|
9
|
+
* class SocketServer
|
|
10
|
+
* handles the WebSocket server in standalone mode, or provides hooks for smartserve integration
|
|
11
11
|
*/
|
|
12
12
|
export class SocketServer {
|
|
13
13
|
private smartsocket: Smartsocket;
|
|
14
|
-
private httpServerDeferred: plugins.smartpromise.Deferred<any>;
|
|
15
14
|
private httpServer: pluginsTyped.http.Server | pluginsTyped.https.Server;
|
|
15
|
+
private wsServer: pluginsTyped.ws.WebSocketServer;
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
18
|
+
* whether httpServer is standalone (created by us)
|
|
19
19
|
*/
|
|
20
20
|
private standaloneServer = false;
|
|
21
21
|
|
|
@@ -24,71 +24,180 @@ export class SocketServer {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
27
|
+
* Starts listening to incoming websocket connections (standalone mode).
|
|
28
|
+
* If no port is specified, this is a no-op (hooks mode via smartserve).
|
|
29
29
|
*/
|
|
30
|
-
public async
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
this.httpServerDeferred = plugins.smartpromise.defer();
|
|
35
|
-
await serverArg.startedPromise;
|
|
36
|
-
this.httpServer = serverArg.httpServer;
|
|
37
|
-
this.httpServerDeferred.resolve();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* gets the server for socket.io
|
|
42
|
-
*/
|
|
43
|
-
public async getServerForSocketIo() {
|
|
44
|
-
if (this.httpServerDeferred) {
|
|
45
|
-
await this.httpServerDeferred.promise;
|
|
46
|
-
}
|
|
47
|
-
if (this.httpServer) {
|
|
48
|
-
return this.httpServer;
|
|
49
|
-
} else {
|
|
50
|
-
const httpModule = await this.smartsocket.smartenv.getSafeNodeModule('http');
|
|
51
|
-
this.httpServer = new httpModule.Server();
|
|
52
|
-
this.standaloneServer = true;
|
|
53
|
-
return this.httpServer;
|
|
30
|
+
public async start() {
|
|
31
|
+
// If no port specified, we're in hooks mode - nothing to start
|
|
32
|
+
if (!this.smartsocket.options.port) {
|
|
33
|
+
return;
|
|
54
34
|
}
|
|
35
|
+
|
|
36
|
+
// Standalone mode - create our own HTTP server and WebSocket server
|
|
37
|
+
const done = plugins.smartpromise.defer();
|
|
38
|
+
const httpModule = await this.smartsocket.smartenv.getSafeNodeModule('http');
|
|
39
|
+
const wsModule = await this.smartsocket.smartenv.getSafeNodeModule('ws');
|
|
40
|
+
|
|
41
|
+
this.httpServer = httpModule.createServer();
|
|
42
|
+
this.standaloneServer = true;
|
|
43
|
+
|
|
44
|
+
// Create WebSocket server attached to HTTP server
|
|
45
|
+
this.wsServer = new wsModule.WebSocketServer({ server: this.httpServer });
|
|
46
|
+
|
|
47
|
+
this.wsServer.on('connection', (ws: pluginsTyped.ws.WebSocket) => {
|
|
48
|
+
this.smartsocket.handleNewConnection(ws);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
this.httpServer.listen(this.smartsocket.options.port, () => {
|
|
52
|
+
logger.log(
|
|
53
|
+
'success',
|
|
54
|
+
`Server started in standalone mode on port ${this.smartsocket.options.port}`
|
|
55
|
+
);
|
|
56
|
+
done.resolve();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
await done.promise;
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
/**
|
|
58
|
-
*
|
|
63
|
+
* closes the server
|
|
59
64
|
*/
|
|
60
|
-
public async
|
|
61
|
-
const done = plugins.smartpromise.defer();
|
|
65
|
+
public async stop() {
|
|
66
|
+
const done = plugins.smartpromise.defer<void>();
|
|
67
|
+
let resolved = false;
|
|
68
|
+
|
|
69
|
+
if (this.wsServer) {
|
|
70
|
+
// Close all WebSocket connections
|
|
71
|
+
this.wsServer.clients.forEach((client) => {
|
|
72
|
+
client.terminate();
|
|
73
|
+
});
|
|
74
|
+
this.wsServer.close();
|
|
75
|
+
this.wsServer = null;
|
|
76
|
+
}
|
|
62
77
|
|
|
63
|
-
// handle http servers
|
|
64
|
-
// in case an external server has been set "this.standaloneServer" should be false
|
|
65
78
|
if (this.httpServer && this.standaloneServer) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
79
|
+
const resolveOnce = () => {
|
|
80
|
+
if (!resolved) {
|
|
81
|
+
resolved = true;
|
|
82
|
+
this.httpServer = null;
|
|
83
|
+
this.standaloneServer = false;
|
|
84
|
+
done.resolve();
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
this.httpServer.close(() => {
|
|
89
|
+
resolveOnce();
|
|
76
90
|
});
|
|
91
|
+
|
|
92
|
+
// Add a timeout in case close callback doesn't fire
|
|
93
|
+
const timeoutId = setTimeout(() => {
|
|
94
|
+
resolveOnce();
|
|
95
|
+
}, 2000);
|
|
96
|
+
|
|
97
|
+
// Ensure timeout doesn't keep process alive
|
|
98
|
+
if (timeoutId.unref) {
|
|
99
|
+
timeoutId.unref();
|
|
100
|
+
}
|
|
77
101
|
} else {
|
|
78
102
|
done.resolve();
|
|
79
103
|
}
|
|
80
104
|
|
|
81
|
-
// nothing else to do if express server is set
|
|
82
105
|
await done.promise;
|
|
83
|
-
return;
|
|
84
106
|
}
|
|
85
107
|
|
|
86
108
|
/**
|
|
87
|
-
*
|
|
109
|
+
* Returns WebSocket hooks for integration with smartserve.
|
|
110
|
+
* Pass these hooks to SmartServe's websocket config.
|
|
88
111
|
*/
|
|
89
|
-
public
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
112
|
+
public getSmartserveWebSocketHooks(): pluginsTyped.ISmartserveWebSocketHooks {
|
|
113
|
+
return {
|
|
114
|
+
onOpen: async (peer: pluginsTyped.ISmartserveWebSocketPeer) => {
|
|
115
|
+
// Create a wrapper that adapts ISmartserveWebSocketPeer to WebSocket-like interface
|
|
116
|
+
const wsLikeSocket = this.createWsLikeFromPeer(peer);
|
|
117
|
+
await this.smartsocket.handleNewConnection(wsLikeSocket as any);
|
|
118
|
+
},
|
|
119
|
+
onMessage: async (peer: pluginsTyped.ISmartserveWebSocketPeer, message: pluginsTyped.ISmartserveWebSocketMessage) => {
|
|
120
|
+
// Dispatch message to the SocketConnection via the adapter
|
|
121
|
+
const adapter = peer.data.get('smartsocket_adapter') as any;
|
|
122
|
+
if (adapter) {
|
|
123
|
+
let textData: string | undefined;
|
|
124
|
+
if (message.type === 'text' && message.text) {
|
|
125
|
+
textData = message.text;
|
|
126
|
+
} else if (message.type === 'binary' && message.data) {
|
|
127
|
+
// Convert binary to text (Buffer/Uint8Array to string)
|
|
128
|
+
textData = new TextDecoder().decode(message.data);
|
|
129
|
+
}
|
|
130
|
+
if (textData) {
|
|
131
|
+
adapter.dispatchMessage(textData);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
onClose: async (peer: pluginsTyped.ISmartserveWebSocketPeer, code: number, reason: string) => {
|
|
136
|
+
// Dispatch close to the SocketConnection via the adapter
|
|
137
|
+
const adapter = peer.data.get('smartsocket_adapter') as any;
|
|
138
|
+
if (adapter) {
|
|
139
|
+
adapter.dispatchClose();
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
onError: async (peer: pluginsTyped.ISmartserveWebSocketPeer, error: Error) => {
|
|
143
|
+
// Dispatch error to the SocketConnection via the adapter
|
|
144
|
+
const adapter = peer.data.get('smartsocket_adapter') as any;
|
|
145
|
+
if (adapter) {
|
|
146
|
+
adapter.dispatchError();
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Creates a WebSocket-like object from a smartserve peer
|
|
154
|
+
* This allows our SocketConnection to work with both native WebSocket and smartserve peers
|
|
155
|
+
*/
|
|
156
|
+
private createWsLikeFromPeer(peer: pluginsTyped.ISmartserveWebSocketPeer): any {
|
|
157
|
+
const messageListeners: Array<(event: any) => void> = [];
|
|
158
|
+
const closeListeners: Array<() => void> = [];
|
|
159
|
+
const errorListeners: Array<() => void> = [];
|
|
160
|
+
|
|
161
|
+
// Store the adapter on the peer for message routing
|
|
162
|
+
peer.data.set('smartsocket_adapter', {
|
|
163
|
+
dispatchMessage: (data: string) => {
|
|
164
|
+
messageListeners.forEach((listener) => {
|
|
165
|
+
listener({ data });
|
|
166
|
+
});
|
|
167
|
+
},
|
|
168
|
+
dispatchClose: () => {
|
|
169
|
+
closeListeners.forEach((listener) => listener());
|
|
170
|
+
},
|
|
171
|
+
dispatchError: () => {
|
|
172
|
+
errorListeners.forEach((listener) => listener());
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
readyState: peer.readyState,
|
|
178
|
+
send: (data: string) => peer.send(data),
|
|
179
|
+
close: (code?: number, reason?: string) => peer.close(code, reason),
|
|
180
|
+
addEventListener: (event: string, listener: any) => {
|
|
181
|
+
if (event === 'message') {
|
|
182
|
+
messageListeners.push(listener);
|
|
183
|
+
} else if (event === 'close') {
|
|
184
|
+
closeListeners.push(listener);
|
|
185
|
+
} else if (event === 'error') {
|
|
186
|
+
errorListeners.push(listener);
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
removeEventListener: (event: string, listener: any) => {
|
|
190
|
+
if (event === 'message') {
|
|
191
|
+
const idx = messageListeners.indexOf(listener);
|
|
192
|
+
if (idx >= 0) messageListeners.splice(idx, 1);
|
|
193
|
+
} else if (event === 'close') {
|
|
194
|
+
const idx = closeListeners.indexOf(listener);
|
|
195
|
+
if (idx >= 0) closeListeners.splice(idx, 1);
|
|
196
|
+
} else if (event === 'error') {
|
|
197
|
+
const idx = errorListeners.indexOf(listener);
|
|
198
|
+
if (idx >= 0) errorListeners.splice(idx, 1);
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
};
|
|
93
202
|
}
|
|
94
203
|
}
|
|
@@ -4,20 +4,43 @@ import type * as https from 'https';
|
|
|
4
4
|
|
|
5
5
|
export type { http, https };
|
|
6
6
|
|
|
7
|
-
//
|
|
8
|
-
import type * as
|
|
7
|
+
// third party scope - ws types
|
|
8
|
+
import type * as wsTypes from 'ws';
|
|
9
9
|
|
|
10
|
-
export
|
|
10
|
+
export namespace ws {
|
|
11
|
+
export type WebSocket = wsTypes.WebSocket;
|
|
12
|
+
export type WebSocketServer = wsTypes.WebSocketServer;
|
|
13
|
+
export type RawData = wsTypes.RawData;
|
|
14
|
+
}
|
|
11
15
|
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
// smartserve compatibility interface (for setExternalServer)
|
|
17
|
+
// This mirrors the IWebSocketPeer interface from smartserve
|
|
18
|
+
export interface ISmartserveWebSocketPeer {
|
|
19
|
+
id: string;
|
|
20
|
+
url: string;
|
|
21
|
+
readyState: 0 | 1 | 2 | 3;
|
|
22
|
+
protocol: string;
|
|
23
|
+
extensions: string;
|
|
24
|
+
send(data: string): void;
|
|
25
|
+
sendBinary(data: Uint8Array | ArrayBuffer): void;
|
|
26
|
+
close(code?: number, reason?: string): void;
|
|
27
|
+
ping(data?: Uint8Array): void;
|
|
28
|
+
terminate(): void;
|
|
29
|
+
context: any;
|
|
30
|
+
data: Map<string, unknown>;
|
|
31
|
+
tags: Set<string>;
|
|
32
|
+
}
|
|
15
33
|
|
|
16
|
-
export
|
|
17
|
-
|
|
18
|
-
|
|
34
|
+
export interface ISmartserveWebSocketMessage {
|
|
35
|
+
type: 'text' | 'binary';
|
|
36
|
+
text?: string;
|
|
37
|
+
data?: Uint8Array;
|
|
38
|
+
size: number;
|
|
19
39
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
40
|
+
|
|
41
|
+
export interface ISmartserveWebSocketHooks {
|
|
42
|
+
onOpen?: (peer: ISmartserveWebSocketPeer) => void | Promise<void>;
|
|
43
|
+
onMessage?: (peer: ISmartserveWebSocketPeer, message: ISmartserveWebSocketMessage) => void | Promise<void>;
|
|
44
|
+
onClose?: (peer: ISmartserveWebSocketPeer, code: number, reason: string) => void | Promise<void>;
|
|
45
|
+
onError?: (peer: ISmartserveWebSocketPeer, error: Error) => void | Promise<void>;
|
|
23
46
|
}
|