javonet-nodejs-sdk 2.5.20 → 2.6.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/dist/core/handler/Handler.cjs +2 -2
- package/dist/core/handler/ResolveReferenceHandler.cjs +8 -1
- package/dist/core/interpreter/Interpreter.cjs +31 -56
- package/dist/core/receiver/Receiver.cjs +20 -12
- package/dist/core/webSocketClient/WebSocketClient.cjs +64 -9
- package/dist/core/webSocketClient/WebSocketClientBrowser.cjs +59 -4
- package/dist/sdk/InvocationContext.cjs +6 -2
- package/dist/types/core/handler/ResolveReferenceHandler.d.ts +1 -4
- package/dist/types/core/interpreter/Interpreter.d.ts +3 -1
- package/dist/types/core/receiver/Receiver.d.ts +1 -0
- package/dist/types/core/webSocketClient/WebSocketClient.d.ts +24 -2
- package/dist/types/core/webSocketClient/WebSocketClientBrowser.d.ts +24 -2
- package/dist/types/utils/CommandType.d.ts +3 -0
- package/dist/utils/CommandType.cjs +4 -1
- package/dist/utils/exception/ExceptionSerializer.cjs +46 -21
- package/lib/core/handler/Handler.js +2 -2
- package/lib/core/handler/ResolveReferenceHandler.js +9 -4
- package/lib/core/interpreter/Interpreter.js +39 -71
- package/lib/core/receiver/Receiver.js +22 -14
- package/lib/core/webSocketClient/WebSocketClient.js +73 -7
- package/lib/core/webSocketClient/WebSocketClientBrowser.js +63 -2
- package/lib/sdk/InvocationContext.js +8 -2
- package/lib/utils/CommandType.js +3 -0
- package/lib/utils/exception/ExceptionSerializer.js +51 -21
- package/package.json +2 -2
|
@@ -28,19 +28,31 @@ var import_ExceptionType = require("../ExceptionType.cjs");
|
|
|
28
28
|
class ExceptionSerializer {
|
|
29
29
|
static serializeException(exception, command) {
|
|
30
30
|
let exceptionCommand = new import_Command.Command(import_RuntimeName.RuntimeName.Nodejs, import_CommandType.CommandType.Exception, []);
|
|
31
|
-
exceptionCommand = exceptionCommand.addArgToPayload(this.getExceptionCode(exception));
|
|
32
|
-
exceptionCommand = exceptionCommand.addArgToPayload(command.toString());
|
|
33
|
-
exceptionCommand = exceptionCommand.addArgToPayload(exception.name);
|
|
34
|
-
exceptionCommand = exceptionCommand.addArgToPayload(exception.message);
|
|
35
31
|
let stackClasses = [];
|
|
36
32
|
let stackMethods = [];
|
|
37
33
|
let stackLines = [];
|
|
38
34
|
let stackFiles = [];
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
try {
|
|
36
|
+
this.serializeStackTrace(exception, stackClasses, stackMethods, stackLines, stackFiles);
|
|
37
|
+
exceptionCommand = exceptionCommand.addArgToPayload(this.getExceptionCode(exception));
|
|
38
|
+
exceptionCommand = exceptionCommand.addArgToPayload(command ? command.toString() : "Command is null");
|
|
39
|
+
exceptionCommand = exceptionCommand.addArgToPayload(exception.name);
|
|
40
|
+
exceptionCommand = exceptionCommand.addArgToPayload(exception.message);
|
|
41
|
+
exceptionCommand = exceptionCommand.addArgToPayload(stackClasses.join("|"));
|
|
42
|
+
exceptionCommand = exceptionCommand.addArgToPayload(stackMethods.join("|"));
|
|
43
|
+
exceptionCommand = exceptionCommand.addArgToPayload(stackLines.join("|"));
|
|
44
|
+
exceptionCommand = exceptionCommand.addArgToPayload(stackFiles.join("|"));
|
|
45
|
+
} catch (e) {
|
|
46
|
+
exceptionCommand = new import_Command.Command(import_RuntimeName.RuntimeName.Nodejs, import_CommandType.CommandType.Exception, []);
|
|
47
|
+
exceptionCommand = exceptionCommand.addArgToPayload(this.getExceptionCode(e));
|
|
48
|
+
exceptionCommand = exceptionCommand.addArgToPayload(command ? command.toString() : "Command is null");
|
|
49
|
+
exceptionCommand = exceptionCommand.addArgToPayload("Node.js Exception Serialization Error");
|
|
50
|
+
exceptionCommand = exceptionCommand.addArgToPayload(e.message);
|
|
51
|
+
exceptionCommand = exceptionCommand.addArgToPayload("ExceptionSerializer");
|
|
52
|
+
exceptionCommand = exceptionCommand.addArgToPayload("serializeException");
|
|
53
|
+
exceptionCommand = exceptionCommand.addArgToPayload("unknown");
|
|
54
|
+
exceptionCommand = exceptionCommand.addArgToPayload("ExceptionSerializer.js");
|
|
55
|
+
}
|
|
44
56
|
return exceptionCommand;
|
|
45
57
|
}
|
|
46
58
|
static getExceptionCode(exception) {
|
|
@@ -56,22 +68,35 @@ class ExceptionSerializer {
|
|
|
56
68
|
}
|
|
57
69
|
}
|
|
58
70
|
static serializeStackTrace(exception, stackClasses, stackMethods, stackLines, stackFiles) {
|
|
71
|
+
if (!exception || typeof exception.stack !== "string") {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
59
74
|
const stackTrace = exception.stack.split("\n").slice(1);
|
|
60
|
-
for (
|
|
61
|
-
const
|
|
75
|
+
for (const line of stackTrace) {
|
|
76
|
+
const trimmedLine = line.trim();
|
|
77
|
+
if (trimmedLine.includes("Javonet.Node.js")) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
let parts = trimmedLine.match(/at\s+(.*?)\s+\((.*?):(\d+):\d+\)/);
|
|
62
81
|
if (parts) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const parts2 = stackTrace[i].trim().match(/at\s(.*):(\d+):(\d+)/);
|
|
69
|
-
if (parts2) {
|
|
82
|
+
const classAndMethod = parts[1].split(".");
|
|
83
|
+
if (classAndMethod.length > 1) {
|
|
84
|
+
stackClasses.push(classAndMethod[0]);
|
|
85
|
+
stackMethods.push(classAndMethod.slice(1).join("."));
|
|
86
|
+
} else {
|
|
70
87
|
stackClasses.push("unknown");
|
|
71
|
-
stackMethods.push(
|
|
72
|
-
stackLines.push(parts2[2]);
|
|
73
|
-
stackFiles.push(parts2[1]);
|
|
88
|
+
stackMethods.push(parts[1]);
|
|
74
89
|
}
|
|
90
|
+
stackFiles.push(parts[2]);
|
|
91
|
+
stackLines.push(parts[3]);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
parts = trimmedLine.match(/at\s+(.*?):(\d+):\d+/);
|
|
95
|
+
if (parts) {
|
|
96
|
+
stackClasses.push("unknown");
|
|
97
|
+
stackMethods.push("unknown");
|
|
98
|
+
stackFiles.push(parts[1]);
|
|
99
|
+
stackLines.push(parts[2]);
|
|
75
100
|
}
|
|
76
101
|
}
|
|
77
102
|
}
|
|
@@ -100,8 +100,8 @@ class Handler {
|
|
|
100
100
|
}
|
|
101
101
|
let response = handlers[command.commandType].handleCommand(command)
|
|
102
102
|
return this.parseCommand(response, command.runtimeName)
|
|
103
|
-
} catch (
|
|
104
|
-
return ExceptionSerializer.serializeException(
|
|
103
|
+
} catch (e) {
|
|
104
|
+
return ExceptionSerializer.serializeException(e, command)
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import { ReferencesCache } from '../referenceCache/ReferencesCache.js'
|
|
3
3
|
import { AbstractHandler } from './AbstractHandler.js'
|
|
4
|
+
import { CommandType } from '../../utils/CommandType.js'
|
|
5
|
+
import { RuntimeName } from '../../utils/RuntimeName.js'
|
|
6
|
+
import { Command } from '../../utils/Command.js'
|
|
4
7
|
|
|
5
|
-
/**
|
|
6
|
-
* @typedef {import('../../utils/Command.js').Command} Command
|
|
7
|
-
*/
|
|
8
8
|
|
|
9
9
|
class ResolveReferenceHandler extends AbstractHandler {
|
|
10
10
|
constructor() {
|
|
@@ -16,7 +16,12 @@ class ResolveReferenceHandler extends AbstractHandler {
|
|
|
16
16
|
* @returns {any}
|
|
17
17
|
*/
|
|
18
18
|
process(command) {
|
|
19
|
-
|
|
19
|
+
if (command.runtimeName === RuntimeName.Nodejs) {
|
|
20
|
+
return ReferencesCache.getInstance().resolveReference(command.payload[0])
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
return new Command(command.runtimeName, CommandType.Reference, command.payload[0])
|
|
24
|
+
}
|
|
20
25
|
}
|
|
21
26
|
}
|
|
22
27
|
|
|
@@ -29,7 +29,16 @@ const requireDynamic = getRequire(import.meta.url)
|
|
|
29
29
|
|
|
30
30
|
export class Interpreter {
|
|
31
31
|
/** @type {Handler | null} */
|
|
32
|
-
|
|
32
|
+
_handler = null
|
|
33
|
+
|
|
34
|
+
/** @type {Handler} */
|
|
35
|
+
get handler() {
|
|
36
|
+
if (!this._handler) {
|
|
37
|
+
this._handler = new Handler(this)
|
|
38
|
+
}
|
|
39
|
+
return this._handler
|
|
40
|
+
}
|
|
41
|
+
|
|
33
42
|
/**
|
|
34
43
|
*
|
|
35
44
|
* @param {Command} command
|
|
@@ -38,57 +47,42 @@ export class Interpreter {
|
|
|
38
47
|
*/
|
|
39
48
|
async executeAsync(command, connectionData) {
|
|
40
49
|
try {
|
|
41
|
-
if (!this.handler) {
|
|
42
|
-
this.handler = new Handler(this)
|
|
43
|
-
}
|
|
44
50
|
let messageByteArray = new CommandSerializer().serialize(command, connectionData)
|
|
45
51
|
/** @type {Int8Array | undefined} */
|
|
46
52
|
let responseByteArray = undefined
|
|
47
53
|
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
if (connectionData.connectionType === ConnectionType.WEB_SOCKET) {
|
|
55
|
+
const _response = await _TransmitterWebsocket?.sendCommand(messageByteArray, connectionData)
|
|
56
|
+
if (_response) {
|
|
57
|
+
const command = new CommandDeserializer(_response).deserialize()
|
|
58
|
+
return command
|
|
59
|
+
} else {
|
|
60
|
+
throw new Error('Response not received from TransmitterWebsocket')
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
if (!isNodejsRuntime()) {
|
|
64
|
+
throw new Error('InMemory is only allowed in Nodejs runtime')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (command.runtimeName === RuntimeName.Nodejs) {
|
|
54
68
|
if (!_Receiver) {
|
|
55
69
|
const { Receiver } = requireDynamic('../receiver/Receiver.js')
|
|
56
70
|
_Receiver = Receiver
|
|
57
71
|
}
|
|
58
|
-
responseByteArray = _Receiver?.sendCommand(messageByteArray)
|
|
72
|
+
responseByteArray = await _Receiver?.sendCommand(messageByteArray)
|
|
59
73
|
} else {
|
|
60
|
-
throw new Error('Node.js runtime not found')
|
|
61
|
-
}
|
|
62
|
-
} else if (connectionData.connectionType === ConnectionType.WEB_SOCKET) {
|
|
63
|
-
try {
|
|
64
|
-
const _response = await _TransmitterWebsocket?.sendCommand(
|
|
65
|
-
messageByteArray,
|
|
66
|
-
connectionData
|
|
67
|
-
)
|
|
68
|
-
if (_response) {
|
|
69
|
-
const command = new CommandDeserializer(_response).deserialize()
|
|
70
|
-
return command
|
|
71
|
-
} else {
|
|
72
|
-
throw new Error('Response not received from TransmitterWebsocket')
|
|
73
|
-
}
|
|
74
|
-
} catch (error) {
|
|
75
|
-
throw error
|
|
76
|
-
}
|
|
77
|
-
} else {
|
|
78
|
-
if (isNodejsRuntime()) {
|
|
79
|
-
// lazy transmitter loading
|
|
80
74
|
if (!_Transmitter) {
|
|
81
75
|
const { Transmitter } = requireDynamic('../transmitter/Transmitter.js')
|
|
82
76
|
_Transmitter = Transmitter
|
|
83
77
|
}
|
|
84
78
|
responseByteArray = await _Transmitter?.sendCommand(messageByteArray)
|
|
85
|
-
} else {
|
|
86
|
-
throw new Error('Allowed only to run in nodejs runtime')
|
|
87
79
|
}
|
|
88
80
|
}
|
|
81
|
+
|
|
89
82
|
if (!responseByteArray) {
|
|
90
83
|
throw new Error('No response received from Transmitter')
|
|
91
84
|
}
|
|
85
|
+
|
|
92
86
|
return new CommandDeserializer(responseByteArray).deserialize()
|
|
93
87
|
} catch (error) {
|
|
94
88
|
throw error
|
|
@@ -103,60 +97,37 @@ export class Interpreter {
|
|
|
103
97
|
*/
|
|
104
98
|
execute(command, connectionData) {
|
|
105
99
|
try {
|
|
106
|
-
if (!this.handler) {
|
|
107
|
-
this.handler = new Handler(this)
|
|
108
|
-
}
|
|
109
100
|
let messageByteArray = new CommandSerializer().serialize(command, connectionData)
|
|
110
101
|
/** @type {Int8Array | undefined} */
|
|
111
102
|
let responseByteArray = undefined
|
|
112
103
|
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
) {
|
|
117
|
-
if (isNodejsRuntime()) {
|
|
118
|
-
|
|
104
|
+
if (connectionData.connectionType === ConnectionType.WEB_SOCKET) {
|
|
105
|
+
throw new Error('Not supported')
|
|
106
|
+
}
|
|
107
|
+
if (connectionData.connectionType === ConnectionType.IN_MEMORY) {
|
|
108
|
+
if (!isNodejsRuntime()) {
|
|
109
|
+
throw new Error('InMemory is only allowed in Nodejs runtime')
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (command.runtimeName === RuntimeName.Nodejs) {
|
|
119
113
|
if (!_Receiver) {
|
|
120
114
|
const { Receiver } = requireDynamic('../receiver/Receiver.js')
|
|
121
115
|
_Receiver = Receiver
|
|
122
116
|
}
|
|
123
117
|
responseByteArray = _Receiver?.sendCommand(messageByteArray)
|
|
124
|
-
}
|
|
125
|
-
} else if (connectionData.connectionType === ConnectionType.WEB_SOCKET) {
|
|
126
|
-
// lazy transmitter websocket loading
|
|
127
|
-
const promise = _TransmitterWebsocket?.sendCommand(messageByteArray, connectionData)
|
|
128
|
-
|
|
129
|
-
if (!promise) {
|
|
130
|
-
throw new Error('TransmitterWebsocket not found')
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return promise
|
|
134
|
-
.then((_response) => {
|
|
135
|
-
if (_response) {
|
|
136
|
-
const command = new CommandDeserializer(_response).deserialize()
|
|
137
|
-
return command
|
|
138
|
-
} else {
|
|
139
|
-
throw new Error('Response not received from TransmitterWebsocket')
|
|
140
|
-
}
|
|
141
|
-
})
|
|
142
|
-
.catch((error) => {
|
|
143
|
-
throw error
|
|
144
|
-
})
|
|
145
|
-
} else {
|
|
146
|
-
if (isNodejsRuntime()) {
|
|
118
|
+
} else {
|
|
147
119
|
if (!_Transmitter) {
|
|
148
120
|
const { Transmitter } = requireDynamic('../transmitter/Transmitter.js')
|
|
149
121
|
_Transmitter = Transmitter
|
|
150
122
|
}
|
|
151
|
-
|
|
152
123
|
responseByteArray = _Transmitter?.sendCommand(messageByteArray)
|
|
153
|
-
} else {
|
|
154
|
-
throw new Error('Allowed only to run in nodejs runtime')
|
|
155
124
|
}
|
|
156
125
|
}
|
|
126
|
+
|
|
157
127
|
if (!responseByteArray) {
|
|
158
128
|
throw new Error('No response received from Transmitter')
|
|
159
129
|
}
|
|
130
|
+
|
|
160
131
|
return new CommandDeserializer(responseByteArray).deserialize()
|
|
161
132
|
} catch (error) {
|
|
162
133
|
throw error
|
|
@@ -170,9 +141,6 @@ export class Interpreter {
|
|
|
170
141
|
*/
|
|
171
142
|
process(messageByteArray) {
|
|
172
143
|
try {
|
|
173
|
-
if (!this.handler) {
|
|
174
|
-
this.handler = new Handler(this)
|
|
175
|
-
}
|
|
176
144
|
const receivedCommand = new CommandDeserializer(messageByteArray).deserialize()
|
|
177
145
|
return this.handler?.handleCommand(receivedCommand)
|
|
178
146
|
} catch (error) {
|
|
@@ -3,30 +3,38 @@ import { Interpreter } from '../interpreter/Interpreter.js'
|
|
|
3
3
|
import { CommandSerializer } from '../protocol/CommandSerializer.js'
|
|
4
4
|
import { getRequire } from '../../utils/Runtime.js'
|
|
5
5
|
import { InMemoryConnectionData } from '../../utils/connectionData/InMemoryConnectionData.js'
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
import { ExceptionSerializer } from '../../utils/exception/ExceptionSerializer.js'
|
|
7
|
+
import { Command } from '../../utils/Command.js'
|
|
8
|
+
import { CommandType } from '../../utils/CommandType.js'
|
|
9
|
+
import { RuntimeName } from '../../utils/RuntimeName.js'
|
|
10
|
+
import { RuntimeLogger } from '../../utils/RuntimeLogger.js'
|
|
11
11
|
|
|
12
12
|
export class Receiver {
|
|
13
13
|
static connectionData = new InMemoryConnectionData()
|
|
14
14
|
Receiver() {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static getRuntimeInfo() {
|
|
19
|
+
return RuntimeLogger.getRuntimeInfo()
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* @param {Int8Array} messageByteArray
|
|
24
24
|
*/
|
|
25
25
|
static sendCommand(messageByteArray) {
|
|
26
|
-
|
|
27
|
-
new
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
try {
|
|
27
|
+
return new CommandSerializer().serialize(
|
|
28
|
+
new Interpreter().process(messageByteArray),
|
|
29
|
+
this.connectionData
|
|
30
|
+
)
|
|
31
|
+
} catch (error) {
|
|
32
|
+
const exceptionCommand = ExceptionSerializer.serializeException(
|
|
33
|
+
error,
|
|
34
|
+
new Command(RuntimeName.Nodejs, CommandType.Exception, [])
|
|
35
|
+
)
|
|
36
|
+
return new CommandSerializer().serialize(exceptionCommand, this.connectionData)
|
|
37
|
+
}
|
|
30
38
|
}
|
|
31
39
|
|
|
32
40
|
/**
|
|
@@ -36,6 +36,12 @@ let WebSocket = null
|
|
|
36
36
|
/** @type {Record<string, wsClient>} */
|
|
37
37
|
export const clients = {}
|
|
38
38
|
|
|
39
|
+
/** @type {Record<string, Array<{resolve: Function, reject: Function, messageArray: Int8Array}>>} */
|
|
40
|
+
export const messageQueue = {}
|
|
41
|
+
|
|
42
|
+
/** @type {Record<string, boolean>} */
|
|
43
|
+
export const processingQueues = {}
|
|
44
|
+
|
|
39
45
|
/**
|
|
40
46
|
* WebSocketClient class that handles WebSocket connection, message sending, and automatic disconnection.
|
|
41
47
|
*/
|
|
@@ -61,16 +67,65 @@ class WebSocketClient {
|
|
|
61
67
|
}
|
|
62
68
|
|
|
63
69
|
/**
|
|
64
|
-
* Sends messageArray through websocket connection
|
|
70
|
+
* Sends messageArray through websocket connection with guaranteed order preservation
|
|
65
71
|
* @async
|
|
66
72
|
* @param {Int8Array} messageArray
|
|
67
73
|
* @returns {Promise<Int8Array>}
|
|
68
74
|
*/
|
|
69
75
|
send(messageArray) {
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
if (!messageQueue[this.url]) {
|
|
78
|
+
messageQueue[this.url] = []
|
|
79
|
+
}
|
|
80
|
+
messageQueue[this.url].push({ resolve, reject, messageArray })
|
|
81
|
+
|
|
82
|
+
this._processMessage()
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Processes message queue sequentially to maintain order
|
|
88
|
+
* @private
|
|
89
|
+
* @async
|
|
90
|
+
*/
|
|
91
|
+
async _processMessage() {
|
|
92
|
+
if (processingQueues[this.url]) {
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
processingQueues[this.url] = true
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
while (messageQueue[this.url] && messageQueue[this.url].length > 0) {
|
|
99
|
+
const item = messageQueue[this.url].shift()
|
|
100
|
+
if (!item) break
|
|
101
|
+
|
|
102
|
+
const { resolve, reject, messageArray } = item
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const response = await this._send(messageArray)
|
|
106
|
+
resolve(response)
|
|
107
|
+
} catch (error) {
|
|
108
|
+
reject(error)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} finally {
|
|
112
|
+
processingQueues[this.url] = false
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Sends a single message through websocket connection
|
|
118
|
+
* @private
|
|
119
|
+
* @async
|
|
120
|
+
* @param {Int8Array} messageArray
|
|
121
|
+
* @returns {Promise<Int8Array>}
|
|
122
|
+
*/
|
|
123
|
+
_send(messageArray) {
|
|
70
124
|
return new Promise((resolve, reject) => {
|
|
71
125
|
const client = this.instance
|
|
72
|
-
if (client) {
|
|
126
|
+
if (client && this.isConnected) {
|
|
73
127
|
client.send(/** @type {any} */ (messageArray))
|
|
128
|
+
|
|
74
129
|
const messageHandler = (/** @type {any} */ message) => {
|
|
75
130
|
resolve(message)
|
|
76
131
|
if (this.isDisconnectedAfterMessage) {
|
|
@@ -81,16 +136,16 @@ class WebSocketClient {
|
|
|
81
136
|
client.on(WebSocketStateEvent.MESSAGE, messageHandler)
|
|
82
137
|
} else {
|
|
83
138
|
this._connect()
|
|
84
|
-
.then((
|
|
85
|
-
|
|
139
|
+
.then((client) => {
|
|
140
|
+
client.send(/** @type {any} */ (messageArray))
|
|
86
141
|
const messageHandler = (/** @type {any} */ message) => {
|
|
87
142
|
resolve(message)
|
|
88
143
|
if (this.isDisconnectedAfterMessage) {
|
|
89
144
|
this.disconnect()
|
|
90
145
|
}
|
|
91
|
-
|
|
146
|
+
client.removeListener(WebSocketStateEvent.MESSAGE, messageHandler)
|
|
92
147
|
}
|
|
93
|
-
|
|
148
|
+
client.on(WebSocketStateEvent.MESSAGE, messageHandler)
|
|
94
149
|
})
|
|
95
150
|
.catch(reject)
|
|
96
151
|
}
|
|
@@ -98,13 +153,24 @@ class WebSocketClient {
|
|
|
98
153
|
}
|
|
99
154
|
|
|
100
155
|
/**
|
|
101
|
-
* Disconnects the WebSocket by terminating the connection.
|
|
156
|
+
* Disconnects the WebSocket by terminating the connection and cleans up queues.
|
|
102
157
|
*/
|
|
103
158
|
disconnect() {
|
|
104
159
|
if (this.instance) {
|
|
105
160
|
this.instance.close()
|
|
106
161
|
delete clients[this.url]
|
|
107
162
|
}
|
|
163
|
+
|
|
164
|
+
// Clean up message queue and processing state
|
|
165
|
+
if (messageQueue[this.url]) {
|
|
166
|
+
// Reject any pending messages
|
|
167
|
+
messageQueue[this.url].forEach(({ reject }) => {
|
|
168
|
+
reject(new Error('WebSocket disconnected'))
|
|
169
|
+
})
|
|
170
|
+
delete messageQueue[this.url]
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
delete processingQueues[this.url]
|
|
108
174
|
}
|
|
109
175
|
|
|
110
176
|
/**
|
|
@@ -25,6 +25,12 @@ if (isBrowserRuntime()) {
|
|
|
25
25
|
/** @type {Record<string, WebSocket>} */
|
|
26
26
|
export const clients = {}
|
|
27
27
|
|
|
28
|
+
/** @type {Record<string, Array<{resolve: Function, reject: Function, messageArray: Int8Array}>>} */
|
|
29
|
+
export const messageQueue = {}
|
|
30
|
+
|
|
31
|
+
/** @type {Record<string, boolean>} */
|
|
32
|
+
export const processingQueues = {}
|
|
33
|
+
|
|
28
34
|
/**
|
|
29
35
|
* WebSocketClient class that handles WebSocket connection, message sending, and automatic disconnection.
|
|
30
36
|
*/
|
|
@@ -51,12 +57,59 @@ class WebSocketClientBrowser {
|
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
/**
|
|
54
|
-
* Sends messageArray through websocket connection
|
|
60
|
+
* Sends messageArray through websocket connection with guaranteed order preservation
|
|
55
61
|
* @async
|
|
56
62
|
* @param {Int8Array} messageArray
|
|
57
63
|
* @returns {Promise<Int8Array>}
|
|
58
64
|
*/
|
|
59
65
|
send(messageArray) {
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
if (!messageQueue[this.url]) {
|
|
68
|
+
messageQueue[this.url] = []
|
|
69
|
+
}
|
|
70
|
+
messageQueue[this.url].push({ resolve, reject, messageArray })
|
|
71
|
+
|
|
72
|
+
this._processMessage()
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Processes message queue sequentially to maintain order
|
|
78
|
+
* @private
|
|
79
|
+
* @async
|
|
80
|
+
*/
|
|
81
|
+
async _processMessage() {
|
|
82
|
+
if (processingQueues[this.url]) {
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
processingQueues[this.url] = true
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
while (messageQueue[this.url] && messageQueue[this.url].length > 0) {
|
|
89
|
+
const item = messageQueue[this.url].shift()
|
|
90
|
+
if (!item) break
|
|
91
|
+
|
|
92
|
+
const { resolve, reject, messageArray } = item
|
|
93
|
+
try {
|
|
94
|
+
const response = await this._sendSingle(messageArray)
|
|
95
|
+
resolve(response)
|
|
96
|
+
} catch (error) {
|
|
97
|
+
reject(error)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
} finally {
|
|
101
|
+
processingQueues[this.url] = false
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Sends a single message through websocket connection
|
|
107
|
+
* @private
|
|
108
|
+
* @async
|
|
109
|
+
* @param {Int8Array} messageArray
|
|
110
|
+
* @returns {Promise<Int8Array>}
|
|
111
|
+
*/
|
|
112
|
+
_sendSingle(messageArray) {
|
|
60
113
|
return new Promise((resolve, reject) => {
|
|
61
114
|
const client = this.instance
|
|
62
115
|
if (client && this.isConnected) {
|
|
@@ -72,7 +125,7 @@ class WebSocketClientBrowser {
|
|
|
72
125
|
}
|
|
73
126
|
|
|
74
127
|
/**
|
|
75
|
-
* Disconnects the WebSocket by terminating the connection.
|
|
128
|
+
* Disconnects the WebSocket by terminating the connection and cleans up queues.
|
|
76
129
|
*/
|
|
77
130
|
disconnect() {
|
|
78
131
|
const client = this.instance
|
|
@@ -80,6 +133,14 @@ class WebSocketClientBrowser {
|
|
|
80
133
|
client.close()
|
|
81
134
|
delete clients[this.url]
|
|
82
135
|
}
|
|
136
|
+
|
|
137
|
+
if (messageQueue[this.url]) {
|
|
138
|
+
messageQueue[this.url].forEach(({ reject }) => {
|
|
139
|
+
reject(new Error('WebSocket disconnected'))
|
|
140
|
+
})
|
|
141
|
+
delete messageQueue[this.url]
|
|
142
|
+
}
|
|
143
|
+
delete processingQueues[this.url]
|
|
83
144
|
}
|
|
84
145
|
|
|
85
146
|
/**
|
|
@@ -122,7 +122,9 @@ class InvocationContext {
|
|
|
122
122
|
throw new Error('currentCommand is undefined in Invocation Context execute method')
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
this.#interpreter
|
|
125
|
+
if (!this.#interpreter) {
|
|
126
|
+
this.#interpreter = new Interpreter()
|
|
127
|
+
}
|
|
126
128
|
//@ts-expect-error
|
|
127
129
|
this.#responseCommand = this.#interpreter.execute(this.#currentCommand, this.#connectionData)
|
|
128
130
|
|
|
@@ -137,6 +139,7 @@ class InvocationContext {
|
|
|
137
139
|
this.#isExecuted = true
|
|
138
140
|
return this
|
|
139
141
|
}
|
|
142
|
+
|
|
140
143
|
return new InvocationContext(this.#runtimeName, this.#connectionData, this.#responseCommand, true)
|
|
141
144
|
}
|
|
142
145
|
|
|
@@ -531,7 +534,9 @@ class InvocationWsContext extends InvocationContext {
|
|
|
531
534
|
throw new Error('currentCommand is undefined in Invocation Context execute method')
|
|
532
535
|
}
|
|
533
536
|
|
|
534
|
-
this.#interpreter
|
|
537
|
+
if (!this.#interpreter) {
|
|
538
|
+
this.#interpreter = new Interpreter()
|
|
539
|
+
}
|
|
535
540
|
this.#responseCommand = await this.#interpreter.executeAsync(
|
|
536
541
|
this.#currentCommand,
|
|
537
542
|
this.#connectionData
|
|
@@ -548,6 +553,7 @@ class InvocationWsContext extends InvocationContext {
|
|
|
548
553
|
this.#isExecuted = true
|
|
549
554
|
return this
|
|
550
555
|
}
|
|
556
|
+
|
|
551
557
|
return new InvocationWsContext(this.#runtimeName, this.#connectionData, this.#responseCommand, true)
|
|
552
558
|
}
|
|
553
559
|
}
|