@shipload/sdk 1.0.0-next.0 → 1.0.0-next.2
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/lib/shipload.d.ts +19 -1
- package/lib/shipload.js +80 -2
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +80 -2
- package/lib/shipload.m.js.map +1 -1
- package/package.json +7 -2
- package/src/subscriptions/connection.ts +50 -2
- package/src/subscriptions/manager.ts +44 -1
package/lib/shipload.d.ts
CHANGED
|
@@ -2417,6 +2417,9 @@ type SubscriptionEntityType = 'ship' | 'warehouse' | 'container';
|
|
|
2417
2417
|
type EntityInstance = Ship | Warehouse | Container;
|
|
2418
2418
|
interface SubscriptionsOptions {
|
|
2419
2419
|
url: string;
|
|
2420
|
+
minReconnectDelay?: number;
|
|
2421
|
+
pingIntervalMs?: number;
|
|
2422
|
+
pongTimeoutMs?: number;
|
|
2420
2423
|
}
|
|
2421
2424
|
interface BoundsSubscriptionHandle {
|
|
2422
2425
|
readonly subId: string;
|
|
@@ -2436,6 +2439,7 @@ declare class SubscriptionsManager {
|
|
|
2436
2439
|
private readonly entitySubs;
|
|
2437
2440
|
private readonly boundsSubs;
|
|
2438
2441
|
private subCounter;
|
|
2442
|
+
private hasConnected;
|
|
2439
2443
|
constructor(opts: SubscriptionsOptions);
|
|
2440
2444
|
close(): void;
|
|
2441
2445
|
private generateSubID;
|
|
@@ -2451,6 +2455,7 @@ declare class SubscriptionsManager {
|
|
|
2451
2455
|
}): BoundsSubscriptionHandle;
|
|
2452
2456
|
private unsubscribeBounds;
|
|
2453
2457
|
private updateBounds;
|
|
2458
|
+
private onStateChange;
|
|
2454
2459
|
private onMessage;
|
|
2455
2460
|
private parseEntity;
|
|
2456
2461
|
private handleSnapshot;
|
|
@@ -3098,6 +3103,9 @@ interface WebSocketConnectionOptions {
|
|
|
3098
3103
|
url: string;
|
|
3099
3104
|
onMessage: (message: ServerMessage) => void;
|
|
3100
3105
|
onStateChange?: (state: ConnectionState) => void;
|
|
3106
|
+
minReconnectDelay?: number;
|
|
3107
|
+
pingIntervalMs?: number;
|
|
3108
|
+
pongTimeoutMs?: number;
|
|
3101
3109
|
}
|
|
3102
3110
|
declare class WebSocketConnection {
|
|
3103
3111
|
private ws;
|
|
@@ -3109,15 +3117,25 @@ declare class WebSocketConnection {
|
|
|
3109
3117
|
private _state;
|
|
3110
3118
|
private shouldReconnect;
|
|
3111
3119
|
private sendQueue;
|
|
3112
|
-
private
|
|
3120
|
+
private minReconnectDelay;
|
|
3121
|
+
private pingIntervalMs;
|
|
3122
|
+
private pongTimeoutMs;
|
|
3123
|
+
private pingTimer;
|
|
3124
|
+
private staleTimer;
|
|
3125
|
+
private static readonly DEFAULT_MIN_RECONNECT_DELAY;
|
|
3113
3126
|
private static readonly MAX_RECONNECT_DELAY;
|
|
3114
3127
|
private static readonly RECONNECT_MULTIPLIER;
|
|
3128
|
+
private static readonly DEFAULT_PING_INTERVAL_MS;
|
|
3129
|
+
private static readonly DEFAULT_PONG_TIMEOUT_MS;
|
|
3115
3130
|
constructor(options: WebSocketConnectionOptions);
|
|
3116
3131
|
get state(): ConnectionState;
|
|
3117
3132
|
private setState;
|
|
3118
3133
|
connect(): void;
|
|
3119
3134
|
private scheduleReconnect;
|
|
3120
3135
|
disconnect(): void;
|
|
3136
|
+
private startHeartbeat;
|
|
3137
|
+
private stopHeartbeat;
|
|
3138
|
+
private resetStaleTimer;
|
|
3121
3139
|
close(): void;
|
|
3122
3140
|
send(message: ClientMessage): void;
|
|
3123
3141
|
get isConnected(): boolean;
|
package/lib/shipload.js
CHANGED
|
@@ -9103,9 +9103,15 @@ class WebSocketConnection {
|
|
|
9103
9103
|
this._state = 'disconnected';
|
|
9104
9104
|
this.shouldReconnect = true;
|
|
9105
9105
|
this.sendQueue = [];
|
|
9106
|
+
this.pingTimer = null;
|
|
9107
|
+
this.staleTimer = null;
|
|
9106
9108
|
this.url = options.url;
|
|
9107
9109
|
this.onMessage = options.onMessage;
|
|
9108
9110
|
this.onStateChange = options.onStateChange;
|
|
9111
|
+
this.minReconnectDelay =
|
|
9112
|
+
options.minReconnectDelay ?? WebSocketConnection.DEFAULT_MIN_RECONNECT_DELAY;
|
|
9113
|
+
this.pingIntervalMs = options.pingIntervalMs ?? WebSocketConnection.DEFAULT_PING_INTERVAL_MS;
|
|
9114
|
+
this.pongTimeoutMs = options.pongTimeoutMs ?? WebSocketConnection.DEFAULT_PONG_TIMEOUT_MS;
|
|
9109
9115
|
}
|
|
9110
9116
|
get state() {
|
|
9111
9117
|
return this._state;
|
|
@@ -9134,8 +9140,10 @@ class WebSocketConnection {
|
|
|
9134
9140
|
this.ws.readyState === WebSocket.OPEN) {
|
|
9135
9141
|
this.ws.send(this.sendQueue.shift());
|
|
9136
9142
|
}
|
|
9143
|
+
this.startHeartbeat();
|
|
9137
9144
|
};
|
|
9138
9145
|
this.ws.onmessage = (event) => {
|
|
9146
|
+
this.resetStaleTimer();
|
|
9139
9147
|
try {
|
|
9140
9148
|
const message = JSON.parse(event.data);
|
|
9141
9149
|
this.onMessage(message);
|
|
@@ -9145,6 +9153,7 @@ class WebSocketConnection {
|
|
|
9145
9153
|
}
|
|
9146
9154
|
};
|
|
9147
9155
|
this.ws.onclose = () => {
|
|
9156
|
+
this.stopHeartbeat();
|
|
9148
9157
|
this.ws = null;
|
|
9149
9158
|
this.sendQueue.length = 0;
|
|
9150
9159
|
if (this.shouldReconnect) {
|
|
@@ -9169,7 +9178,7 @@ class WebSocketConnection {
|
|
|
9169
9178
|
if (this.reconnectTimeout) {
|
|
9170
9179
|
return;
|
|
9171
9180
|
}
|
|
9172
|
-
const delay = Math.min(
|
|
9181
|
+
const delay = Math.min(this.minReconnectDelay *
|
|
9173
9182
|
WebSocketConnection.RECONNECT_MULTIPLIER ** this.reconnectAttempts, WebSocketConnection.MAX_RECONNECT_DELAY);
|
|
9174
9183
|
debug(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts + 1})`);
|
|
9175
9184
|
this.reconnectTimeout = setTimeout(() => {
|
|
@@ -9184,6 +9193,7 @@ class WebSocketConnection {
|
|
|
9184
9193
|
clearTimeout(this.reconnectTimeout);
|
|
9185
9194
|
this.reconnectTimeout = null;
|
|
9186
9195
|
}
|
|
9196
|
+
this.stopHeartbeat();
|
|
9187
9197
|
if (this.ws) {
|
|
9188
9198
|
this.ws.close();
|
|
9189
9199
|
this.ws = null;
|
|
@@ -9191,6 +9201,34 @@ class WebSocketConnection {
|
|
|
9191
9201
|
this.sendQueue.length = 0;
|
|
9192
9202
|
this.setState('disconnected');
|
|
9193
9203
|
}
|
|
9204
|
+
startHeartbeat() {
|
|
9205
|
+
this.stopHeartbeat();
|
|
9206
|
+
this.resetStaleTimer();
|
|
9207
|
+
this.pingTimer = setInterval(() => {
|
|
9208
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
9209
|
+
this.ws.send(JSON.stringify({ type: 'ping' }));
|
|
9210
|
+
}
|
|
9211
|
+
}, this.pingIntervalMs);
|
|
9212
|
+
}
|
|
9213
|
+
stopHeartbeat() {
|
|
9214
|
+
if (this.pingTimer) {
|
|
9215
|
+
clearInterval(this.pingTimer);
|
|
9216
|
+
this.pingTimer = null;
|
|
9217
|
+
}
|
|
9218
|
+
if (this.staleTimer) {
|
|
9219
|
+
clearTimeout(this.staleTimer);
|
|
9220
|
+
this.staleTimer = null;
|
|
9221
|
+
}
|
|
9222
|
+
}
|
|
9223
|
+
resetStaleTimer() {
|
|
9224
|
+
if (this.staleTimer)
|
|
9225
|
+
clearTimeout(this.staleTimer);
|
|
9226
|
+
this.staleTimer = setTimeout(() => {
|
|
9227
|
+
debug('No frames within ping interval + pong timeout — forcing reconnect');
|
|
9228
|
+
if (this.ws)
|
|
9229
|
+
this.ws.close();
|
|
9230
|
+
}, this.pingIntervalMs + this.pongTimeoutMs);
|
|
9231
|
+
}
|
|
9194
9232
|
close() {
|
|
9195
9233
|
this.disconnect();
|
|
9196
9234
|
}
|
|
@@ -9206,9 +9244,11 @@ class WebSocketConnection {
|
|
|
9206
9244
|
return this._state === 'connected';
|
|
9207
9245
|
}
|
|
9208
9246
|
}
|
|
9209
|
-
WebSocketConnection.
|
|
9247
|
+
WebSocketConnection.DEFAULT_MIN_RECONNECT_DELAY = 1000;
|
|
9210
9248
|
WebSocketConnection.MAX_RECONNECT_DELAY = 30000;
|
|
9211
9249
|
WebSocketConnection.RECONNECT_MULTIPLIER = 2;
|
|
9250
|
+
WebSocketConnection.DEFAULT_PING_INTERVAL_MS = 25000;
|
|
9251
|
+
WebSocketConnection.DEFAULT_PONG_TIMEOUT_MS = 10000;
|
|
9212
9252
|
|
|
9213
9253
|
function mapEntity(ei) {
|
|
9214
9254
|
if (ei.type.equals('ship'))
|
|
@@ -9237,9 +9277,14 @@ class SubscriptionsManager {
|
|
|
9237
9277
|
this.entitySubs = new Map();
|
|
9238
9278
|
this.boundsSubs = new Map();
|
|
9239
9279
|
this.subCounter = 0;
|
|
9280
|
+
this.hasConnected = false;
|
|
9240
9281
|
this.conn = new WebSocketConnection({
|
|
9241
9282
|
url: opts.url,
|
|
9242
9283
|
onMessage: (m) => this.onMessage(m),
|
|
9284
|
+
onStateChange: (s) => this.onStateChange(s),
|
|
9285
|
+
minReconnectDelay: opts.minReconnectDelay,
|
|
9286
|
+
pingIntervalMs: opts.pingIntervalMs,
|
|
9287
|
+
pongTimeoutMs: opts.pongTimeoutMs,
|
|
9243
9288
|
});
|
|
9244
9289
|
this.conn.connect();
|
|
9245
9290
|
}
|
|
@@ -9296,6 +9341,9 @@ class SubscriptionsManager {
|
|
|
9296
9341
|
current: new Map(),
|
|
9297
9342
|
};
|
|
9298
9343
|
this.boundsSubs.set(subId, {
|
|
9344
|
+
bounds,
|
|
9345
|
+
owner: handlers.owner,
|
|
9346
|
+
prioritizeOwner: handlers.prioritizeOwner,
|
|
9299
9347
|
onSnapshot: handlers.onSnapshot,
|
|
9300
9348
|
onUpdate: handlers.onUpdate,
|
|
9301
9349
|
onBoundsDelta: handlers.onBoundsDelta,
|
|
@@ -9309,9 +9357,39 @@ class SubscriptionsManager {
|
|
|
9309
9357
|
this.sendMessage({ type: 'unsubscribe', sub_id: subId });
|
|
9310
9358
|
}
|
|
9311
9359
|
updateBounds(subId, bounds) {
|
|
9360
|
+
const entry = this.boundsSubs.get(subId);
|
|
9361
|
+
if (entry)
|
|
9362
|
+
entry.bounds = bounds;
|
|
9312
9363
|
const msg = { type: 'update_bounds', sub_id: subId, bounds };
|
|
9313
9364
|
this.sendMessage(msg);
|
|
9314
9365
|
}
|
|
9366
|
+
onStateChange(state) {
|
|
9367
|
+
if (state !== 'connected')
|
|
9368
|
+
return;
|
|
9369
|
+
if (!this.hasConnected) {
|
|
9370
|
+
this.hasConnected = true;
|
|
9371
|
+
return;
|
|
9372
|
+
}
|
|
9373
|
+
for (const [subId, entry] of this.entitySubs) {
|
|
9374
|
+
const msg = {
|
|
9375
|
+
type: 'subscribe_entity',
|
|
9376
|
+
sub_id: subId,
|
|
9377
|
+
entity_type: entry.type,
|
|
9378
|
+
entity_id: entry.id,
|
|
9379
|
+
};
|
|
9380
|
+
this.sendMessage(msg);
|
|
9381
|
+
}
|
|
9382
|
+
for (const [subId, entry] of this.boundsSubs) {
|
|
9383
|
+
const msg = {
|
|
9384
|
+
type: 'subscribe',
|
|
9385
|
+
sub_id: subId,
|
|
9386
|
+
bounds: entry.bounds,
|
|
9387
|
+
owner: entry.owner,
|
|
9388
|
+
prioritize_owner: entry.prioritizeOwner,
|
|
9389
|
+
};
|
|
9390
|
+
this.sendMessage(msg);
|
|
9391
|
+
}
|
|
9392
|
+
}
|
|
9315
9393
|
onMessage(msg) {
|
|
9316
9394
|
switch (msg.type) {
|
|
9317
9395
|
case 'snapshot':
|