@sogni-ai/sogni-client 4.0.0-alpha.19 → 4.0.0-alpha.20

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.
Files changed (24) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/ChannelCoordinator.d.ts +66 -0
  3. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/ChannelCoordinator.js +332 -0
  4. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/ChannelCoordinator.js.map +1 -0
  5. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/index.d.ts +3 -9
  6. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/index.js +104 -100
  7. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/index.js.map +1 -1
  8. package/dist/ApiClient/WebSocketClient/index.js +2 -2
  9. package/dist/ApiClient/WebSocketClient/index.js.map +1 -1
  10. package/dist/ApiClient/index.js +1 -1
  11. package/dist/ApiClient/index.js.map +1 -1
  12. package/package.json +1 -1
  13. package/src/ApiClient/WebSocketClient/BrowserWebSocketClient/ChannelCoordinator.ts +426 -0
  14. package/src/ApiClient/WebSocketClient/BrowserWebSocketClient/index.ts +127 -98
  15. package/src/ApiClient/WebSocketClient/index.ts +2 -2
  16. package/src/ApiClient/index.ts +1 -1
  17. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/WSCoordinator.d.ts +0 -102
  18. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/WSCoordinator.js +0 -378
  19. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/WSCoordinator.js.map +0 -1
  20. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/types.d.ts +0 -102
  21. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/types.js +0 -3
  22. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/types.js.map +0 -1
  23. package/src/ApiClient/WebSocketClient/BrowserWebSocketClient/WSCoordinator.ts +0 -443
  24. package/src/ApiClient/WebSocketClient/BrowserWebSocketClient/types.ts +0 -107
@@ -3,10 +3,41 @@ import { AuthManager, TokenAuthManager } from '../../../lib/AuthManager';
3
3
  import { Logger } from '../../../lib/DefaultLogger';
4
4
  import WebSocketClient from '../index';
5
5
  import RestClient from '../../../lib/RestClient';
6
- import { ServerDisconnectData, SocketEventMap } from '../events';
7
- import WSCoordinator from './WSCoordinator';
6
+ import { SocketEventMap } from '../events';
8
7
  import { MessageType, SocketMessageMap } from '../messages';
9
- import { Heartbeat, SocketEventReceived, SendSocketMessage } from './types';
8
+ import ChannelCoordinator from './ChannelCoordinator';
9
+
10
+ interface SocketSend<T extends MessageType = MessageType> {
11
+ type: 'socket-send';
12
+ payload: { type: T; data: SocketMessageMap[T] };
13
+ }
14
+
15
+ interface SocketConnect {
16
+ type: 'connect';
17
+ }
18
+
19
+ interface SocketDisconnect {
20
+ type: 'disconnect';
21
+ }
22
+
23
+ interface SwitchNetwork {
24
+ type: 'switchNetwork';
25
+ payload: SupernetType;
26
+ }
27
+
28
+ type Message = SocketConnect | SocketDisconnect | SocketSend | SwitchNetwork;
29
+
30
+ interface EventNotification<T extends keyof SocketEventMap = keyof SocketEventMap> {
31
+ type: 'socket-event';
32
+ payload: { type: T; data: SocketEventMap[T] };
33
+ }
34
+
35
+ interface AuthStateChanged {
36
+ type: 'auth-state-changed';
37
+ payload: boolean;
38
+ }
39
+
40
+ type Notification = EventNotification | AuthStateChanged;
10
41
 
11
42
  type EventInterceptor<T extends keyof SocketEventMap = keyof SocketEventMap> = (
12
43
  eventType: T,
@@ -30,8 +61,7 @@ class BrowserWebSocketClient extends RestClient<SocketEventMap> implements IWebS
30
61
  appId: string;
31
62
  baseUrl: string;
32
63
  private socketClient: WrappedClient;
33
- private coordinator: WSCoordinator;
34
- private isPrimary = false;
64
+ private coordinator: ChannelCoordinator<Message, Notification>;
35
65
  private _isConnected = false;
36
66
  private _supernetType: SupernetType;
37
67
 
@@ -48,67 +78,125 @@ class BrowserWebSocketClient extends RestClient<SocketEventMap> implements IWebS
48
78
  this.appId = appId;
49
79
  this.baseUrl = socketClient.baseUrl;
50
80
  this._supernetType = supernetType;
51
- this.coordinator = new WSCoordinator(
52
- {
53
- onAuthChanged: this.handleAuthChanged.bind(this),
81
+ this.coordinator = new ChannelCoordinator({
82
+ callbacks: {
54
83
  onRoleChange: this.handleRoleChange.bind(this),
55
- onConnectionToggle: this.handleConnectionToggle.bind(this),
56
- onMessageFromPrimary: this.handleMessageFromPrimary.bind(this),
57
- onSendRequest: this.handleSendRequest.bind(this)
84
+ onMessage: this.handleMessage.bind(this),
85
+ onNotification: this.handleNotification.bind(this)
58
86
  },
59
87
  logger
60
- );
88
+ });
61
89
  this.auth.on('updated', this.handleAuthUpdated.bind(this));
62
90
  this.socketClient.intercept(this.handleSocketEvent.bind(this));
91
+ //@ts-expect-error window is defined in browser
92
+ window.DISCONNECT = () => {
93
+ this.disconnect();
94
+ };
63
95
  }
64
96
 
65
97
  get isConnected() {
66
- return this.isPrimary ? this.socketClient.isConnected : this._isConnected;
98
+ return this.coordinator.isPrimary ? this.socketClient.isConnected : this._isConnected;
67
99
  }
68
100
 
69
101
  get supernetType() {
70
- return this.isPrimary ? this.socketClient.supernetType : this._supernetType;
102
+ return this.coordinator.isPrimary ? this.socketClient.supernetType : this._supernetType;
71
103
  }
72
104
 
73
105
  async connect(): Promise<void> {
74
- const isPrimary = await this.coordinator.initialize();
75
- this.isPrimary = isPrimary;
76
- if (isPrimary) {
106
+ await this.coordinator.isReady();
107
+ if (this.coordinator.isPrimary) {
77
108
  await this.socketClient.connect();
78
109
  } else {
79
- this.coordinator.connect();
110
+ return this.coordinator.sendMessage({
111
+ type: 'connect'
112
+ });
80
113
  }
81
114
  }
82
115
 
83
- disconnect() {
84
- if (this.isPrimary) {
116
+ async disconnect() {
117
+ await this.coordinator.isReady();
118
+ if (this.coordinator.isPrimary) {
85
119
  this.socketClient.disconnect();
86
120
  } else {
87
- this.coordinator.disconnect();
121
+ this.coordinator.sendMessage({
122
+ type: 'disconnect'
123
+ });
88
124
  }
89
125
  }
90
126
 
91
127
  async switchNetwork(supernetType: SupernetType): Promise<SupernetType> {
92
- if (this.isPrimary) {
128
+ await this.coordinator.isReady();
129
+ if (this.coordinator.isPrimary) {
93
130
  return this.socketClient.switchNetwork(supernetType);
94
131
  }
95
- return new Promise<SupernetType>(async (resolve) => {
96
- this.once('changeNetwork', ({ network }) => {
97
- this._supernetType = network;
98
- resolve(network);
99
- });
100
- await this.send('changeNetwork', supernetType);
132
+ await this.coordinator.sendMessage({
133
+ type: 'switchNetwork',
134
+ payload: supernetType
101
135
  });
136
+ this._supernetType = supernetType;
137
+ return supernetType;
102
138
  }
103
139
 
104
140
  async send<T extends MessageType>(messageType: T, data: SocketMessageMap[T]): Promise<void> {
105
- if (this.isPrimary) {
141
+ await this.coordinator.isReady();
142
+ if (this.coordinator.isPrimary) {
106
143
  if (!this.socketClient.isConnected) {
107
144
  await this.socketClient.connect();
108
145
  }
109
146
  return this.socketClient.send(messageType, data);
110
147
  }
111
- return this.coordinator.sendToSocket(messageType, data);
148
+ return this.coordinator.sendMessage({
149
+ type: 'socket-send',
150
+ payload: { type: messageType, data }
151
+ });
152
+ }
153
+
154
+ private async handleMessage(message: Message) {
155
+ this._logger.debug('Received control message', message);
156
+ switch (message.type) {
157
+ case 'socket-send': {
158
+ if (!this.socketClient.isConnected) {
159
+ await this.socketClient.connect();
160
+ }
161
+ return this.socketClient.send(message.payload.type, message.payload.data);
162
+ }
163
+ case 'connect': {
164
+ if (!this.socketClient.isConnected) {
165
+ await this.socketClient.connect();
166
+ }
167
+ return;
168
+ }
169
+ case 'disconnect': {
170
+ if (this.socketClient.isConnected) {
171
+ this.socketClient.disconnect();
172
+ }
173
+ return;
174
+ }
175
+ case 'switchNetwork': {
176
+ await this.switchNetwork(message.payload);
177
+ return;
178
+ }
179
+ default: {
180
+ this._logger.error('Received unknown message type:', message);
181
+ }
182
+ }
183
+ }
184
+
185
+ private async handleNotification(notification: Notification) {
186
+ this._logger.debug('Received notification', notification.type, notification.payload);
187
+ switch (notification.type) {
188
+ case 'socket-event': {
189
+ this.emit(notification.payload.type, notification.payload.data);
190
+ return;
191
+ }
192
+ case 'auth-state-changed': {
193
+ this.handleAuthChanged(notification.payload);
194
+ return;
195
+ }
196
+ default: {
197
+ this._logger.error('Received unknown notification type:', notification);
198
+ }
199
+ }
112
200
  }
113
201
 
114
202
  private handleAuthChanged(isAuthenticated: boolean) {
@@ -125,88 +213,29 @@ class BrowserWebSocketClient extends RestClient<SocketEventMap> implements IWebS
125
213
  }
126
214
 
127
215
  private handleSocketEvent(eventType: keyof SocketEventMap, payload: any) {
128
- if (this.isPrimary) {
129
- this.coordinator.broadcastSocketEvent(eventType, payload);
216
+ if (this.coordinator.isPrimary) {
217
+ this.coordinator.notify({
218
+ type: 'socket-event',
219
+ payload: { type: eventType, data: payload }
220
+ });
130
221
  this.emit(eventType, payload);
131
222
  }
132
223
  }
133
224
 
134
225
  private handleAuthUpdated(isAuthenticated: boolean) {
135
- this.coordinator.changeAuthState(isAuthenticated);
226
+ this.coordinator.notify({
227
+ type: 'auth-state-changed',
228
+ payload: isAuthenticated
229
+ });
136
230
  }
137
231
 
138
232
  private handleRoleChange(isPrimary: boolean) {
139
- this.isPrimary = isPrimary;
140
233
  if (isPrimary && !this.socketClient.isConnected && this.isConnected) {
141
234
  this.socketClient.connect();
142
235
  } else if (!isPrimary && this.socketClient.isConnected) {
143
236
  this.socketClient.disconnect();
144
237
  }
145
238
  }
146
-
147
- private handleConnectionToggle(isConnected: boolean) {
148
- if (this.isPrimary) {
149
- if (isConnected && !this.socketClient.isConnected) {
150
- this.socketClient.connect();
151
- } else if (!isConnected && this.socketClient.isConnected) {
152
- this.socketClient.disconnect();
153
- }
154
- }
155
- }
156
-
157
- /**
158
- * Emit events from socket to listeners
159
- * @param message
160
- */
161
- private handleMessageFromPrimary(message: SocketEventReceived | Heartbeat) {
162
- if (this.isPrimary) {
163
- throw new Error('Received message from primary socket, but it is primary');
164
- }
165
- this._logger.debug('Received message from primary client:', message.type, message.payload);
166
- if (message.type === 'primary-present') {
167
- const shouldUpdateStatus = message.payload.connected !== this._isConnected;
168
- if (shouldUpdateStatus) {
169
- this._isConnected = message.payload.connected;
170
- if (message.payload.connected) {
171
- this._logger.debug('Primary socket is active emitting connected event');
172
- this.emit('connected', { network: this._supernetType });
173
- } else {
174
- this._logger.debug('Primary socket is inactive emitting disconnected event');
175
- this.emit('disconnected', { code: 5000, reason: 'Primary socket disconnected' });
176
- }
177
- }
178
- return;
179
- }
180
- const event = message.payload;
181
- switch (event.eventType) {
182
- case 'connected': {
183
- if (!this._isConnected) {
184
- this._isConnected = true;
185
- this.emit('connected', { network: this._supernetType });
186
- }
187
- return;
188
- }
189
- case 'disconnected': {
190
- this._isConnected = false;
191
- this.emit('disconnected', event.payload as ServerDisconnectData);
192
- return;
193
- }
194
- default: {
195
- this.emit(event.eventType, event.payload as any);
196
- }
197
- }
198
- }
199
-
200
- private async handleSendRequest(message: SendSocketMessage) {
201
- if (!this.isPrimary) {
202
- // Should never happen, but just in case
203
- return Promise.resolve();
204
- }
205
- if (!this.socketClient.isConnected) {
206
- await this.socketClient.connect();
207
- }
208
- return this.socketClient.send(message.payload.messageType, message.payload.data);
209
- }
210
239
  }
211
240
 
212
241
  export default BrowserWebSocketClient;
@@ -86,7 +86,7 @@ class WebSocketClient extends RestClient<SocketEventMap> implements IWebSocketCl
86
86
  socket.onmessage = null;
87
87
  socket.onopen = null;
88
88
  this.stopPing();
89
- socket.close();
89
+ socket.close(1000, 'Client disconnected');
90
90
  }
91
91
 
92
92
  private startPing(socket: WebSocket) {
@@ -148,7 +148,7 @@ class WebSocketClient extends RestClient<SocketEventMap> implements IWebSocketCl
148
148
  }
149
149
 
150
150
  private handleClose(e: CloseEvent) {
151
- if (e.target === this.socket) {
151
+ if (e.target === this.socket || !this.socket) {
152
152
  this._logger.info('WebSocket disconnected, cleanup', e);
153
153
  this.disconnect();
154
154
  this.emit('disconnected', {
@@ -112,7 +112,7 @@ class ApiClient extends TypedEventEmitter<ApiClientEvents> {
112
112
 
113
113
  handleSocketDisconnect(data: ServerDisconnectData) {
114
114
  // If user is not authenticated, we don't need to reconnect
115
- if (!this.auth.isAuthenticated) {
115
+ if (!this.auth.isAuthenticated || data.code === 1000) {
116
116
  this.emit('disconnected', data);
117
117
  return;
118
118
  }
@@ -1,102 +0,0 @@
1
- import { Logger } from '../../../lib/DefaultLogger';
2
- import { SocketEventMap, SocketEventName } from '../events';
3
- import { MessageType, SocketMessageMap } from '../messages';
4
- import { Heartbeat, SocketEventReceived, SendSocketMessage } from './types';
5
- interface WSCoordinatorCallbacks {
6
- /**
7
- * Invoked when authentication state changes (authenticated/unauthenticated).
8
- * @param isAuthenticated - true if client is authenticated, false if not.
9
- */
10
- onAuthChanged: (isAuthenticated: boolean) => void;
11
- /**
12
- * Invoked when role changes (primary/secondary).
13
- * @param isPrimary - true if client is primary, false if secondary.
14
- */
15
- onRoleChange: (isPrimary: boolean) => void;
16
- /**
17
- * Invoked when connection state must change (connected/disconnected).
18
- * @param isConnected
19
- */
20
- onConnectionToggle: (isConnected: boolean) => void;
21
- /**
22
- * Invoked when secondary client receives a socket event from primary.
23
- * @param message
24
- */
25
- onMessageFromPrimary: (message: SocketEventReceived | Heartbeat) => void;
26
- /**
27
- * Invoked when primary client receives a socket send request from secondary.
28
- * @param message
29
- */
30
- onSendRequest: (message: SendSocketMessage) => Promise<void>;
31
- }
32
- declare class WSCoordinator {
33
- private static readonly HEARTBEAT_INTERVAL;
34
- private static readonly PRIMARY_TIMEOUT;
35
- private static readonly CHANNEL_NAME;
36
- private static readonly ACK_TIMEOUT;
37
- private id;
38
- private callbacks;
39
- private channel;
40
- private _isPrimary;
41
- private _isConnected;
42
- private lastPrimaryHeartbeat;
43
- private logger;
44
- private heartbeatInterval;
45
- private primaryCheckInterval;
46
- private startingEvents;
47
- private ackCallbacks;
48
- private initialized;
49
- constructor(callbacks: WSCoordinatorCallbacks, logger: Logger);
50
- /**
51
- * Initialize tab coordination and determine role
52
- */
53
- initialize(): Promise<boolean>;
54
- connect(): void;
55
- disconnect(): void;
56
- changeAuthState(isAuthenticated: boolean): Promise<void>;
57
- /**
58
- * Wait briefly to see if a primary tab responds
59
- */
60
- private waitForPrimaryResponse;
61
- /**
62
- * Become the primary tab
63
- */
64
- private becomePrimary;
65
- /**
66
- * Release primary role (when closing)
67
- */
68
- private releasePrimary;
69
- /**
70
- * Start sending heartbeat messages as primary
71
- */
72
- private startHeartbeat;
73
- /**
74
- * Stop sending heartbeat messages
75
- */
76
- private stopHeartbeat;
77
- /**
78
- * Start checking for primary heartbeat (as secondary)
79
- */
80
- private startPrimaryCheck;
81
- /**
82
- * Stop checking for primary heartbeat
83
- */
84
- private stopPrimaryCheck;
85
- private handleMessage;
86
- private broadcast;
87
- /**
88
- * Send a message to be transmitted over the socket (from secondary to primary)
89
- */
90
- sendToSocket<T extends MessageType = MessageType>(messageType: T, data: SocketMessageMap[T]): Promise<void>;
91
- /**
92
- * Broadcast a socket event from primary to all secondaries
93
- */
94
- broadcastSocketEvent<E extends SocketEventName = SocketEventName>(eventType: E, payload: SocketEventMap[E]): void;
95
- private updateStartingState;
96
- isPrimary(): boolean;
97
- /**
98
- * Handle tab closing event
99
- */
100
- private handleBeforeUnload;
101
- }
102
- export default WSCoordinator;