@kinotic-ai/core 1.2.2 → 1.3.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/index.cjs CHANGED
@@ -68,6 +68,7 @@ __export(exports_src, {
68
68
  Version: () => Version,
69
69
  TextEventFactory: () => TextEventFactory,
70
70
  Sort: () => Sort,
71
+ SessionKeepAliveMode: () => SessionKeepAliveMode,
71
72
  ServiceRegistry: () => ServiceRegistry,
72
73
  ServerInfo: () => ServerInfo,
73
74
  Scope: () => Scope,
@@ -95,7 +96,6 @@ __export(exports_src, {
95
96
  Context: () => Context,
96
97
  ConnectionInfo: () => ConnectionInfo,
97
98
  ConnectedInfo: () => ConnectedInfo,
98
- ConnectHeaders: () => ConnectHeaders,
99
99
  CONTEXT_METADATA_KEY: () => CONTEXT_METADATA_KEY,
100
100
  AuthorizationError: () => AuthorizationError,
101
101
  AuthenticationError: () => AuthenticationError,
@@ -104,19 +104,22 @@ __export(exports_src, {
104
104
  module.exports = __toCommonJS(exports_src);
105
105
 
106
106
  // packages/core/src/api/ConnectionInfo.ts
107
- class ConnectHeaders {
108
- }
109
-
110
107
  class ServerInfo {
111
108
  host;
112
109
  port;
113
110
  useSSL;
114
111
  }
112
+ var SessionKeepAliveMode;
113
+ ((SessionKeepAliveMode2) => {
114
+ SessionKeepAliveMode2["NONE"] = "NONE";
115
+ SessionKeepAliveMode2["ACTIVITY"] = "ACTIVITY";
116
+ SessionKeepAliveMode2["CONNECTION"] = "CONNECTION";
117
+ })(SessionKeepAliveMode ||= {});
115
118
 
116
119
  class ConnectionInfo extends ServerInfo {
117
- connectHeaders;
120
+ webSocketFactory;
118
121
  maxConnectionAttempts;
119
- disableStickySession;
122
+ sessionKeepAlive = "ACTIVITY" /* ACTIVITY */;
120
123
  }
121
124
  // packages/core/src/api/errors/KinoticError.ts
122
125
  class KinoticError extends Error {
@@ -132,10 +135,8 @@ var EventConstants;
132
135
  EventConstants2["CONTENT_TYPE_HEADER"] = "content-type";
133
136
  EventConstants2["CONTENT_LENGTH_HEADER"] = "content-length";
134
137
  EventConstants2["REPLY_TO_HEADER"] = "reply-to";
135
- EventConstants2["REPLY_TO_ID_HEADER"] = "reply-to-id";
136
- EventConstants2["SESSION_HEADER"] = "session";
137
138
  EventConstants2["CONNECTED_INFO_HEADER"] = "connected-info";
138
- EventConstants2["DISABLE_STICKY_SESSION_HEADER"] = "disable-sticky-session";
139
+ EventConstants2["SESSION_KEEP_ALIVE_HEADER"] = "session-keep-alive";
139
140
  EventConstants2["CORRELATION_ID_HEADER"] = "__correlation-id";
140
141
  EventConstants2["ERROR_HEADER"] = "error";
141
142
  EventConstants2["COMPLETE_HEADER"] = "complete";
@@ -148,6 +149,8 @@ var EventConstants;
148
149
  EventConstants2["SERVICE_DESTINATION_SCHEME"] = "srv";
149
150
  EventConstants2["STREAM_DESTINATION_PREFIX"] = "stream://";
150
151
  EventConstants2["STREAM_DESTINATION_SCHEME"] = "stream";
152
+ EventConstants2["REPLY_DESTINATION_PREFIX"] = "reply://";
153
+ EventConstants2["REPLY_DESTINATION_SCHEME"] = "reply";
151
154
  EventConstants2["CONTENT_JSON"] = "application/json";
152
155
  EventConstants2["CONTENT_TEXT"] = "text/plain";
153
156
  EventConstants2["TRACEPARENT_HEADER"] = "traceparent";
@@ -157,8 +160,8 @@ var EventConstants;
157
160
  // packages/core/src/internal/api/event/StompConnectionManager.ts
158
161
  var import_rx_stomp = require("@stomp/rx-stomp");
159
162
  var import_stompjs = require("@stomp/stompjs");
160
- var import_uuid = require("uuid");
161
163
  var import_debug = __toESM(require("debug"));
164
+ var import_uuid = require("uuid");
162
165
 
163
166
  class StompConnectionManager {
164
167
  lastWebsocketError = null;
@@ -171,9 +174,10 @@ class StompConnectionManager {
171
174
  initialConnectionSuccessful = false;
172
175
  debugLogger = import_debug.default("kinoitc:stomp");
173
176
  uuidv4 = import_uuid.v4();
174
- replyToId = import_uuid.v4();
175
- _replyToCri = "srv://" /* SERVICE_DESTINATION_PREFIX */ + this.replyToId + ":" + this.uuidv4 + "@kinoitc.js.EventBus/replyHandler";
177
+ _replyToCri = null;
178
+ serverHeadersSubscription = null;
176
179
  deactivationHandler = null;
180
+ replyToCriChangedHandler = null;
177
181
  get active() {
178
182
  return !!this.rxStomp;
179
183
  }
@@ -201,31 +205,21 @@ class StompConnectionManager {
201
205
  this.initialConnectionSuccessful = false;
202
206
  this.lastWebsocketError = null;
203
207
  this.maxConnectionAttemptsReached = false;
208
+ this._replyToCri = null;
209
+ this.serverHeadersSubscription?.unsubscribe();
210
+ this.serverHeadersSubscription = null;
204
211
  const url = "ws" + (connectionInfo.useSSL ? "s" : "") + "://" + connectionInfo.host + (connectionInfo.port ? ":" + connectionInfo.port : "") + "/v1";
205
212
  this.rxStomp = new import_rx_stomp.RxStomp;
206
- let connectHeadersInternal = typeof connectionInfo.connectHeaders !== "function" && connectionInfo.connectHeaders != null ? connectionInfo.connectHeaders : {};
207
213
  const stompConfig = {
208
214
  brokerURL: url,
209
- connectHeaders: connectHeadersInternal,
215
+ connectHeaders: {
216
+ ["session-keep-alive" /* SESSION_KEEP_ALIVE_HEADER */]: connectionInfo.sessionKeepAlive
217
+ },
210
218
  heartbeatIncoming: 120000,
211
219
  heartbeatOutgoing: 30000,
212
220
  reconnectDelay: this.INITIAL_RECONNECT_DELAY,
221
+ webSocketFactory: connectionInfo.webSocketFactory,
213
222
  beforeConnect: async () => {
214
- if (typeof connectionInfo.connectHeaders === "function") {
215
- const headers = await connectionInfo.connectHeaders();
216
- for (const key in headers) {
217
- connectHeadersInternal[key] = headers[key];
218
- }
219
- }
220
- if (connectionInfo.disableStickySession) {
221
- connectHeadersInternal["disable-sticky-session" /* DISABLE_STICKY_SESSION_HEADER */] = "true";
222
- }
223
- if (connectHeadersInternal["reply-to-id" /* REPLY_TO_ID_HEADER */]) {
224
- this.replyToId = connectHeadersInternal["reply-to-id" /* REPLY_TO_ID_HEADER */];
225
- this._replyToCri = "srv://" /* SERVICE_DESTINATION_PREFIX */ + this.replyToId + ":" + this.uuidv4 + "@kinoitc.js.EventBus/replyHandler";
226
- } else {
227
- connectHeadersInternal["reply-to-id" /* REPLY_TO_ID_HEADER */] = this.replyToId;
228
- }
229
223
  if (connectionInfo?.maxConnectionAttempts) {
230
224
  this.connectionAttempts++;
231
225
  if (this.connectionAttempts > connectionInfo.maxConnectionAttempts) {
@@ -267,36 +261,29 @@ class StompConnectionManager {
267
261
  this.rxStomp = null;
268
262
  reject(message);
269
263
  });
270
- const serverHeadersSubscription = this.rxStomp.serverHeaders$.subscribe((value) => {
271
- let connectedInfoJson = value["connected-info" /* CONNECTED_INFO_HEADER */];
272
- if (connectedInfoJson != null) {
273
- const connectedInfo = JSON.parse(connectedInfoJson);
274
- if (!connectionInfo.disableStickySession) {
275
- serverHeadersSubscription.unsubscribe();
276
- if (connectedInfo.sessionId != null && connectedInfo.replyToId != null) {
277
- if (connectionInfo.connectHeaders != null) {
278
- for (let key in connectHeadersInternal) {
279
- delete connectHeadersInternal[key];
280
- }
281
- }
282
- connectHeadersInternal["session" /* SESSION_HEADER */] = connectedInfo.sessionId;
283
- resolve(connectedInfo);
284
- } else {
285
- reject("Server did not return proper data for successful login");
286
- }
287
- } else if (typeof connectionInfo.connectHeaders === "function") {
288
- for (let key in connectHeadersInternal) {
289
- delete connectHeadersInternal[key];
290
- }
291
- if (!this.initialConnectionSuccessful) {
292
- resolve(connectedInfo);
293
- }
294
- } else if (typeof connectionInfo.connectHeaders === "object") {
295
- serverHeadersSubscription.unsubscribe();
296
- resolve(connectedInfo);
264
+ this.serverHeadersSubscription = this.rxStomp.serverHeaders$.subscribe((value) => {
265
+ const connectedInfoJson = value["connected-info" /* CONNECTED_INFO_HEADER */];
266
+ const firstConnect = this._replyToCri == null;
267
+ if (connectedInfoJson == null) {
268
+ if (firstConnect) {
269
+ reject("Server did not return proper data for successful login");
297
270
  }
298
- } else {
299
- reject("Server did not return proper data for successful login");
271
+ return;
272
+ }
273
+ const connectedInfo = JSON.parse(connectedInfoJson);
274
+ if (connectedInfo.replyToId == null) {
275
+ if (firstConnect) {
276
+ reject("Server did not return a replyToId for successful login");
277
+ }
278
+ return;
279
+ }
280
+ const newReplyToCri = "reply://" /* REPLY_DESTINATION_PREFIX */ + connectedInfo.replyToId + ":" + this.uuidv4 + "@kinoitc.js.EventBus/replyHandler";
281
+ if (firstConnect) {
282
+ this._replyToCri = newReplyToCri;
283
+ resolve(connectedInfo);
284
+ } else if (this._replyToCri !== newReplyToCri) {
285
+ this._replyToCri = newReplyToCri;
286
+ this.replyToCriChangedHandler?.(newReplyToCri);
300
287
  }
301
288
  });
302
289
  this.rxStomp.activate();
@@ -305,6 +292,8 @@ class StompConnectionManager {
305
292
  async deactivate(force) {
306
293
  if (this.rxStomp) {
307
294
  await this.rxStomp.deactivate({ force });
295
+ this.serverHeadersSubscription?.unsubscribe();
296
+ this.serverHeadersSubscription = null;
308
297
  if (this.deactivationHandler) {
309
298
  this.deactivationHandler();
310
299
  }
@@ -385,6 +374,10 @@ class EventBus {
385
374
  this.stompConnectionManager.deactivationHandler = () => {
386
375
  this.cleanup();
387
376
  };
377
+ this.stompConnectionManager.replyToCriChangedHandler = (replyToCri) => {
378
+ this.replyToCri = replyToCri;
379
+ this.resetRequestReplies("Reply destination changed");
380
+ };
388
381
  }
389
382
  isConnectionActive() {
390
383
  return this.stompConnectionManager.active;
@@ -452,7 +445,7 @@ class EventBus {
452
445
  })).subscribe({
453
446
  next(value) {
454
447
  if (value.hasHeader("control" /* CONTROL_HEADER */)) {
455
- if (value.headers.get("control" /* CONTROL_HEADER */) === "complete") {
448
+ if (value.headers.get("control" /* CONTROL_HEADER */) === "complete" /* CONTROL_VALUE_COMPLETE */) {
456
449
  serverSignaledCompletion = true;
457
450
  subscriber.complete();
458
451
  } else {
@@ -496,8 +489,16 @@ class EventBus {
496
489
  return this._observe(cri);
497
490
  }
498
491
  cleanup() {
492
+ this.resetRequestReplies("Connection disconnected");
493
+ if (this.errorSubjectSubscription) {
494
+ this.errorSubjectSubscription.unsubscribe();
495
+ this.errorSubjectSubscription = null;
496
+ }
497
+ this.serverInfo = null;
498
+ }
499
+ resetRequestReplies(reason) {
499
500
  if (this.requestRepliesSubject != null) {
500
- this.requestRepliesSubject.error(new Error("Connection disconnected"));
501
+ this.requestRepliesSubject.error(new Error(reason));
501
502
  if (this.requestRepliesSubscription != null) {
502
503
  this.requestRepliesSubscription.unsubscribe();
503
504
  this.requestRepliesSubscription = null;
@@ -505,11 +506,6 @@ class EventBus {
505
506
  this.requestRepliesSubject = null;
506
507
  this.requestRepliesObservable = null;
507
508
  }
508
- if (this.errorSubjectSubscription) {
509
- this.errorSubjectSubscription.unsubscribe();
510
- this.errorSubjectSubscription = null;
511
- }
512
- this.serverInfo = null;
513
509
  }
514
510
  createSendUnavailableError() {
515
511
  let ret = "You must call connect on the event bus before sending any request";
@@ -993,7 +989,7 @@ var import_operators2 = require("rxjs/operators");
993
989
  // packages/core/package.json
994
990
  var package_default = {
995
991
  name: "@kinotic-ai/core",
996
- version: "1.2.2",
992
+ version: "1.3.0",
997
993
  type: "module",
998
994
  files: [
999
995
  "dist"
@@ -1487,7 +1483,6 @@ class AuthorizationError extends KinoticError {
1487
1483
  }
1488
1484
  // packages/core/src/api/security/ConnectedInfo.ts
1489
1485
  class ConnectedInfo {
1490
- sessionId;
1491
1486
  replyToId;
1492
1487
  participant;
1493
1488
  }
package/dist/index.d.cts CHANGED
@@ -1,26 +1,53 @@
1
1
  /**
2
- * ConnectHeaders to use during connection to the kinoitc server
3
- * These headers will be sent as part of the STOMP CONNECT frame
4
- * This is typically used for authentication information, but any data can be sent
2
+ * Structural shape of a WebSocket used by the underlying STOMP client.
3
+ * Copied from the WebSocket interface to avoid pulling in the DOM typelib,
4
+ * so this type stays usable in Node environments where `lib: dom` is not set.
5
5
  */
6
- declare class ConnectHeaders {
7
- [key: string]: string;
6
+ interface IWebSocket {
7
+ url: string;
8
+ binaryType?: string;
9
+ readyState: number;
10
+ onopen: ((ev?: any) => any) | undefined | null;
11
+ onclose: ((ev?: any) => any) | undefined | null;
12
+ onerror: ((ev: any) => any) | undefined | null;
13
+ onmessage: ((ev: any) => any) | undefined | null;
14
+ close(code?: number, reason?: string): void;
15
+ send(data: string | ArrayBuffer): void;
8
16
  }
17
+ /**
18
+ * Factory invoked on every (re)connect to produce the WebSocket the STOMP
19
+ * client will use. Supply this in Node when you need to set headers on the
20
+ * upgrade request (for example, an Authorization header). Browser callers
21
+ * normally leave this unset and rely on the session cookie established by a
22
+ * prior REST login.
23
+ */
24
+ type WebSocketFactory = () => IWebSocket;
9
25
  declare class ServerInfo {
10
26
  host: string;
11
27
  port?: number | null;
12
28
  useSSL?: boolean | null;
13
29
  }
30
+ declare enum SessionKeepAliveMode {
31
+ NONE = "NONE",
32
+ ACTIVITY = "ACTIVITY",
33
+ CONNECTION = "CONNECTION"
34
+ }
14
35
  /**
15
- * ConnectionInfo provides the information needed to connect to the kinoitc server
36
+ * ConnectionInfo provides the information needed to connect to the kinoitc server.
37
+ *
38
+ * Authentication is performed during the WebSocket upgrade (handshake), not in
39
+ * the STOMP CONNECT frame. In the browser, log in via the REST endpoints first
40
+ * and the established session cookie will be used. In Node, supply a
41
+ * {@link WebSocketFactory} that attaches the required upgrade headers.
16
42
  */
17
43
  declare class ConnectionInfo extends ServerInfo {
18
44
  /**
19
- * The headers to send during the connection to the kinoitc server.
20
- * If a function is provided, it will be called to get the headers each time the connection is established.
21
- * This is useful for providing dynamic headers, such as a JWT token that expires.
45
+ * Optional factory used to create the underlying WebSocket. Use this in
46
+ * Node to attach custom headers (such as Authorization) to the upgrade
47
+ * request. If omitted, a default WebSocket is created and authentication
48
+ * is expected to come from the session cookie.
22
49
  */
23
- connectHeaders?: ConnectHeaders | (() => Promise<ConnectHeaders>);
50
+ webSocketFactory?: WebSocketFactory;
24
51
  /**
25
52
  * The maximum number of connection attempts to make during the {@link IEventBus} initial connection request.
26
53
  * If the limit is reached the {@link IEventBus} will return an error to the caller of {@link IEventBus#connect}
@@ -28,10 +55,11 @@ declare class ConnectionInfo extends ServerInfo {
28
55
  */
29
56
  maxConnectionAttempts?: number | null;
30
57
  /**
31
- * If true, the session will not be kept alive after the connection is established and then disrupted.
32
- * If false, the session will be kept alive after the connection is established and then disrupted, for a period of time.
58
+ * Controls whether session expiration is extended by gateway activity or by an active websocket connection.
59
+ * Defaults to {@link SessionKeepAliveMode.ACTIVITY}.
60
+ * Use {@link SessionKeepAliveMode.NONE} to remove the session when the websocket connection closes.
33
61
  */
34
- disableStickySession?: boolean | null;
62
+ sessionKeepAlive: SessionKeepAliveMode;
35
63
  }
36
64
  import { Optional } from "typescript-optional";
37
65
  import { Observable } from "rxjs";
@@ -100,7 +128,6 @@ declare class Participant implements IParticipant {
100
128
  * Contains information about the connection that was established
101
129
  */
102
130
  declare class ConnectedInfo {
103
- sessionId: string;
104
131
  replyToId: string;
105
132
  participant: Participant;
106
133
  }
@@ -253,22 +280,13 @@ declare enum EventConstants {
253
280
  CONTENT_LENGTH_HEADER = "content-length",
254
281
  REPLY_TO_HEADER = "reply-to",
255
282
  /**
256
- * This is the replyToId that will be supplied by the client, which will be used when sending replies to the client.
257
- */
258
- REPLY_TO_ID_HEADER = "reply-to-id",
259
- /**
260
- * Header provided by the sever on connection to represent the user's session id
261
- */
262
- SESSION_HEADER = "session",
263
- /**
264
283
  * Header provided by the server on connection to provide the {@link ConnectionInfo} as a JSON string
265
284
  */
266
285
  CONNECTED_INFO_HEADER = "connected-info",
267
286
  /**
268
- * Header provided by the client on connection request to represent that the server
269
- * should not keep the session alive after any network disconnection.
287
+ * Header provided by the client on connection request to choose how the session is kept alive.
270
288
  */
271
- DISABLE_STICKY_SESSION_HEADER = "disable-sticky-session",
289
+ SESSION_KEEP_ALIVE_HEADER = "session-keep-alive",
272
290
  /**
273
291
  * Correlates a response with a given request
274
292
  * Headers that start with __ will always be persisted between messages
@@ -297,6 +315,8 @@ declare enum EventConstants {
297
315
  SERVICE_DESTINATION_SCHEME = "srv",
298
316
  STREAM_DESTINATION_PREFIX = "stream://",
299
317
  STREAM_DESTINATION_SCHEME = "stream",
318
+ REPLY_DESTINATION_PREFIX = "reply://",
319
+ REPLY_DESTINATION_SCHEME = "reply",
300
320
  CONTENT_JSON = "application/json",
301
321
  CONTENT_TEXT = "text/plain",
302
322
  /**
@@ -1094,6 +1114,12 @@ declare class EventBus implements IEventBus {
1094
1114
  observe(cri: string): Observable3<IEvent>;
1095
1115
  private cleanup;
1096
1116
  /**
1117
+ * Tears down the shared request-replies stream so the next request rebuilds it against the
1118
+ * current {@link replyToCri}. Any in-flight requests are failed with the given reason since
1119
+ * their replies can no longer be delivered.
1120
+ */
1121
+ private resetRequestReplies;
1122
+ /**
1097
1123
  * Creates the proper error to return if this.stompConnectionManager?.rxStomp is not available on a send request
1098
1124
  */
1099
1125
  private createSendUnavailableError;
@@ -1117,4 +1143,4 @@ declare class ParticipantConstants {
1117
1143
  static readonly PARTICIPANT_TYPE_NODE: string;
1118
1144
  static readonly CLI_PARTICIPANT_ID: string;
1119
1145
  }
1120
- export { createCRI, Version, TextEventFactory, Sort, ServiceRegistry, ServiceContext, ServerInfo, Scope, Publish, ParticipantConstants, Participant, Pageable, Page, Order, OffsetPageable, NullHandling, KinoticSingleton, KinoticPlugin, KinoticError, Kinotic, JsonEventFactory, IterablePage, Identifiable, IServiceRegistry, IServiceProxy, IParticipant, IKinotic, IEventFactory, IEventBus, IEvent, IEditableDataSource, IDataSource, ICrudServiceProxyFactory, ICrudServiceProxy, FunctionalIterablePage, EventConstants, EventBus, Event, Direction, DefaultCRI, DataSourceUtils, CursorPageable, CrudServiceProxyFactory, CrudServiceProxy, ContextInterceptor, Context, ConnectionInfo, ConnectedInfo, ConnectHeaders, CRI, CONTEXT_METADATA_KEY, AuthorizationError, AuthenticationError, AbstractIterablePage };
1146
+ export { createCRI, WebSocketFactory, Version, TextEventFactory, Sort, SessionKeepAliveMode, ServiceRegistry, ServiceContext, ServerInfo, Scope, Publish, ParticipantConstants, Participant, Pageable, Page, Order, OffsetPageable, NullHandling, KinoticSingleton, KinoticPlugin, KinoticError, Kinotic, JsonEventFactory, IterablePage, Identifiable, IWebSocket, IServiceRegistry, IServiceProxy, IParticipant, IKinotic, IEventFactory, IEventBus, IEvent, IEditableDataSource, IDataSource, ICrudServiceProxyFactory, ICrudServiceProxy, FunctionalIterablePage, EventConstants, EventBus, Event, Direction, DefaultCRI, DataSourceUtils, CursorPageable, CrudServiceProxyFactory, CrudServiceProxy, ContextInterceptor, Context, ConnectionInfo, ConnectedInfo, CRI, CONTEXT_METADATA_KEY, AuthorizationError, AuthenticationError, AbstractIterablePage };
package/dist/index.d.ts CHANGED
@@ -1,26 +1,53 @@
1
1
  /**
2
- * ConnectHeaders to use during connection to the kinoitc server
3
- * These headers will be sent as part of the STOMP CONNECT frame
4
- * This is typically used for authentication information, but any data can be sent
2
+ * Structural shape of a WebSocket used by the underlying STOMP client.
3
+ * Copied from the WebSocket interface to avoid pulling in the DOM typelib,
4
+ * so this type stays usable in Node environments where `lib: dom` is not set.
5
5
  */
6
- declare class ConnectHeaders {
7
- [key: string]: string;
6
+ interface IWebSocket {
7
+ url: string;
8
+ binaryType?: string;
9
+ readyState: number;
10
+ onopen: ((ev?: any) => any) | undefined | null;
11
+ onclose: ((ev?: any) => any) | undefined | null;
12
+ onerror: ((ev: any) => any) | undefined | null;
13
+ onmessage: ((ev: any) => any) | undefined | null;
14
+ close(code?: number, reason?: string): void;
15
+ send(data: string | ArrayBuffer): void;
8
16
  }
17
+ /**
18
+ * Factory invoked on every (re)connect to produce the WebSocket the STOMP
19
+ * client will use. Supply this in Node when you need to set headers on the
20
+ * upgrade request (for example, an Authorization header). Browser callers
21
+ * normally leave this unset and rely on the session cookie established by a
22
+ * prior REST login.
23
+ */
24
+ type WebSocketFactory = () => IWebSocket;
9
25
  declare class ServerInfo {
10
26
  host: string;
11
27
  port?: number | null;
12
28
  useSSL?: boolean | null;
13
29
  }
30
+ declare enum SessionKeepAliveMode {
31
+ NONE = "NONE",
32
+ ACTIVITY = "ACTIVITY",
33
+ CONNECTION = "CONNECTION"
34
+ }
14
35
  /**
15
- * ConnectionInfo provides the information needed to connect to the kinoitc server
36
+ * ConnectionInfo provides the information needed to connect to the kinoitc server.
37
+ *
38
+ * Authentication is performed during the WebSocket upgrade (handshake), not in
39
+ * the STOMP CONNECT frame. In the browser, log in via the REST endpoints first
40
+ * and the established session cookie will be used. In Node, supply a
41
+ * {@link WebSocketFactory} that attaches the required upgrade headers.
16
42
  */
17
43
  declare class ConnectionInfo extends ServerInfo {
18
44
  /**
19
- * The headers to send during the connection to the kinoitc server.
20
- * If a function is provided, it will be called to get the headers each time the connection is established.
21
- * This is useful for providing dynamic headers, such as a JWT token that expires.
45
+ * Optional factory used to create the underlying WebSocket. Use this in
46
+ * Node to attach custom headers (such as Authorization) to the upgrade
47
+ * request. If omitted, a default WebSocket is created and authentication
48
+ * is expected to come from the session cookie.
22
49
  */
23
- connectHeaders?: ConnectHeaders | (() => Promise<ConnectHeaders>);
50
+ webSocketFactory?: WebSocketFactory;
24
51
  /**
25
52
  * The maximum number of connection attempts to make during the {@link IEventBus} initial connection request.
26
53
  * If the limit is reached the {@link IEventBus} will return an error to the caller of {@link IEventBus#connect}
@@ -28,10 +55,11 @@ declare class ConnectionInfo extends ServerInfo {
28
55
  */
29
56
  maxConnectionAttempts?: number | null;
30
57
  /**
31
- * If true, the session will not be kept alive after the connection is established and then disrupted.
32
- * If false, the session will be kept alive after the connection is established and then disrupted, for a period of time.
58
+ * Controls whether session expiration is extended by gateway activity or by an active websocket connection.
59
+ * Defaults to {@link SessionKeepAliveMode.ACTIVITY}.
60
+ * Use {@link SessionKeepAliveMode.NONE} to remove the session when the websocket connection closes.
33
61
  */
34
- disableStickySession?: boolean | null;
62
+ sessionKeepAlive: SessionKeepAliveMode;
35
63
  }
36
64
  import { Optional } from "typescript-optional";
37
65
  import { Observable } from "rxjs";
@@ -100,7 +128,6 @@ declare class Participant implements IParticipant {
100
128
  * Contains information about the connection that was established
101
129
  */
102
130
  declare class ConnectedInfo {
103
- sessionId: string;
104
131
  replyToId: string;
105
132
  participant: Participant;
106
133
  }
@@ -253,22 +280,13 @@ declare enum EventConstants {
253
280
  CONTENT_LENGTH_HEADER = "content-length",
254
281
  REPLY_TO_HEADER = "reply-to",
255
282
  /**
256
- * This is the replyToId that will be supplied by the client, which will be used when sending replies to the client.
257
- */
258
- REPLY_TO_ID_HEADER = "reply-to-id",
259
- /**
260
- * Header provided by the sever on connection to represent the user's session id
261
- */
262
- SESSION_HEADER = "session",
263
- /**
264
283
  * Header provided by the server on connection to provide the {@link ConnectionInfo} as a JSON string
265
284
  */
266
285
  CONNECTED_INFO_HEADER = "connected-info",
267
286
  /**
268
- * Header provided by the client on connection request to represent that the server
269
- * should not keep the session alive after any network disconnection.
287
+ * Header provided by the client on connection request to choose how the session is kept alive.
270
288
  */
271
- DISABLE_STICKY_SESSION_HEADER = "disable-sticky-session",
289
+ SESSION_KEEP_ALIVE_HEADER = "session-keep-alive",
272
290
  /**
273
291
  * Correlates a response with a given request
274
292
  * Headers that start with __ will always be persisted between messages
@@ -297,6 +315,8 @@ declare enum EventConstants {
297
315
  SERVICE_DESTINATION_SCHEME = "srv",
298
316
  STREAM_DESTINATION_PREFIX = "stream://",
299
317
  STREAM_DESTINATION_SCHEME = "stream",
318
+ REPLY_DESTINATION_PREFIX = "reply://",
319
+ REPLY_DESTINATION_SCHEME = "reply",
300
320
  CONTENT_JSON = "application/json",
301
321
  CONTENT_TEXT = "text/plain",
302
322
  /**
@@ -1094,6 +1114,12 @@ declare class EventBus implements IEventBus {
1094
1114
  observe(cri: string): Observable3<IEvent>;
1095
1115
  private cleanup;
1096
1116
  /**
1117
+ * Tears down the shared request-replies stream so the next request rebuilds it against the
1118
+ * current {@link replyToCri}. Any in-flight requests are failed with the given reason since
1119
+ * their replies can no longer be delivered.
1120
+ */
1121
+ private resetRequestReplies;
1122
+ /**
1097
1123
  * Creates the proper error to return if this.stompConnectionManager?.rxStomp is not available on a send request
1098
1124
  */
1099
1125
  private createSendUnavailableError;
@@ -1117,4 +1143,4 @@ declare class ParticipantConstants {
1117
1143
  static readonly PARTICIPANT_TYPE_NODE: string;
1118
1144
  static readonly CLI_PARTICIPANT_ID: string;
1119
1145
  }
1120
- export { createCRI, Version, TextEventFactory, Sort, ServiceRegistry, ServiceContext, ServerInfo, Scope, Publish, ParticipantConstants, Participant, Pageable, Page, Order, OffsetPageable, NullHandling, KinoticSingleton, KinoticPlugin, KinoticError, Kinotic, JsonEventFactory, IterablePage, Identifiable, IServiceRegistry, IServiceProxy, IParticipant, IKinotic, IEventFactory, IEventBus, IEvent, IEditableDataSource, IDataSource, ICrudServiceProxyFactory, ICrudServiceProxy, FunctionalIterablePage, EventConstants, EventBus, Event, Direction, DefaultCRI, DataSourceUtils, CursorPageable, CrudServiceProxyFactory, CrudServiceProxy, ContextInterceptor, Context, ConnectionInfo, ConnectedInfo, ConnectHeaders, CRI, CONTEXT_METADATA_KEY, AuthorizationError, AuthenticationError, AbstractIterablePage };
1146
+ export { createCRI, WebSocketFactory, Version, TextEventFactory, Sort, SessionKeepAliveMode, ServiceRegistry, ServiceContext, ServerInfo, Scope, Publish, ParticipantConstants, Participant, Pageable, Page, Order, OffsetPageable, NullHandling, KinoticSingleton, KinoticPlugin, KinoticError, Kinotic, JsonEventFactory, IterablePage, Identifiable, IWebSocket, IServiceRegistry, IServiceProxy, IParticipant, IKinotic, IEventFactory, IEventBus, IEvent, IEditableDataSource, IDataSource, ICrudServiceProxyFactory, ICrudServiceProxy, FunctionalIterablePage, EventConstants, EventBus, Event, Direction, DefaultCRI, DataSourceUtils, CursorPageable, CrudServiceProxyFactory, CrudServiceProxy, ContextInterceptor, Context, ConnectionInfo, ConnectedInfo, CRI, CONTEXT_METADATA_KEY, AuthorizationError, AuthenticationError, AbstractIterablePage };
package/dist/index.js CHANGED
@@ -2,19 +2,22 @@ import { createRequire } from "node:module";
2
2
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
3
 
4
4
  // packages/core/src/api/ConnectionInfo.ts
5
- class ConnectHeaders {
6
- }
7
-
8
5
  class ServerInfo {
9
6
  host;
10
7
  port;
11
8
  useSSL;
12
9
  }
10
+ var SessionKeepAliveMode;
11
+ ((SessionKeepAliveMode2) => {
12
+ SessionKeepAliveMode2["NONE"] = "NONE";
13
+ SessionKeepAliveMode2["ACTIVITY"] = "ACTIVITY";
14
+ SessionKeepAliveMode2["CONNECTION"] = "CONNECTION";
15
+ })(SessionKeepAliveMode ||= {});
13
16
 
14
17
  class ConnectionInfo extends ServerInfo {
15
- connectHeaders;
18
+ webSocketFactory;
16
19
  maxConnectionAttempts;
17
- disableStickySession;
20
+ sessionKeepAlive = "ACTIVITY" /* ACTIVITY */;
18
21
  }
19
22
  // packages/core/src/api/errors/KinoticError.ts
20
23
  class KinoticError extends Error {
@@ -30,10 +33,8 @@ var EventConstants;
30
33
  EventConstants2["CONTENT_TYPE_HEADER"] = "content-type";
31
34
  EventConstants2["CONTENT_LENGTH_HEADER"] = "content-length";
32
35
  EventConstants2["REPLY_TO_HEADER"] = "reply-to";
33
- EventConstants2["REPLY_TO_ID_HEADER"] = "reply-to-id";
34
- EventConstants2["SESSION_HEADER"] = "session";
35
36
  EventConstants2["CONNECTED_INFO_HEADER"] = "connected-info";
36
- EventConstants2["DISABLE_STICKY_SESSION_HEADER"] = "disable-sticky-session";
37
+ EventConstants2["SESSION_KEEP_ALIVE_HEADER"] = "session-keep-alive";
37
38
  EventConstants2["CORRELATION_ID_HEADER"] = "__correlation-id";
38
39
  EventConstants2["ERROR_HEADER"] = "error";
39
40
  EventConstants2["COMPLETE_HEADER"] = "complete";
@@ -46,6 +47,8 @@ var EventConstants;
46
47
  EventConstants2["SERVICE_DESTINATION_SCHEME"] = "srv";
47
48
  EventConstants2["STREAM_DESTINATION_PREFIX"] = "stream://";
48
49
  EventConstants2["STREAM_DESTINATION_SCHEME"] = "stream";
50
+ EventConstants2["REPLY_DESTINATION_PREFIX"] = "reply://";
51
+ EventConstants2["REPLY_DESTINATION_SCHEME"] = "reply";
49
52
  EventConstants2["CONTENT_JSON"] = "application/json";
50
53
  EventConstants2["CONTENT_TEXT"] = "text/plain";
51
54
  EventConstants2["TRACEPARENT_HEADER"] = "traceparent";
@@ -55,8 +58,8 @@ var EventConstants;
55
58
  // packages/core/src/internal/api/event/StompConnectionManager.ts
56
59
  import { RxStomp } from "@stomp/rx-stomp";
57
60
  import { ReconnectionTimeMode } from "@stomp/stompjs";
58
- import { v4 as uuidv4 } from "uuid";
59
61
  import debug from "debug";
62
+ import { v4 as uuidv4 } from "uuid";
60
63
 
61
64
  class StompConnectionManager {
62
65
  lastWebsocketError = null;
@@ -69,9 +72,10 @@ class StompConnectionManager {
69
72
  initialConnectionSuccessful = false;
70
73
  debugLogger = debug("kinoitc:stomp");
71
74
  uuidv4 = uuidv4();
72
- replyToId = uuidv4();
73
- _replyToCri = "srv://" /* SERVICE_DESTINATION_PREFIX */ + this.replyToId + ":" + this.uuidv4 + "@kinoitc.js.EventBus/replyHandler";
75
+ _replyToCri = null;
76
+ serverHeadersSubscription = null;
74
77
  deactivationHandler = null;
78
+ replyToCriChangedHandler = null;
75
79
  get active() {
76
80
  return !!this.rxStomp;
77
81
  }
@@ -99,31 +103,21 @@ class StompConnectionManager {
99
103
  this.initialConnectionSuccessful = false;
100
104
  this.lastWebsocketError = null;
101
105
  this.maxConnectionAttemptsReached = false;
106
+ this._replyToCri = null;
107
+ this.serverHeadersSubscription?.unsubscribe();
108
+ this.serverHeadersSubscription = null;
102
109
  const url = "ws" + (connectionInfo.useSSL ? "s" : "") + "://" + connectionInfo.host + (connectionInfo.port ? ":" + connectionInfo.port : "") + "/v1";
103
110
  this.rxStomp = new RxStomp;
104
- let connectHeadersInternal = typeof connectionInfo.connectHeaders !== "function" && connectionInfo.connectHeaders != null ? connectionInfo.connectHeaders : {};
105
111
  const stompConfig = {
106
112
  brokerURL: url,
107
- connectHeaders: connectHeadersInternal,
113
+ connectHeaders: {
114
+ ["session-keep-alive" /* SESSION_KEEP_ALIVE_HEADER */]: connectionInfo.sessionKeepAlive
115
+ },
108
116
  heartbeatIncoming: 120000,
109
117
  heartbeatOutgoing: 30000,
110
118
  reconnectDelay: this.INITIAL_RECONNECT_DELAY,
119
+ webSocketFactory: connectionInfo.webSocketFactory,
111
120
  beforeConnect: async () => {
112
- if (typeof connectionInfo.connectHeaders === "function") {
113
- const headers = await connectionInfo.connectHeaders();
114
- for (const key in headers) {
115
- connectHeadersInternal[key] = headers[key];
116
- }
117
- }
118
- if (connectionInfo.disableStickySession) {
119
- connectHeadersInternal["disable-sticky-session" /* DISABLE_STICKY_SESSION_HEADER */] = "true";
120
- }
121
- if (connectHeadersInternal["reply-to-id" /* REPLY_TO_ID_HEADER */]) {
122
- this.replyToId = connectHeadersInternal["reply-to-id" /* REPLY_TO_ID_HEADER */];
123
- this._replyToCri = "srv://" /* SERVICE_DESTINATION_PREFIX */ + this.replyToId + ":" + this.uuidv4 + "@kinoitc.js.EventBus/replyHandler";
124
- } else {
125
- connectHeadersInternal["reply-to-id" /* REPLY_TO_ID_HEADER */] = this.replyToId;
126
- }
127
121
  if (connectionInfo?.maxConnectionAttempts) {
128
122
  this.connectionAttempts++;
129
123
  if (this.connectionAttempts > connectionInfo.maxConnectionAttempts) {
@@ -165,36 +159,29 @@ class StompConnectionManager {
165
159
  this.rxStomp = null;
166
160
  reject(message);
167
161
  });
168
- const serverHeadersSubscription = this.rxStomp.serverHeaders$.subscribe((value) => {
169
- let connectedInfoJson = value["connected-info" /* CONNECTED_INFO_HEADER */];
170
- if (connectedInfoJson != null) {
171
- const connectedInfo = JSON.parse(connectedInfoJson);
172
- if (!connectionInfo.disableStickySession) {
173
- serverHeadersSubscription.unsubscribe();
174
- if (connectedInfo.sessionId != null && connectedInfo.replyToId != null) {
175
- if (connectionInfo.connectHeaders != null) {
176
- for (let key in connectHeadersInternal) {
177
- delete connectHeadersInternal[key];
178
- }
179
- }
180
- connectHeadersInternal["session" /* SESSION_HEADER */] = connectedInfo.sessionId;
181
- resolve(connectedInfo);
182
- } else {
183
- reject("Server did not return proper data for successful login");
184
- }
185
- } else if (typeof connectionInfo.connectHeaders === "function") {
186
- for (let key in connectHeadersInternal) {
187
- delete connectHeadersInternal[key];
188
- }
189
- if (!this.initialConnectionSuccessful) {
190
- resolve(connectedInfo);
191
- }
192
- } else if (typeof connectionInfo.connectHeaders === "object") {
193
- serverHeadersSubscription.unsubscribe();
194
- resolve(connectedInfo);
162
+ this.serverHeadersSubscription = this.rxStomp.serverHeaders$.subscribe((value) => {
163
+ const connectedInfoJson = value["connected-info" /* CONNECTED_INFO_HEADER */];
164
+ const firstConnect = this._replyToCri == null;
165
+ if (connectedInfoJson == null) {
166
+ if (firstConnect) {
167
+ reject("Server did not return proper data for successful login");
195
168
  }
196
- } else {
197
- reject("Server did not return proper data for successful login");
169
+ return;
170
+ }
171
+ const connectedInfo = JSON.parse(connectedInfoJson);
172
+ if (connectedInfo.replyToId == null) {
173
+ if (firstConnect) {
174
+ reject("Server did not return a replyToId for successful login");
175
+ }
176
+ return;
177
+ }
178
+ const newReplyToCri = "reply://" /* REPLY_DESTINATION_PREFIX */ + connectedInfo.replyToId + ":" + this.uuidv4 + "@kinoitc.js.EventBus/replyHandler";
179
+ if (firstConnect) {
180
+ this._replyToCri = newReplyToCri;
181
+ resolve(connectedInfo);
182
+ } else if (this._replyToCri !== newReplyToCri) {
183
+ this._replyToCri = newReplyToCri;
184
+ this.replyToCriChangedHandler?.(newReplyToCri);
198
185
  }
199
186
  });
200
187
  this.rxStomp.activate();
@@ -203,6 +190,8 @@ class StompConnectionManager {
203
190
  async deactivate(force) {
204
191
  if (this.rxStomp) {
205
192
  await this.rxStomp.deactivate({ force });
193
+ this.serverHeadersSubscription?.unsubscribe();
194
+ this.serverHeadersSubscription = null;
206
195
  if (this.deactivationHandler) {
207
196
  this.deactivationHandler();
208
197
  }
@@ -283,6 +272,10 @@ class EventBus {
283
272
  this.stompConnectionManager.deactivationHandler = () => {
284
273
  this.cleanup();
285
274
  };
275
+ this.stompConnectionManager.replyToCriChangedHandler = (replyToCri) => {
276
+ this.replyToCri = replyToCri;
277
+ this.resetRequestReplies("Reply destination changed");
278
+ };
286
279
  }
287
280
  isConnectionActive() {
288
281
  return this.stompConnectionManager.active;
@@ -350,7 +343,7 @@ class EventBus {
350
343
  })).subscribe({
351
344
  next(value) {
352
345
  if (value.hasHeader("control" /* CONTROL_HEADER */)) {
353
- if (value.headers.get("control" /* CONTROL_HEADER */) === "complete") {
346
+ if (value.headers.get("control" /* CONTROL_HEADER */) === "complete" /* CONTROL_VALUE_COMPLETE */) {
354
347
  serverSignaledCompletion = true;
355
348
  subscriber.complete();
356
349
  } else {
@@ -394,8 +387,16 @@ class EventBus {
394
387
  return this._observe(cri);
395
388
  }
396
389
  cleanup() {
390
+ this.resetRequestReplies("Connection disconnected");
391
+ if (this.errorSubjectSubscription) {
392
+ this.errorSubjectSubscription.unsubscribe();
393
+ this.errorSubjectSubscription = null;
394
+ }
395
+ this.serverInfo = null;
396
+ }
397
+ resetRequestReplies(reason) {
397
398
  if (this.requestRepliesSubject != null) {
398
- this.requestRepliesSubject.error(new Error("Connection disconnected"));
399
+ this.requestRepliesSubject.error(new Error(reason));
399
400
  if (this.requestRepliesSubscription != null) {
400
401
  this.requestRepliesSubscription.unsubscribe();
401
402
  this.requestRepliesSubscription = null;
@@ -403,11 +404,6 @@ class EventBus {
403
404
  this.requestRepliesSubject = null;
404
405
  this.requestRepliesObservable = null;
405
406
  }
406
- if (this.errorSubjectSubscription) {
407
- this.errorSubjectSubscription.unsubscribe();
408
- this.errorSubjectSubscription = null;
409
- }
410
- this.serverInfo = null;
411
407
  }
412
408
  createSendUnavailableError() {
413
409
  let ret = "You must call connect on the event bus before sending any request";
@@ -894,7 +890,7 @@ import { first, map as map2 } from "rxjs/operators";
894
890
  // packages/core/package.json
895
891
  var package_default = {
896
892
  name: "@kinotic-ai/core",
897
- version: "1.2.2",
893
+ version: "1.3.0",
898
894
  type: "module",
899
895
  files: [
900
896
  "dist"
@@ -1388,7 +1384,6 @@ class AuthorizationError extends KinoticError {
1388
1384
  }
1389
1385
  // packages/core/src/api/security/ConnectedInfo.ts
1390
1386
  class ConnectedInfo {
1391
- sessionId;
1392
1387
  replyToId;
1393
1388
  participant;
1394
1389
  }
@@ -1423,6 +1418,7 @@ export {
1423
1418
  Version,
1424
1419
  TextEventFactory,
1425
1420
  Sort,
1421
+ SessionKeepAliveMode,
1426
1422
  ServiceRegistry,
1427
1423
  ServerInfo,
1428
1424
  Scope,
@@ -1450,7 +1446,6 @@ export {
1450
1446
  Context,
1451
1447
  ConnectionInfo,
1452
1448
  ConnectedInfo,
1453
- ConnectHeaders,
1454
1449
  CONTEXT_METADATA_KEY,
1455
1450
  AuthorizationError,
1456
1451
  AuthenticationError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kinotic-ai/core",
3
- "version": "1.2.2",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"