@replit/river 0.6.3 → 0.7.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/__tests__/e2e.test.js +7 -8
- package/dist/__tests__/typescript-stress.test.d.ts +4 -2
- package/dist/__tests__/typescript-stress.test.d.ts.map +1 -1
- package/dist/__tests__/typescript-stress.test.js +3 -1
- package/dist/router/client.d.ts +3 -2
- package/dist/router/client.d.ts.map +1 -1
- package/dist/router/client.js +5 -5
- package/dist/router/server.d.ts +2 -2
- package/dist/router/server.d.ts.map +1 -1
- package/dist/router/server.js +10 -10
- package/dist/testUtils.d.ts +6 -5
- package/dist/testUtils.d.ts.map +1 -1
- package/dist/testUtils.js +7 -16
- package/dist/transport/impls/stdio/stdio.d.ts +37 -0
- package/dist/transport/impls/stdio/stdio.d.ts.map +1 -0
- package/dist/transport/impls/stdio/stdio.js +80 -0
- package/dist/transport/impls/stdio/stdio.test.d.ts +2 -0
- package/dist/transport/impls/stdio/stdio.test.d.ts.map +1 -0
- package/dist/transport/impls/stdio/stdio.test.js +20 -0
- package/dist/transport/impls/ws/client.d.ts +45 -0
- package/dist/transport/impls/ws/client.d.ts.map +1 -0
- package/dist/transport/impls/ws/client.js +102 -0
- package/dist/transport/impls/ws/connection.d.ts +11 -0
- package/dist/transport/impls/ws/connection.d.ts.map +1 -0
- package/dist/transport/impls/ws/connection.js +22 -0
- package/dist/transport/impls/ws/server.d.ts +19 -0
- package/dist/transport/impls/ws/server.d.ts.map +1 -0
- package/dist/transport/impls/ws/server.js +53 -0
- package/dist/transport/impls/ws/ws.test.d.ts +2 -0
- package/dist/transport/impls/ws/ws.test.d.ts.map +1 -0
- package/dist/transport/impls/ws/ws.test.js +97 -0
- package/dist/transport/impls/ws.d.ts +6 -2
- package/dist/transport/impls/ws.d.ts.map +1 -1
- package/dist/transport/impls/ws.js +22 -7
- package/dist/transport/impls/ws.test.js +2 -2
- package/dist/transport/index.d.ts +3 -3
- package/dist/transport/index.d.ts.map +1 -1
- package/dist/transport/index.js +6 -3
- package/dist/transport/message.d.ts +4 -1
- package/dist/transport/message.d.ts.map +1 -1
- package/dist/transport/message.js +3 -0
- package/dist/transport/transport.d.ts +132 -0
- package/dist/transport/transport.d.ts.map +1 -0
- package/dist/transport/transport.js +241 -0
- package/dist/transport/types.d.ts.map +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/// <reference types="ws" />
|
|
2
|
+
import { TransportClientId } from '../../message';
|
|
3
|
+
import { Connection, Transport } from '../../transport';
|
|
4
|
+
import WebSocket from 'isomorphic-ws';
|
|
5
|
+
export declare class WebSocketConnection extends Connection {
|
|
6
|
+
ws: WebSocket;
|
|
7
|
+
constructor(transport: Transport<WebSocketConnection>, connectedTo: TransportClientId, ws: WebSocket);
|
|
8
|
+
send(payload: Uint8Array): boolean;
|
|
9
|
+
close(): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=connection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../../../transport/impls/ws/connection.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,SAAS,MAAM,eAAe,CAAC;AAEtC,qBAAa,mBAAoB,SAAQ,UAAU;IACjD,EAAE,EAAE,SAAS,CAAC;gBAGZ,SAAS,EAAE,SAAS,CAAC,mBAAmB,CAAC,EACzC,WAAW,EAAE,iBAAiB,EAC9B,EAAE,EAAE,SAAS;IAQf,IAAI,CAAC,OAAO,EAAE,UAAU;IASlB,KAAK;CAGZ"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Connection } from '../../transport';
|
|
2
|
+
export class WebSocketConnection extends Connection {
|
|
3
|
+
ws;
|
|
4
|
+
constructor(transport, connectedTo, ws) {
|
|
5
|
+
super(transport, connectedTo);
|
|
6
|
+
this.ws = ws;
|
|
7
|
+
ws.binaryType = 'arraybuffer';
|
|
8
|
+
this.ws.onmessage = (msg) => this.onMessage(msg.data);
|
|
9
|
+
}
|
|
10
|
+
send(payload) {
|
|
11
|
+
if (this.ws.readyState === this.ws.OPEN) {
|
|
12
|
+
this.ws.send(payload);
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async close() {
|
|
20
|
+
this.ws.close();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Codec } from '../../../codec';
|
|
2
|
+
import { TransportClientId } from '../../message';
|
|
3
|
+
import { Transport } from '../../transport';
|
|
4
|
+
import { Server } from 'ws';
|
|
5
|
+
import { WebSocketConnection } from './connection';
|
|
6
|
+
interface Options {
|
|
7
|
+
codec: Codec;
|
|
8
|
+
}
|
|
9
|
+
export declare class WebSocketServerTransport extends Transport<WebSocketConnection> {
|
|
10
|
+
wss: Server;
|
|
11
|
+
clientId: TransportClientId;
|
|
12
|
+
constructor(wss: Server, clientId: TransportClientId, providedOptions?: Partial<Options>);
|
|
13
|
+
setupConnectionStatusListeners(): void;
|
|
14
|
+
createNewConnection(to: string): Promise<void>;
|
|
15
|
+
destroy(): Promise<void>;
|
|
16
|
+
close(): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../../transport/impls/ws/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAkB,MAAM,gBAAgB,CAAC;AAEvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AAC5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEnD,UAAU,OAAO;IACf,KAAK,EAAE,KAAK,CAAC;CACd;AAMD,qBAAa,wBAAyB,SAAQ,SAAS,CAAC,mBAAmB,CAAC;IAC1E,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,iBAAiB,CAAC;gBAG1B,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,iBAAiB,EAC3B,eAAe,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC;IASpC,8BAA8B,IAAI,IAAI;IA8BhC,mBAAmB,CAAC,EAAE,EAAE,MAAM;IAM9B,OAAO;IAKP,KAAK;CAIZ"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { NaiveJsonCodec } from '../../../codec';
|
|
2
|
+
import { log } from '../../../logging';
|
|
3
|
+
import { Transport } from '../../transport';
|
|
4
|
+
import { WebSocketConnection } from './connection';
|
|
5
|
+
const defaultOptions = {
|
|
6
|
+
codec: NaiveJsonCodec,
|
|
7
|
+
};
|
|
8
|
+
export class WebSocketServerTransport extends Transport {
|
|
9
|
+
wss;
|
|
10
|
+
clientId;
|
|
11
|
+
constructor(wss, clientId, providedOptions) {
|
|
12
|
+
const options = { ...defaultOptions, ...providedOptions };
|
|
13
|
+
super(options.codec, clientId);
|
|
14
|
+
this.wss = wss;
|
|
15
|
+
this.clientId = clientId;
|
|
16
|
+
this.setupConnectionStatusListeners();
|
|
17
|
+
}
|
|
18
|
+
setupConnectionStatusListeners() {
|
|
19
|
+
this.wss.on('connection', (ws) => {
|
|
20
|
+
let conn = undefined;
|
|
21
|
+
ws.onmessage = (msg) => {
|
|
22
|
+
const parsedMsg = this.parseMsg(msg.data);
|
|
23
|
+
if (parsedMsg) {
|
|
24
|
+
conn = new WebSocketConnection(this, parsedMsg.from, ws);
|
|
25
|
+
this.onConnect(conn);
|
|
26
|
+
this.handleMsg(parsedMsg);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
// close is always emitted, even on error, ok to do cleanup here
|
|
30
|
+
ws.onclose = () => {
|
|
31
|
+
if (conn) {
|
|
32
|
+
this.onDisconnect(conn);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
ws.onerror = (msg) => {
|
|
36
|
+
log?.warn(`${this.clientId} -- ws error from client ${conn?.connectedTo ?? 'unknown'}: ${msg}`);
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
async createNewConnection(to) {
|
|
41
|
+
const err = `${this.clientId} -- failed to send msg to ${to}, client probably dropped`;
|
|
42
|
+
log?.warn(err);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
async destroy() {
|
|
46
|
+
super.destroy();
|
|
47
|
+
this.wss.close();
|
|
48
|
+
}
|
|
49
|
+
async close() {
|
|
50
|
+
super.close();
|
|
51
|
+
this.wss.close();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws.test.d.ts","sourceRoot":"","sources":["../../../../transport/impls/ws/ws.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import http from 'http';
|
|
2
|
+
import { describe, test, expect, afterAll } from 'vitest';
|
|
3
|
+
import { createWebSocketServer, createWsTransports, createDummyTransportMessage, onServerReady, createLocalWebSocketClient, } from '../../../testUtils';
|
|
4
|
+
import { msg, waitForMessage } from '../..';
|
|
5
|
+
import { WebSocketServerTransport } from './server';
|
|
6
|
+
import { WebSocketClientTransport } from './client';
|
|
7
|
+
describe('sending and receiving across websockets works', async () => {
|
|
8
|
+
const server = http.createServer();
|
|
9
|
+
const port = await onServerReady(server);
|
|
10
|
+
const wss = await createWebSocketServer(server);
|
|
11
|
+
afterAll(() => {
|
|
12
|
+
wss.clients.forEach((socket) => {
|
|
13
|
+
socket.close();
|
|
14
|
+
});
|
|
15
|
+
server.close();
|
|
16
|
+
});
|
|
17
|
+
test('basic send/receive', async () => {
|
|
18
|
+
const [clientTransport, serverTransport] = createWsTransports(port, wss);
|
|
19
|
+
const msg = createDummyTransportMessage();
|
|
20
|
+
clientTransport.send(msg);
|
|
21
|
+
return expect(waitForMessage(serverTransport, (recv) => recv.id === msg.id)).resolves.toStrictEqual(msg.payload);
|
|
22
|
+
});
|
|
23
|
+
test('sending respects to/from fields', async () => {
|
|
24
|
+
const makeDummyMessage = (from, to, message) => {
|
|
25
|
+
return msg(from, to, 'service', 'proc', 'stream', {
|
|
26
|
+
msg: message,
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
const clientId1 = 'client1';
|
|
30
|
+
const clientId2 = 'client2';
|
|
31
|
+
const serverId = 'SERVER';
|
|
32
|
+
const serverTransport = new WebSocketServerTransport(wss, serverId);
|
|
33
|
+
const initClient = async (id) => {
|
|
34
|
+
const client = new WebSocketClientTransport(() => createLocalWebSocketClient(port), id, 'SERVER');
|
|
35
|
+
const initMsg = makeDummyMessage(id, serverId, 'hello server');
|
|
36
|
+
client.send(initMsg);
|
|
37
|
+
await expect(waitForMessage(serverTransport, (recv) => recv.id === initMsg.id)).resolves.toStrictEqual(initMsg.payload);
|
|
38
|
+
return client;
|
|
39
|
+
};
|
|
40
|
+
const client1 = await initClient(clientId1);
|
|
41
|
+
const client2 = await initClient(clientId2);
|
|
42
|
+
// sending messages from server to client shouldn't leak between clients
|
|
43
|
+
const msg1 = makeDummyMessage('SERVER', 'client1', 'hello client1');
|
|
44
|
+
const msg2 = makeDummyMessage('SERVER', 'client2', 'hello client1');
|
|
45
|
+
const promises = Promise.all([
|
|
46
|
+
// true means reject if we receive any message that isn't the one we are expecting
|
|
47
|
+
waitForMessage(client2, (recv) => recv.id === msg2.id, true),
|
|
48
|
+
waitForMessage(client1, (recv) => recv.id === msg1.id, true),
|
|
49
|
+
]);
|
|
50
|
+
serverTransport.send(msg1);
|
|
51
|
+
serverTransport.send(msg2);
|
|
52
|
+
await expect(promises).resolves.toStrictEqual([msg1.payload, msg2.payload]);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe('retry logic', async () => {
|
|
56
|
+
const server = http.createServer();
|
|
57
|
+
const port = await onServerReady(server);
|
|
58
|
+
const wss = await createWebSocketServer(server);
|
|
59
|
+
afterAll(() => {
|
|
60
|
+
wss.clients.forEach((socket) => {
|
|
61
|
+
socket.close();
|
|
62
|
+
});
|
|
63
|
+
server.close();
|
|
64
|
+
});
|
|
65
|
+
// TODO: right now, we only test client-side disconnects, we probably
|
|
66
|
+
// need to also write tests for server-side crashes (but this involves clearing/restoring state)
|
|
67
|
+
// not going to worry about this rn but for future
|
|
68
|
+
test('ws transport is recreated after clean disconnect', async () => {
|
|
69
|
+
const [clientTransport, serverTransport] = createWsTransports(port, wss);
|
|
70
|
+
const msg1 = createDummyTransportMessage();
|
|
71
|
+
const msg2 = createDummyTransportMessage();
|
|
72
|
+
clientTransport.send(msg1);
|
|
73
|
+
await expect(waitForMessage(serverTransport, (recv) => recv.id === msg1.id)).resolves.toStrictEqual(msg1.payload);
|
|
74
|
+
clientTransport.connections.forEach((conn) => conn.ws.close());
|
|
75
|
+
clientTransport.send(msg2);
|
|
76
|
+
return expect(waitForMessage(serverTransport, (recv) => recv.id === msg2.id)).resolves.toStrictEqual(msg2.payload);
|
|
77
|
+
});
|
|
78
|
+
test('ws transport is recreated after unclean disconnect', async () => {
|
|
79
|
+
const [clientTransport, serverTransport] = createWsTransports(port, wss);
|
|
80
|
+
const msg1 = createDummyTransportMessage();
|
|
81
|
+
const msg2 = createDummyTransportMessage();
|
|
82
|
+
clientTransport.send(msg1);
|
|
83
|
+
await expect(waitForMessage(serverTransport, (recv) => recv.id === msg1.id)).resolves.toStrictEqual(msg1.payload);
|
|
84
|
+
clientTransport.connections.forEach((conn) => conn.ws.terminate());
|
|
85
|
+
clientTransport.send(msg2);
|
|
86
|
+
return expect(waitForMessage(serverTransport, (recv) => recv.id === msg2.id)).resolves.toStrictEqual(msg2.payload);
|
|
87
|
+
});
|
|
88
|
+
test('ws transport is not recreated after destroy', async () => {
|
|
89
|
+
const [clientTransport, serverTransport] = createWsTransports(port, wss);
|
|
90
|
+
const msg1 = createDummyTransportMessage();
|
|
91
|
+
const msg2 = createDummyTransportMessage();
|
|
92
|
+
clientTransport.send(msg1);
|
|
93
|
+
await expect(waitForMessage(serverTransport, (recv) => recv.id === msg1.id)).resolves.toStrictEqual(msg1.payload);
|
|
94
|
+
clientTransport.destroy();
|
|
95
|
+
return expect(() => clientTransport.send(msg2)).toThrow(new Error('transport is destroyed, cant send'));
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -29,7 +29,7 @@ export declare class WebSocketTransport extends Transport {
|
|
|
29
29
|
* A flag indicating whether the transport has been destroyed.
|
|
30
30
|
* A destroyed transport will not attempt to reconnect and cannot be used again.
|
|
31
31
|
*/
|
|
32
|
-
|
|
32
|
+
state: 'open' | 'closed' | 'destroyed';
|
|
33
33
|
/**
|
|
34
34
|
* An ongoing reconnect attempt if it exists. When the attempt finishes, it contains a
|
|
35
35
|
* {@link WebSocketResult} object when a connection is established or an error occurs.
|
|
@@ -59,9 +59,13 @@ export declare class WebSocketTransport extends Transport {
|
|
|
59
59
|
*/
|
|
60
60
|
send(msg: OpaqueTransportMessage): MessageId;
|
|
61
61
|
/**
|
|
62
|
-
*
|
|
62
|
+
* Closes the WebSocket transport. Any messages sent while the transport is closed will be silently discarded.
|
|
63
63
|
*/
|
|
64
64
|
close(): Promise<void | undefined>;
|
|
65
|
+
/**
|
|
66
|
+
* Destroys the WebSocket transport. Any messages sent while the transport is closed will throw an error.
|
|
67
|
+
*/
|
|
68
|
+
destroy(): Promise<void | undefined>;
|
|
65
69
|
}
|
|
66
70
|
export {};
|
|
67
71
|
//# sourceMappingURL=ws.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ws.d.ts","sourceRoot":"","sources":["../../../transport/impls/ws.ts"],"names":[],"mappings":";AAAA,OAAO,SAAS,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,OAAO,EACL,SAAS,EACT,sBAAsB,EACtB,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC,UAAU,OAAO;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,KAAK,CAAC;IACb,UAAU,EAAE,aAAa,GAAG,MAAM,CAAC;CACpC;AAQD,KAAK,eAAe,GAAG;IAAE,EAAE,EAAE,SAAS,CAAA;CAAE,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3D;;;;GAIG;AACH,qBAAa,kBAAmB,SAAQ,SAAS;IAC/C;;OAEG;IACH,QAAQ,EAAE,MAAM,OAAO,CAAC,SAAS,CAAC,CAAC;IACnC,EAAE,CAAC,EAAE,SAAS,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IAEjB;;;OAGG;IACH,
|
|
1
|
+
{"version":3,"file":"ws.d.ts","sourceRoot":"","sources":["../../../transport/impls/ws.ts"],"names":[],"mappings":";AAAA,OAAO,SAAS,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,OAAO,EACL,SAAS,EACT,sBAAsB,EACtB,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC,UAAU,OAAO;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,KAAK,CAAC;IACb,UAAU,EAAE,aAAa,GAAG,MAAM,CAAC;CACpC;AAQD,KAAK,eAAe,GAAG;IAAE,EAAE,EAAE,SAAS,CAAA;CAAE,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3D;;;;GAIG;AACH,qBAAa,kBAAmB,SAAQ,SAAS;IAC/C;;OAEG;IACH,QAAQ,EAAE,MAAM,OAAO,CAAC,SAAS,CAAC,CAAC;IACnC,EAAE,CAAC,EAAE,SAAS,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IAEjB;;;OAGG;IACH,KAAK,EAAE,MAAM,GAAG,QAAQ,GAAG,WAAW,CAAC;IAEvC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IAE5C;;;OAGG;IACH,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAE5B;;;;;OAKG;gBAED,QAAQ,EAAE,MAAM,OAAO,CAAC,SAAS,CAAC,EAClC,QAAQ,EAAE,iBAAiB,EAC3B,eAAe,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC;IAWpC;;OAEG;YACW,UAAU;IAuExB;;;;;OAKG;IACH,IAAI,CAAC,GAAG,EAAE,sBAAsB,GAAG,SAAS;IA4B5C;;OAEG;IACG,KAAK;IAMX;;OAEG;IACG,OAAO;CAKd"}
|
|
@@ -22,7 +22,7 @@ export class WebSocketTransport extends Transport {
|
|
|
22
22
|
* A flag indicating whether the transport has been destroyed.
|
|
23
23
|
* A destroyed transport will not attempt to reconnect and cannot be used again.
|
|
24
24
|
*/
|
|
25
|
-
|
|
25
|
+
state;
|
|
26
26
|
/**
|
|
27
27
|
* An ongoing reconnect attempt if it exists. When the attempt finishes, it contains a
|
|
28
28
|
* {@link WebSocketResult} object when a connection is established or an error occurs.
|
|
@@ -42,7 +42,7 @@ export class WebSocketTransport extends Transport {
|
|
|
42
42
|
constructor(wsGetter, clientId, providedOptions) {
|
|
43
43
|
const options = { ...defaultOptions, ...providedOptions };
|
|
44
44
|
super(options.codec, clientId);
|
|
45
|
-
this.
|
|
45
|
+
this.state = 'open';
|
|
46
46
|
this.wsGetter = wsGetter;
|
|
47
47
|
this.options = options;
|
|
48
48
|
this.sendQueue = [];
|
|
@@ -52,6 +52,9 @@ export class WebSocketTransport extends Transport {
|
|
|
52
52
|
* Begins a new attempt to establish a WebSocket connection.
|
|
53
53
|
*/
|
|
54
54
|
async tryConnect() {
|
|
55
|
+
if (this.state !== 'open') {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
55
58
|
// wait until it's ready or we get an error
|
|
56
59
|
this.reconnectPromise ??= new Promise(async (resolve) => {
|
|
57
60
|
log?.info(`${this.clientId} -- establishing a new websocket`);
|
|
@@ -113,11 +116,15 @@ export class WebSocketTransport extends Transport {
|
|
|
113
116
|
*/
|
|
114
117
|
send(msg) {
|
|
115
118
|
const id = msg.id;
|
|
116
|
-
if (this.destroyed) {
|
|
119
|
+
if (this.state === 'destroyed') {
|
|
117
120
|
const err = 'ws is destroyed, cant send';
|
|
118
|
-
log?.error(err);
|
|
121
|
+
log?.error(err + `: ${JSON.stringify(msg)}`);
|
|
119
122
|
throw new Error(err);
|
|
120
123
|
}
|
|
124
|
+
else if (this.state === 'closed') {
|
|
125
|
+
log?.info(`ws is closed, discarding msg: ${JSON.stringify(msg)}`);
|
|
126
|
+
return msg.id;
|
|
127
|
+
}
|
|
121
128
|
this.sendBuffer.set(id, msg);
|
|
122
129
|
if (this.ws && this.ws.readyState === this.ws.OPEN) {
|
|
123
130
|
log?.info(`${this.clientId} -- sending ${JSON.stringify(msg)}`);
|
|
@@ -131,11 +138,19 @@ export class WebSocketTransport extends Transport {
|
|
|
131
138
|
return id;
|
|
132
139
|
}
|
|
133
140
|
/**
|
|
134
|
-
*
|
|
141
|
+
* Closes the WebSocket transport. Any messages sent while the transport is closed will be silently discarded.
|
|
135
142
|
*/
|
|
136
143
|
async close() {
|
|
137
|
-
log?.info('
|
|
138
|
-
this.
|
|
144
|
+
log?.info('closed ws transport');
|
|
145
|
+
this.state = 'closed';
|
|
146
|
+
return this.ws?.close();
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Destroys the WebSocket transport. Any messages sent while the transport is closed will throw an error.
|
|
150
|
+
*/
|
|
151
|
+
async destroy() {
|
|
152
|
+
log?.info('destroyed ws transport');
|
|
153
|
+
this.state = 'destroyed';
|
|
139
154
|
return this.ws?.close();
|
|
140
155
|
}
|
|
141
156
|
}
|
|
@@ -52,13 +52,13 @@ describe('retry logic', async () => {
|
|
|
52
52
|
clientTransport.send(msg2);
|
|
53
53
|
return expect(waitForMessage(serverTransport, (recv) => recv.id === msg2.id)).resolves.toStrictEqual(msg2.payload);
|
|
54
54
|
});
|
|
55
|
-
test('ws transport is not recreated after
|
|
55
|
+
test('ws transport is not recreated after destroy', async () => {
|
|
56
56
|
const [clientTransport, serverTransport] = createWsTransports(port, wss);
|
|
57
57
|
const msg1 = createDummyTransportMessage();
|
|
58
58
|
const msg2 = createDummyTransportMessage();
|
|
59
59
|
clientTransport.send(msg1);
|
|
60
60
|
await expect(waitForMessage(serverTransport, (recv) => recv.id === msg1.id)).resolves.toStrictEqual(msg1.payload);
|
|
61
|
-
clientTransport.
|
|
61
|
+
clientTransport.destroy();
|
|
62
62
|
return expect(() => clientTransport.send(msg2)).toThrow(new Error('ws is destroyed, cant send'));
|
|
63
63
|
});
|
|
64
64
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { OpaqueTransportMessage } from './message';
|
|
2
|
-
import { Transport } from './
|
|
3
|
-
export { Transport } from './
|
|
2
|
+
import { Connection, Transport } from './transport';
|
|
3
|
+
export { Transport } from './transport';
|
|
4
4
|
export { TransportMessageSchema, OpaqueTransportMessageSchema, msg, reply, } from './message';
|
|
5
5
|
export type { TransportMessage, MessageId, OpaqueTransportMessage, TransportClientId, } from './message';
|
|
6
6
|
/**
|
|
@@ -9,5 +9,5 @@ export type { TransportMessage, MessageId, OpaqueTransportMessage, TransportClie
|
|
|
9
9
|
* @param filter - An optional filter function to apply to the received messages.
|
|
10
10
|
* @returns A promise that resolves with the payload of the first message that passes the filter.
|
|
11
11
|
*/
|
|
12
|
-
export declare function waitForMessage(t: Transport
|
|
12
|
+
export declare function waitForMessage(t: Transport<Connection>, filter?: (msg: OpaqueTransportMessage) => boolean, rejectMismatch?: boolean): Promise<unknown>;
|
|
13
13
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../transport/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../transport/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGpD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EACL,sBAAsB,EACtB,4BAA4B,EAC5B,GAAG,EACH,KAAK,GACN,MAAM,WAAW,CAAC;AACnB,YAAY,EACV,gBAAgB,EAChB,SAAS,EACT,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,WAAW,CAAC;AAEnB;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,CAAC,EAAE,SAAS,CAAC,UAAU,CAAC,EACxB,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,sBAAsB,KAAK,OAAO,EACjD,cAAc,CAAC,EAAE,OAAO,oBAczB"}
|
package/dist/transport/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// re-export
|
|
2
|
-
export { Transport } from './
|
|
2
|
+
export { Transport } from './transport';
|
|
3
3
|
export { TransportMessageSchema, OpaqueTransportMessageSchema, msg, reply, } from './message';
|
|
4
4
|
/**
|
|
5
5
|
* Waits for a message from the transport.
|
|
@@ -7,13 +7,16 @@ export { TransportMessageSchema, OpaqueTransportMessageSchema, msg, reply, } fro
|
|
|
7
7
|
* @param filter - An optional filter function to apply to the received messages.
|
|
8
8
|
* @returns A promise that resolves with the payload of the first message that passes the filter.
|
|
9
9
|
*/
|
|
10
|
-
export async function waitForMessage(t, filter) {
|
|
11
|
-
return new Promise((resolve,
|
|
10
|
+
export async function waitForMessage(t, filter, rejectMismatch) {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
12
|
function onMessage(msg) {
|
|
13
13
|
if (!filter || filter?.(msg)) {
|
|
14
14
|
resolve(msg.payload);
|
|
15
15
|
t.removeMessageListener(onMessage);
|
|
16
16
|
}
|
|
17
|
+
else if (rejectMismatch) {
|
|
18
|
+
reject(new Error('message didnt match the filter'));
|
|
19
|
+
}
|
|
17
20
|
}
|
|
18
21
|
t.addMessageListener(onMessage);
|
|
19
22
|
});
|
|
@@ -44,6 +44,9 @@ export declare const TransportAckSchema: import("@sinclair/typebox").TObject<{
|
|
|
44
44
|
ack: import("@sinclair/typebox").TString;
|
|
45
45
|
}>;
|
|
46
46
|
}>;
|
|
47
|
+
export declare const ControlMessagePayloadSchema: import("@sinclair/typebox").TObject<{
|
|
48
|
+
type: import("@sinclair/typebox").TLiteral<"CLOSE">;
|
|
49
|
+
}>;
|
|
47
50
|
/**
|
|
48
51
|
* Defines the schema for an opaque transport message that is agnostic to any
|
|
49
52
|
* procedure/service.
|
|
@@ -80,7 +83,7 @@ export type MessageId = string;
|
|
|
80
83
|
* @template T - The type of the opaque payload.
|
|
81
84
|
*/
|
|
82
85
|
export type OpaqueTransportMessage = TransportMessage<unknown>;
|
|
83
|
-
export type TransportClientId =
|
|
86
|
+
export type TransportClientId = string;
|
|
84
87
|
/**
|
|
85
88
|
* Creates a transport message with the given parameters. You shouldn't need to call this manually unless
|
|
86
89
|
* you're writing a test.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../transport/message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAGlD;;;;;;GAMG;AACH,0BAAkB,YAAY;IAC5B,MAAM,IAAS;IACf,aAAa,IAAS;IACtB,eAAe,IAAS;CACzB;AAED;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;EAU/B,CAAC;AAEL;;;;GAIG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;EAI9B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,4BAA4B;;;;;;;;;EAExC,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,CAC1B,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IACzE;IACF,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAE/B;;;GAGG;AACH,MAAM,MAAM,sBAAsB,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;AAC/D,MAAM,MAAM,iBAAiB,GAAG,
|
|
1
|
+
{"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../transport/message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAGlD;;;;;;GAMG;AACH,0BAAkB,YAAY;IAC5B,MAAM,IAAS;IACf,aAAa,IAAS;IACtB,eAAe,IAAS;CACzB;AAED;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;EAU/B,CAAC;AAEL;;;;GAIG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;EAI9B,CAAC;AAEF,eAAO,MAAM,2BAA2B;;EAEtC,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,4BAA4B;;;;;;;;;EAExC,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,CAC1B,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IACzE;IACF,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAE/B;;;GAGG;AACH,MAAM,MAAM,sBAAsB,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;AAC/D,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC;AAEvC;;;;;;;;;;GAUG;AACH,wBAAgB,GAAG,CAAC,OAAO,SAAS,MAAM,EACxC,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,GACf,gBAAgB,CAAC,OAAO,CAAC,CAW3B;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,OAAO,SAAS,MAAM,EAC1C,GAAG,EAAE,sBAAsB,EAC3B,QAAQ,EAAE,OAAO,GAChB,gBAAgB,CAAC,OAAO,CAAC,CAQ3B;AAED;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAElD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAIzD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAK1D"}
|
|
@@ -24,6 +24,9 @@ export const TransportMessageSchema = (t) => Type.Object({
|
|
|
24
24
|
export const TransportAckSchema = TransportMessageSchema(Type.Object({
|
|
25
25
|
ack: Type.String(),
|
|
26
26
|
}));
|
|
27
|
+
export const ControlMessagePayloadSchema = Type.Object({
|
|
28
|
+
type: Type.Literal('CLOSE'),
|
|
29
|
+
});
|
|
27
30
|
/**
|
|
28
31
|
* Defines the schema for an opaque transport message that is agnostic to any
|
|
29
32
|
* procedure/service.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { Codec } from '../codec/types';
|
|
2
|
+
import { MessageId, OpaqueTransportMessage, TransportClientId } from './message';
|
|
3
|
+
/**
|
|
4
|
+
* Abstract base for a connection between two nodes in a River network.
|
|
5
|
+
* A connection is responsible for sending and receiving messages on a 1:1
|
|
6
|
+
* basis between nodes.
|
|
7
|
+
* Connections can be reused across different transports.
|
|
8
|
+
* @abstract
|
|
9
|
+
*/
|
|
10
|
+
export declare abstract class Connection {
|
|
11
|
+
connectedTo: TransportClientId;
|
|
12
|
+
transport: Transport<Connection>;
|
|
13
|
+
constructor(transport: Transport<Connection>, connectedTo: TransportClientId);
|
|
14
|
+
onMessage(msg: Uint8Array): void;
|
|
15
|
+
abstract send(msg: Uint8Array): boolean;
|
|
16
|
+
abstract close(): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
export type TransportStatus = 'open' | 'closed' | 'destroyed';
|
|
19
|
+
/**
|
|
20
|
+
* Abstract base for a transport layer for communication between nodes in a River network.
|
|
21
|
+
* A transport is responsible for handling the 1:n connection logic between nodes and
|
|
22
|
+
* delegating sending/receiving to connections.
|
|
23
|
+
* Any River transport methods need to implement this interface.
|
|
24
|
+
* @abstract
|
|
25
|
+
*/
|
|
26
|
+
export declare abstract class Transport<ConnType extends Connection> {
|
|
27
|
+
/**
|
|
28
|
+
* A flag indicating whether the transport has been destroyed.
|
|
29
|
+
* A destroyed transport will not attempt to reconnect and cannot be used again.
|
|
30
|
+
*/
|
|
31
|
+
state: TransportStatus;
|
|
32
|
+
/**
|
|
33
|
+
* The {@link Codec} used to encode and decode messages.
|
|
34
|
+
*/
|
|
35
|
+
codec: Codec;
|
|
36
|
+
/**
|
|
37
|
+
* The client ID of this transport.
|
|
38
|
+
*/
|
|
39
|
+
clientId: TransportClientId;
|
|
40
|
+
/**
|
|
41
|
+
* The set of message handlers registered with this transport.
|
|
42
|
+
*/
|
|
43
|
+
messageHandlers: Set<(msg: OpaqueTransportMessage) => void>;
|
|
44
|
+
/**
|
|
45
|
+
* An array of message IDs that are waiting to be sent over the WebSocket connection.
|
|
46
|
+
* This builds up if the WebSocket is down for a period of time.
|
|
47
|
+
*/
|
|
48
|
+
sendQueue: Map<TransportClientId, Array<MessageId>>;
|
|
49
|
+
/**
|
|
50
|
+
* The buffer of messages that have been sent but not yet acknowledged.
|
|
51
|
+
*/
|
|
52
|
+
sendBuffer: Map<MessageId, OpaqueTransportMessage>;
|
|
53
|
+
/**
|
|
54
|
+
* The map of {@link Connection}s managed by this transport.
|
|
55
|
+
*/
|
|
56
|
+
connections: Map<TransportClientId, ConnType>;
|
|
57
|
+
/**
|
|
58
|
+
* Creates a new Transport instance.
|
|
59
|
+
* @param codec The codec used to encode and decode messages.
|
|
60
|
+
* @param clientId The client ID of this transport.
|
|
61
|
+
*/
|
|
62
|
+
constructor(codec: Codec, clientId: TransportClientId);
|
|
63
|
+
/**
|
|
64
|
+
* Abstract method that sets up {@link onConnect}, and {@link onDisconnect} listeners.
|
|
65
|
+
* The downstream implementation needs to implement this.
|
|
66
|
+
*/
|
|
67
|
+
abstract setupConnectionStatusListeners(): void;
|
|
68
|
+
/**
|
|
69
|
+
* Abstract method that creates a new {@link Connection} object. This should call
|
|
70
|
+
* {@link onConnect} when the connection is established. The downstream implementation needs to implement this.
|
|
71
|
+
* @param to The client ID of the node to connect to.
|
|
72
|
+
* @returns The new connection object.
|
|
73
|
+
*/
|
|
74
|
+
abstract createNewConnection(to: TransportClientId): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* The downstream implementation needs to call this when a new connection is established.
|
|
77
|
+
* @param conn The connection object.
|
|
78
|
+
*/
|
|
79
|
+
onConnect(conn: ConnType): void;
|
|
80
|
+
/**
|
|
81
|
+
* The downstream implementation needs to call this when a connection is closed.
|
|
82
|
+
* @param conn The connection object.
|
|
83
|
+
*/
|
|
84
|
+
onDisconnect(conn: ConnType): void;
|
|
85
|
+
/**
|
|
86
|
+
* Handles a message received by this transport. Thin wrapper around {@link handleMsg} and {@link parseMsg}.
|
|
87
|
+
* @param msg The message to handle.
|
|
88
|
+
*/
|
|
89
|
+
onMessage(msg: Uint8Array): void;
|
|
90
|
+
/**
|
|
91
|
+
* Parses a message from a Uint8Array into a {@link OpaqueTransportMessage}.
|
|
92
|
+
* @param msg The message to parse.
|
|
93
|
+
* @returns The parsed message, or null if the message is malformed or invalid.
|
|
94
|
+
*/
|
|
95
|
+
protected parseMsg(msg: Uint8Array): OpaqueTransportMessage | null;
|
|
96
|
+
/**
|
|
97
|
+
* Called when a message is received by this transport.
|
|
98
|
+
* You generally shouldn't need to override this in downstream transport implementations.
|
|
99
|
+
* @param msg The received message.
|
|
100
|
+
*/
|
|
101
|
+
protected handleMsg(msg: OpaqueTransportMessage | null): void;
|
|
102
|
+
/**
|
|
103
|
+
* Adds a message listener to this transport.
|
|
104
|
+
* @param handler The message handler to add.
|
|
105
|
+
*/
|
|
106
|
+
addMessageListener(handler: (msg: OpaqueTransportMessage) => void): void;
|
|
107
|
+
/**
|
|
108
|
+
* Removes a message listener from this transport.
|
|
109
|
+
* @param handler The message handler to remove.
|
|
110
|
+
*/
|
|
111
|
+
removeMessageListener(handler: (msg: OpaqueTransportMessage) => void): void;
|
|
112
|
+
/**
|
|
113
|
+
* Sends a message over this transport, delegating to the appropriate connection to actually
|
|
114
|
+
* send the message.
|
|
115
|
+
* @param msg The message to send.
|
|
116
|
+
* @returns The ID of the sent message.
|
|
117
|
+
*/
|
|
118
|
+
send(msg: OpaqueTransportMessage): MessageId;
|
|
119
|
+
/**
|
|
120
|
+
* Default close implementation for transports. You should override this in the downstream
|
|
121
|
+
* implementation if you need to do any additional cleanup and call super.close() at the end.
|
|
122
|
+
* Closes the transport. Any messages sent while the transport is closed will be silently discarded.
|
|
123
|
+
*/
|
|
124
|
+
close(): Promise<void>;
|
|
125
|
+
/**
|
|
126
|
+
* Default destroy implementation for transports. You should override this in the downstream
|
|
127
|
+
* implementation if you need to do any additional cleanup and call super.destroy() at the end.
|
|
128
|
+
* Destroys the transport. Any messages sent while the transport is destroyed will throw an error.
|
|
129
|
+
*/
|
|
130
|
+
destroy(): Promise<void>;
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../transport/transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAEvC,OAAO,EAEL,SAAS,EACT,sBAAsB,EAGtB,iBAAiB,EAGlB,MAAM,WAAW,CAAC;AAGnB;;;;;;GAMG;AACH,8BAAsB,UAAU;IAC9B,WAAW,EAAE,iBAAiB,CAAC;IAC/B,SAAS,EAAE,SAAS,CAAC,UAAU,CAAC,CAAC;gBAG/B,SAAS,EAAE,SAAS,CAAC,UAAU,CAAC,EAChC,WAAW,EAAE,iBAAiB;IAMhC,SAAS,CAAC,GAAG,EAAE,UAAU;IAIzB,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO;IACvC,QAAQ,CAAC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAChC;AAED,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,GAAG,WAAW,CAAC;AAE9D;;;;;;GAMG;AACH,8BAAsB,SAAS,CAAC,QAAQ,SAAS,UAAU;IACzD;;;OAGG;IACH,KAAK,EAAE,eAAe,CAAC;IAEvB;;OAEG;IACH,KAAK,EAAE,KAAK,CAAC;IAEb;;OAEG;IACH,QAAQ,EAAE,iBAAiB,CAAC;IAE5B;;OAEG;IACH,eAAe,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,sBAAsB,KAAK,IAAI,CAAC,CAAC;IAE5D;;;OAGG;IACH,SAAS,EAAE,GAAG,CAAC,iBAAiB,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;IAEpD;;OAEG;IACH,UAAU,EAAE,GAAG,CAAC,SAAS,EAAE,sBAAsB,CAAC,CAAC;IAEnD;;OAEG;IACH,WAAW,EAAE,GAAG,CAAC,iBAAiB,EAAE,QAAQ,CAAC,CAAC;IAE9C;;;;OAIG;gBACS,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,iBAAiB;IAUrD;;;OAGG;IACH,QAAQ,CAAC,8BAA8B,IAAI,IAAI;IAE/C;;;;;OAKG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAElE;;;OAGG;IACH,SAAS,CAAC,IAAI,EAAE,QAAQ;IAyBxB;;;OAGG;IACH,YAAY,CAAC,IAAI,EAAE,QAAQ;IAM3B;;;OAGG;IACH,SAAS,CAAC,GAAG,EAAE,UAAU;IAIzB;;;;OAIG;IACH,SAAS,CAAC,QAAQ,CAAC,GAAG,EAAE,UAAU,GAAG,sBAAsB,GAAG,IAAI;IAmBlE;;;;OAIG;IACH,SAAS,CAAC,SAAS,CAAC,GAAG,EAAE,sBAAsB,GAAG,IAAI;IAgCtD;;;OAGG;IACH,kBAAkB,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,sBAAsB,KAAK,IAAI,GAAG,IAAI;IAIxE;;;OAGG;IACH,qBAAqB,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,sBAAsB,KAAK,IAAI,GAAG,IAAI;IAI3E;;;;;OAKG;IACH,IAAI,CAAC,GAAG,EAAE,sBAAsB,GAAG,SAAS;IA4C5C;;;;OAIG;IACG,KAAK;IAUX;;;;OAIG;IACG,OAAO;CASd"}
|