@hla4ts/session 0.1.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/README.md +377 -0
- package/package.json +46 -0
- package/src/errors.ts +98 -0
- package/src/index.ts +147 -0
- package/src/messages.ts +329 -0
- package/src/sequence-number.ts +200 -0
- package/src/session.ts +976 -0
- package/src/timeout-timer.ts +204 -0
- package/src/types.ts +235 -0
package/README.md
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
# @hla4ts/session
|
|
2
|
+
|
|
3
|
+
**HLA 4 Federate Protocol Session Layer**
|
|
4
|
+
|
|
5
|
+
This package provides session management for the HLA 4 Federate Protocol, handling connection establishment, state machine, heartbeats, request/response correlation, and session resumption.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
The session layer sits between the transport layer and the HLA API layer, providing:
|
|
10
|
+
|
|
11
|
+
- **Session lifecycle management** - NEW → STARTING → RUNNING ⇄ DROPPED → TERMINATED
|
|
12
|
+
- **Request/response correlation** - Matches HLA call responses to requests
|
|
13
|
+
- **Heartbeat support** - Keep-alive mechanism for connection health
|
|
14
|
+
- **Session resumption** - Reconnect and resume after network drops
|
|
15
|
+
- **Timeout management** - Connection and response timeouts
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
bun add @hla4ts/session
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { Session, SessionState } from '@hla4ts/session';
|
|
27
|
+
import { TlsTransport } from '@hla4ts/transport';
|
|
28
|
+
|
|
29
|
+
// Create transport and session
|
|
30
|
+
const transport = new TlsTransport({
|
|
31
|
+
host: 'rti.example.com',
|
|
32
|
+
port: 15165,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const session = new Session(transport, {
|
|
36
|
+
connectionTimeout: 30000,
|
|
37
|
+
responseTimeout: 180000,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Listen for state changes
|
|
41
|
+
session.addStateListener({
|
|
42
|
+
onStateTransition: (oldState, newState, reason) => {
|
|
43
|
+
console.log(`Session: ${oldState} -> ${newState} (${reason})`);
|
|
44
|
+
|
|
45
|
+
// Handle connection drops
|
|
46
|
+
if (newState === SessionState.DROPPED) {
|
|
47
|
+
session.resume().catch((err) => {
|
|
48
|
+
console.error('Resume failed:', err);
|
|
49
|
+
session.terminate();
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Start the session
|
|
56
|
+
await session.start({
|
|
57
|
+
onHlaCallbackRequest: (seqNum, callbackData) => {
|
|
58
|
+
// Process the callback (decode protobuf, handle it, encode response)
|
|
59
|
+
const response = processCallback(callbackData);
|
|
60
|
+
session.sendHlaCallbackResponse(seqNum, response);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Send HLA calls
|
|
65
|
+
const response = await session.sendHlaCallRequest(encodedCall);
|
|
66
|
+
|
|
67
|
+
// When done
|
|
68
|
+
await session.terminate();
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Sequence Diagram
|
|
72
|
+
|
|
73
|
+
```mermaid
|
|
74
|
+
sequenceDiagram
|
|
75
|
+
participant App as Federate Code
|
|
76
|
+
participant Session
|
|
77
|
+
participant Transport
|
|
78
|
+
participant RTI
|
|
79
|
+
App->>Session: start(callbackListener)
|
|
80
|
+
Session->>Transport: CTRL_NEW_SESSION
|
|
81
|
+
Transport-->>RTI: framed message
|
|
82
|
+
RTI-->>Transport: CTRL_NEW_SESSION_STATUS
|
|
83
|
+
Transport-->>Session: onMessage
|
|
84
|
+
App->>Session: sendHlaCallRequest(bytes)
|
|
85
|
+
Session->>Transport: HLA_CALL_REQUEST
|
|
86
|
+
RTI-->>Transport: HLA_CALL_RESPONSE
|
|
87
|
+
Transport-->>Session: onMessage
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Session States
|
|
91
|
+
|
|
92
|
+
The session follows a well-defined state machine:
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
┌───────┐
|
|
96
|
+
│ NEW │────────────────────┐
|
|
97
|
+
└───┬───┘ │
|
|
98
|
+
│ start() │
|
|
99
|
+
▼ │
|
|
100
|
+
┌──────────┐ │
|
|
101
|
+
│ STARTING │─────────────────┤
|
|
102
|
+
└────┬─────┘ │
|
|
103
|
+
│ success │ failure
|
|
104
|
+
▼ │
|
|
105
|
+
┌─────────┐ │
|
|
106
|
+
│ RUNNING │◄─────────────┐ │
|
|
107
|
+
└────┬────┘ │ │
|
|
108
|
+
│ │ │
|
|
109
|
+
│ connection lost │ resume()
|
|
110
|
+
▼ │ │
|
|
111
|
+
┌─────────┐ ┌────┴────┐
|
|
112
|
+
│ DROPPED │────────►│ RESUMING│
|
|
113
|
+
└────┬────┘ └────┬────┘
|
|
114
|
+
│ │
|
|
115
|
+
│ terminate() │ failure
|
|
116
|
+
▼ │
|
|
117
|
+
┌─────────────┐ │
|
|
118
|
+
│ TERMINATING │ │
|
|
119
|
+
└──────┬──────┘ │
|
|
120
|
+
│ │
|
|
121
|
+
▼ ▼
|
|
122
|
+
┌────────────┐◄──────────┘
|
|
123
|
+
│ TERMINATED │
|
|
124
|
+
└────────────┘
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### State Descriptions
|
|
128
|
+
|
|
129
|
+
| State | Description |
|
|
130
|
+
|-------|-------------|
|
|
131
|
+
| `NEW` | Session created but not started |
|
|
132
|
+
| `STARTING` | Connecting to RTI, establishing session |
|
|
133
|
+
| `RUNNING` | Session active, can send/receive HLA messages |
|
|
134
|
+
| `DROPPED` | Connection lost, can attempt resume |
|
|
135
|
+
| `RESUMING` | Attempting to resume dropped session |
|
|
136
|
+
| `TERMINATING` | Graceful shutdown in progress |
|
|
137
|
+
| `TERMINATED` | Session ended, cannot be reused |
|
|
138
|
+
|
|
139
|
+
## API Reference
|
|
140
|
+
|
|
141
|
+
### Session Class
|
|
142
|
+
|
|
143
|
+
#### Constructor
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
new Session(transport: Transport, options?: SessionOptions)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Options:**
|
|
150
|
+
|
|
151
|
+
| Option | Type | Default | Description |
|
|
152
|
+
|--------|------|---------|-------------|
|
|
153
|
+
| `connectionTimeout` | `number` | `30000` | Timeout for initial connection (ms) |
|
|
154
|
+
| `responseTimeout` | `number` | `180000` | Timeout for server responses (ms) |
|
|
155
|
+
| `maxRetryAttempts` | `number` | `3` | Max connection retry attempts |
|
|
156
|
+
| `messageQueueSize` | `number` | `1000` | Size of outgoing message queue |
|
|
157
|
+
| `rateLimitEnabled` | `boolean` | `false` | Enable rate limiting |
|
|
158
|
+
| `heartbeatInterval` | `number` | `10000` | Heartbeat check interval (ms) |
|
|
159
|
+
|
|
160
|
+
#### Properties
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
session.id: bigint // Session ID (0 if not established)
|
|
164
|
+
session.state: SessionState // Current state
|
|
165
|
+
session.isOperational: boolean // True if can send/receive messages
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
#### Methods
|
|
169
|
+
|
|
170
|
+
##### `start(callbackListener)`
|
|
171
|
+
|
|
172
|
+
Start the session and connect to the RTI.
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
await session.start({
|
|
176
|
+
onHlaCallbackRequest: (seqNum: number, callback: Uint8Array) => {
|
|
177
|
+
// Handle callback
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
##### `resume()`
|
|
183
|
+
|
|
184
|
+
Resume a dropped session.
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
const success = await session.resume();
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
##### `sendHeartbeat()`
|
|
191
|
+
|
|
192
|
+
Send a heartbeat message (useful for keeping connection alive).
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
await session.sendHeartbeat();
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
##### `sendHlaCallRequest(encodedHlaCall)`
|
|
199
|
+
|
|
200
|
+
Send an HLA call and wait for the response.
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
const response = await session.sendHlaCallRequest(encodedCall);
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
##### `sendHlaCallbackResponse(responseToSequenceNumber, encodedResponse)`
|
|
207
|
+
|
|
208
|
+
Send a response to an HLA callback.
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
await session.sendHlaCallbackResponse(seqNum, encodedResponse);
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
##### `terminate(timeoutMs?)`
|
|
215
|
+
|
|
216
|
+
Gracefully terminate the session.
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
await session.terminate();
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
##### `addStateListener(listener)`
|
|
223
|
+
|
|
224
|
+
Register a state change listener.
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
session.addStateListener({
|
|
228
|
+
onStateTransition: (oldState, newState, reason) => {
|
|
229
|
+
console.log(`${oldState} -> ${newState}: ${reason}`);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
##### `setMessageSentListener(listener)`
|
|
235
|
+
|
|
236
|
+
Set a listener for outgoing messages (useful for heartbeat timing).
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
session.setMessageSentListener({
|
|
240
|
+
onMessageSent: () => {
|
|
241
|
+
// Reset heartbeat timer
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Error Handling
|
|
247
|
+
|
|
248
|
+
The session layer throws specific errors:
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
import {
|
|
252
|
+
SessionLostError,
|
|
253
|
+
SessionAlreadyTerminatedError,
|
|
254
|
+
SessionIllegalStateError,
|
|
255
|
+
BadMessageError,
|
|
256
|
+
ConnectionTimeoutError,
|
|
257
|
+
ResponseTimeoutError,
|
|
258
|
+
} from '@hla4ts/session';
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
await session.start(listener);
|
|
262
|
+
} catch (err) {
|
|
263
|
+
if (err instanceof ConnectionTimeoutError) {
|
|
264
|
+
console.error('Could not connect to RTI');
|
|
265
|
+
} else if (err instanceof SessionLostError) {
|
|
266
|
+
console.error('Session lost:', err.message);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Sequence Numbers
|
|
272
|
+
|
|
273
|
+
The package exports utilities for working with Federate Protocol sequence numbers:
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
import {
|
|
277
|
+
SequenceNumber,
|
|
278
|
+
AtomicSequenceNumber,
|
|
279
|
+
isValidSequenceNumber,
|
|
280
|
+
nextSequenceNumber,
|
|
281
|
+
NO_SEQUENCE_NUMBER,
|
|
282
|
+
INITIAL_SEQUENCE_NUMBER,
|
|
283
|
+
MAX_SEQUENCE_NUMBER,
|
|
284
|
+
} from '@hla4ts/session';
|
|
285
|
+
|
|
286
|
+
// Mutable sequence number
|
|
287
|
+
const seq = new SequenceNumber(0);
|
|
288
|
+
seq.increment(); // Returns 1
|
|
289
|
+
seq.getAndIncrement(); // Returns 1, then increments to 2
|
|
290
|
+
|
|
291
|
+
// For concurrent access
|
|
292
|
+
const atomic = new AtomicSequenceNumber(0);
|
|
293
|
+
atomic.compareAndSet(0, 1); // Returns true if successful
|
|
294
|
+
|
|
295
|
+
// Utilities
|
|
296
|
+
isValidSequenceNumber(100); // true
|
|
297
|
+
isValidSequenceNumber(-1); // false
|
|
298
|
+
nextSequenceNumber(MAX_SEQUENCE_NUMBER); // Returns 0 (wrap-around)
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Timers
|
|
302
|
+
|
|
303
|
+
The package includes timer utilities for session management:
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
import { TimeoutTimer, OneShotTimer } from '@hla4ts/session';
|
|
307
|
+
|
|
308
|
+
// Periodic timeout timer
|
|
309
|
+
const timer = TimeoutTimer.createLazy(30000); // 30 second timeout
|
|
310
|
+
timer.start(() => {
|
|
311
|
+
console.log('Timeout!');
|
|
312
|
+
});
|
|
313
|
+
timer.extend(); // Reset the timeout
|
|
314
|
+
timer.pause(); // Stop checking
|
|
315
|
+
timer.resume(); // Resume checking
|
|
316
|
+
timer.cancel(); // Permanently cancel
|
|
317
|
+
|
|
318
|
+
// One-shot timer
|
|
319
|
+
const oneShot = new OneShotTimer();
|
|
320
|
+
oneShot.schedule(() => {
|
|
321
|
+
console.log('Fired!');
|
|
322
|
+
}, 5000);
|
|
323
|
+
oneShot.clear(); // Cancel before it fires
|
|
324
|
+
oneShot.cancel(); // Permanently cancel
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## Message Encoding (Advanced)
|
|
328
|
+
|
|
329
|
+
For advanced use cases, you can encode/decode session control messages directly:
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
import {
|
|
333
|
+
createNewSessionMessage,
|
|
334
|
+
createHeartbeatMessage,
|
|
335
|
+
createHlaCallRequestMessage,
|
|
336
|
+
decodeHlaCallResponse,
|
|
337
|
+
decodeHlaCallbackRequest,
|
|
338
|
+
} from '@hla4ts/session';
|
|
339
|
+
|
|
340
|
+
// Create a new session message
|
|
341
|
+
const newSessionMsg = createNewSessionMessage();
|
|
342
|
+
|
|
343
|
+
// Create an HLA call request
|
|
344
|
+
const callMsg = createHlaCallRequestMessage(
|
|
345
|
+
sequenceNumber,
|
|
346
|
+
sessionId,
|
|
347
|
+
lastReceivedSequenceNumber,
|
|
348
|
+
protobufPayload
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
// Decode an HLA call response
|
|
352
|
+
const response = decodeHlaCallResponse(payload);
|
|
353
|
+
console.log(response.responseToSequenceNumber);
|
|
354
|
+
console.log(response.hlaServiceReturnValueOrException);
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## Testing
|
|
358
|
+
|
|
359
|
+
```bash
|
|
360
|
+
cd packages/session
|
|
361
|
+
bun test
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## Related Packages
|
|
365
|
+
|
|
366
|
+
- [`@hla4ts/transport`](../transport) - Transport layer (TCP/TLS)
|
|
367
|
+
- [`@hla4ts/proto`](../proto) - Protocol buffer types
|
|
368
|
+
- [`@hla4ts/hla-api`](../hla-api) - High-level HLA API facade
|
|
369
|
+
|
|
370
|
+
## References
|
|
371
|
+
|
|
372
|
+
- [IEEE 1516-2025](https://standards.ieee.org/standard/1516-2025.html) - HLA 4 Standard
|
|
373
|
+
- [Pitch FedProClient](https://github.com/Pitch-Technologies/FedProClient) - Reference implementation
|
|
374
|
+
|
|
375
|
+
## License
|
|
376
|
+
|
|
377
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hla4ts/session",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "HLA 4 Federate Protocol Session Layer - State machine, heartbeats, reconnection",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"types": "src/index.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"README.md",
|
|
10
|
+
"src"
|
|
11
|
+
],
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": "./src/index.ts",
|
|
15
|
+
"types": "./src/index.ts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"test": "bun test",
|
|
24
|
+
"clean": "rm -rf dist"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@hla4ts/transport": "^0.1.0",
|
|
28
|
+
"@hla4ts/proto": "^0.1.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/bun": "latest",
|
|
32
|
+
"typescript": "^5.3.3"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"typescript": "^5.0.0"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"hla",
|
|
39
|
+
"hla4",
|
|
40
|
+
"ieee-1516",
|
|
41
|
+
"federate-protocol",
|
|
42
|
+
"session",
|
|
43
|
+
"simulation"
|
|
44
|
+
],
|
|
45
|
+
"license": "MIT"
|
|
46
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Exceptions
|
|
3
|
+
*
|
|
4
|
+
* Custom error types for session-related failures.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { SessionState } from "./types.ts";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Base class for session-related errors
|
|
11
|
+
*/
|
|
12
|
+
export class SessionError extends Error {
|
|
13
|
+
constructor(message: string) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = "SessionError";
|
|
16
|
+
// Maintains proper stack trace for where the error was thrown
|
|
17
|
+
if (Error.captureStackTrace) {
|
|
18
|
+
Error.captureStackTrace(this, this.constructor);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Thrown when the session is lost unexpectedly.
|
|
25
|
+
* This can happen due to:
|
|
26
|
+
* - Network disconnection
|
|
27
|
+
* - Server timeout
|
|
28
|
+
* - Protocol errors
|
|
29
|
+
*/
|
|
30
|
+
export class SessionLostError extends SessionError {
|
|
31
|
+
constructor(message: string, public readonly cause?: Error) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.name = "SessionLostError";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Thrown when an operation is attempted on a session that is already terminated.
|
|
39
|
+
*/
|
|
40
|
+
export class SessionAlreadyTerminatedError extends SessionError {
|
|
41
|
+
constructor(message: string = "Session has already terminated") {
|
|
42
|
+
super(message);
|
|
43
|
+
this.name = "SessionAlreadyTerminatedError";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Thrown when an operation is attempted in an invalid session state.
|
|
49
|
+
*/
|
|
50
|
+
export class SessionIllegalStateError extends SessionError {
|
|
51
|
+
constructor(
|
|
52
|
+
public readonly currentState: SessionState,
|
|
53
|
+
public readonly operation: string
|
|
54
|
+
) {
|
|
55
|
+
super(`Cannot ${operation} in state ${currentState}`);
|
|
56
|
+
this.name = "SessionIllegalStateError";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Thrown when a bad message is received from the server.
|
|
62
|
+
*/
|
|
63
|
+
export class BadMessageError extends SessionError {
|
|
64
|
+
constructor(message: string) {
|
|
65
|
+
super(message);
|
|
66
|
+
this.name = "BadMessageError";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Thrown when the message queue is full and cannot accept more messages.
|
|
72
|
+
*/
|
|
73
|
+
export class MessageQueueFullError extends SessionError {
|
|
74
|
+
constructor() {
|
|
75
|
+
super("Message queue is full");
|
|
76
|
+
this.name = "MessageQueueFullError";
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Thrown when a connection attempt times out.
|
|
82
|
+
*/
|
|
83
|
+
export class ConnectionTimeoutError extends SessionError {
|
|
84
|
+
constructor(timeoutMs: number) {
|
|
85
|
+
super(`Connection timed out after ${timeoutMs}ms`);
|
|
86
|
+
this.name = "ConnectionTimeoutError";
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Thrown when a response is not received within the expected timeout.
|
|
92
|
+
*/
|
|
93
|
+
export class ResponseTimeoutError extends SessionError {
|
|
94
|
+
constructor(timeoutMs: number) {
|
|
95
|
+
super(`Response timed out after ${timeoutMs}ms`);
|
|
96
|
+
this.name = "ResponseTimeoutError";
|
|
97
|
+
}
|
|
98
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hla4ts/session - HLA 4 Federate Protocol Session Layer
|
|
3
|
+
*
|
|
4
|
+
* This package provides session management for the HLA 4 Federate Protocol,
|
|
5
|
+
* including connection establishment, state machine, heartbeats, and reconnection.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { Session, SessionState } from '@hla4ts/session';
|
|
10
|
+
* import { TlsTransport } from '@hla4ts/transport';
|
|
11
|
+
*
|
|
12
|
+
* // Create transport and session
|
|
13
|
+
* const transport = new TlsTransport({
|
|
14
|
+
* host: 'rti.example.com',
|
|
15
|
+
* port: 15165,
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* const session = new Session(transport, {
|
|
19
|
+
* connectionTimeout: 30000,
|
|
20
|
+
* responseTimeout: 180000,
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* // Listen for state changes
|
|
24
|
+
* session.addStateListener({
|
|
25
|
+
* onStateTransition: (oldState, newState, reason) => {
|
|
26
|
+
* console.log(`Session: ${oldState} -> ${newState} (${reason})`);
|
|
27
|
+
* if (newState === SessionState.DROPPED) {
|
|
28
|
+
* // Attempt to resume
|
|
29
|
+
* session.resume().catch(console.error);
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* // Start the session
|
|
35
|
+
* await session.start({
|
|
36
|
+
* onHlaCallbackRequest: (seqNum, callbackData) => {
|
|
37
|
+
* // Handle RTI callback
|
|
38
|
+
* const response = processCallback(callbackData);
|
|
39
|
+
* session.sendHlaCallbackResponse(seqNum, response);
|
|
40
|
+
* }
|
|
41
|
+
* });
|
|
42
|
+
*
|
|
43
|
+
* // Send HLA calls
|
|
44
|
+
* const response = await session.sendHlaCallRequest(encodedCall);
|
|
45
|
+
*
|
|
46
|
+
* // Clean up
|
|
47
|
+
* await session.terminate();
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* @packageDocumentation
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
// =============================================================================
|
|
54
|
+
// Session
|
|
55
|
+
// =============================================================================
|
|
56
|
+
export { Session } from "./session.ts";
|
|
57
|
+
|
|
58
|
+
// =============================================================================
|
|
59
|
+
// Types
|
|
60
|
+
// =============================================================================
|
|
61
|
+
export {
|
|
62
|
+
// State machine
|
|
63
|
+
SessionState,
|
|
64
|
+
VALID_TRANSITIONS,
|
|
65
|
+
isValidTransition,
|
|
66
|
+
isOperationalState,
|
|
67
|
+
|
|
68
|
+
// Options
|
|
69
|
+
type SessionOptions,
|
|
70
|
+
DEFAULT_SESSION_OPTIONS,
|
|
71
|
+
|
|
72
|
+
// Callbacks
|
|
73
|
+
type HlaCallbackRequestListener,
|
|
74
|
+
type SessionStateListener,
|
|
75
|
+
type MessageSentListener,
|
|
76
|
+
|
|
77
|
+
// Stats
|
|
78
|
+
type SessionStats,
|
|
79
|
+
} from "./types.ts";
|
|
80
|
+
|
|
81
|
+
// =============================================================================
|
|
82
|
+
// Errors
|
|
83
|
+
// =============================================================================
|
|
84
|
+
export {
|
|
85
|
+
SessionError,
|
|
86
|
+
SessionLostError,
|
|
87
|
+
SessionAlreadyTerminatedError,
|
|
88
|
+
SessionIllegalStateError,
|
|
89
|
+
BadMessageError,
|
|
90
|
+
MessageQueueFullError,
|
|
91
|
+
ConnectionTimeoutError,
|
|
92
|
+
ResponseTimeoutError,
|
|
93
|
+
} from "./errors.ts";
|
|
94
|
+
|
|
95
|
+
// =============================================================================
|
|
96
|
+
// Sequence Numbers
|
|
97
|
+
// =============================================================================
|
|
98
|
+
export {
|
|
99
|
+
// Constants
|
|
100
|
+
NO_SEQUENCE_NUMBER,
|
|
101
|
+
INITIAL_SEQUENCE_NUMBER,
|
|
102
|
+
MAX_SEQUENCE_NUMBER,
|
|
103
|
+
|
|
104
|
+
// Functions
|
|
105
|
+
isValidSequenceNumber,
|
|
106
|
+
nextSequenceNumber,
|
|
107
|
+
addSequenceNumbers,
|
|
108
|
+
isInInterval,
|
|
109
|
+
|
|
110
|
+
// Classes
|
|
111
|
+
SequenceNumber,
|
|
112
|
+
AtomicSequenceNumber,
|
|
113
|
+
} from "./sequence-number.ts";
|
|
114
|
+
|
|
115
|
+
// =============================================================================
|
|
116
|
+
// Timers
|
|
117
|
+
// =============================================================================
|
|
118
|
+
export { TimeoutTimer, OneShotTimer } from "./timeout-timer.ts";
|
|
119
|
+
|
|
120
|
+
// =============================================================================
|
|
121
|
+
// Messages (for advanced use)
|
|
122
|
+
// =============================================================================
|
|
123
|
+
export {
|
|
124
|
+
// Session control messages
|
|
125
|
+
createNewSessionMessage,
|
|
126
|
+
decodeNewSessionStatus,
|
|
127
|
+
createResumeRequestMessage,
|
|
128
|
+
decodeResumeStatus,
|
|
129
|
+
ResumeStatusCode,
|
|
130
|
+
createHeartbeatMessage,
|
|
131
|
+
decodeHeartbeatResponse,
|
|
132
|
+
createTerminateSessionMessage,
|
|
133
|
+
|
|
134
|
+
// HLA messages
|
|
135
|
+
createHlaCallRequestMessage,
|
|
136
|
+
decodeHlaCallResponse,
|
|
137
|
+
decodeHlaCallbackRequest,
|
|
138
|
+
createHlaCallbackResponseMessage,
|
|
139
|
+
|
|
140
|
+
// Types
|
|
141
|
+
type ResumeStatusResult,
|
|
142
|
+
type HlaCallResponseResult,
|
|
143
|
+
type HlaCallbackRequestResult,
|
|
144
|
+
|
|
145
|
+
// Utilities
|
|
146
|
+
describeHeader,
|
|
147
|
+
} from "./messages.ts";
|