@sogni-ai/sogni-client 4.0.0-alpha.12 → 4.0.0-alpha.14

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 (36) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/Account/index.d.ts +1 -0
  3. package/dist/Account/index.js +11 -2
  4. package/dist/Account/index.js.map +1 -1
  5. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/WSCoordinator.d.ts +101 -0
  6. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/WSCoordinator.js +359 -0
  7. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/WSCoordinator.js.map +1 -0
  8. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/index.d.ts +34 -0
  9. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/index.js +195 -0
  10. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/index.js.map +1 -0
  11. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/types.d.ts +101 -0
  12. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/types.js +3 -0
  13. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/types.js.map +1 -0
  14. package/dist/ApiClient/WebSocketClient/events.d.ts +1 -0
  15. package/dist/ApiClient/WebSocketClient/index.d.ts +2 -2
  16. package/dist/ApiClient/WebSocketClient/types.d.ts +13 -0
  17. package/dist/ApiClient/index.d.ts +2 -3
  18. package/dist/ApiClient/index.js +20 -2
  19. package/dist/ApiClient/index.js.map +1 -1
  20. package/dist/Projects/Job.d.ts +1 -1
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.js +6 -2
  23. package/dist/index.js.map +1 -1
  24. package/dist/lib/DataEntity.js +4 -2
  25. package/dist/lib/DataEntity.js.map +1 -1
  26. package/package.json +4 -4
  27. package/src/Account/index.ts +11 -2
  28. package/src/ApiClient/WebSocketClient/BrowserWebSocketClient/WSCoordinator.ts +425 -0
  29. package/src/ApiClient/WebSocketClient/BrowserWebSocketClient/index.ts +206 -0
  30. package/src/ApiClient/WebSocketClient/BrowserWebSocketClient/types.ts +107 -0
  31. package/src/ApiClient/WebSocketClient/events.ts +2 -0
  32. package/src/ApiClient/WebSocketClient/index.ts +2 -2
  33. package/src/ApiClient/WebSocketClient/types.ts +16 -0
  34. package/src/ApiClient/index.ts +25 -6
  35. package/src/index.ts +7 -3
  36. package/src/lib/DataEntity.ts +4 -2
@@ -0,0 +1,107 @@
1
+ import { SocketEventMap, SocketEventName } from '../events';
2
+ import { MessageType, SocketMessageMap } from '../messages';
3
+
4
+ /**
5
+ * Primary tab to broadcast socket events to secondary tabs.
6
+ * @param eventType - The event type.
7
+ * @param payload - The event payload. See {@link SocketEventMap} for the list of available events.
8
+ */
9
+ export interface SocketEventReceived<T extends SocketEventName = SocketEventName> {
10
+ type: 'socket-event';
11
+ payload: { eventType: T; payload: SocketEventMap[T] };
12
+ }
13
+
14
+ /**
15
+ * Sent by secondary tabs to the primary tab to send a message to WebSocket.
16
+ * @param messageType - The message type.
17
+ * @param data - The message payload. See {@link SocketMessageMap} for the list of available messages.
18
+ */
19
+ export interface SendSocketMessage<T extends MessageType = MessageType> {
20
+ type: 'socket-send';
21
+ payload: { messageType: T; data: SocketMessageMap[T] };
22
+ }
23
+
24
+ /**
25
+ * Sent by the primary tab to acknowledge that a message was sent to WebSocket.
26
+ */
27
+ export interface SocketMessageAck {
28
+ type: 'socket-ack';
29
+ payload: { envelopeId: string };
30
+ }
31
+
32
+ /**
33
+ * Sent by the primary tab to notify the secondary tabs that the primary tab is still alive.
34
+ * @param connected - true if the primary tab is connected to the server, false otherwise.
35
+ */
36
+ export interface Heartbeat {
37
+ type: 'primary-present';
38
+ payload: { connected: boolean };
39
+ }
40
+
41
+ /**
42
+ * Used to tell the primary tab to connect/disconnect socket.
43
+ * @param connected - true to connect, false to disconnect.
44
+ */
45
+ export interface ConnectionToggle {
46
+ type: 'connection-toggle';
47
+ payload: { connected: boolean };
48
+ }
49
+
50
+ /**
51
+ * Sent by tab when it is opened to notify other tabs. If another tab is present,
52
+ * it will respond with {@link Heartbeat}.
53
+ */
54
+ export interface ClientAnnounce {
55
+ type: 'announce';
56
+ }
57
+
58
+ /**
59
+ * Sent by the client to let other know that it is claiming the primary role.
60
+ */
61
+ export interface PrimaryClaim {
62
+ type: 'primary-claim';
63
+ }
64
+
65
+ /**
66
+ * Sent by the client to let other know that it is releasing the primary role.
67
+ * This usually happens when the tab is closed.
68
+ */
69
+ export interface PrimaryRelease {
70
+ type: 'primary-release';
71
+ }
72
+
73
+ /**
74
+ * Sent by the tab where user has auth state changed.
75
+ * @param authenticated - true if the user is authenticated, false otherwise.
76
+ */
77
+ export interface AuthenticationChange {
78
+ type: 'authentication';
79
+ payload: { authenticated: boolean };
80
+ }
81
+
82
+ export type ChannelMessage =
83
+ | AuthenticationChange
84
+ | SocketEventReceived
85
+ | SendSocketMessage
86
+ | SocketMessageAck
87
+ | ConnectionToggle
88
+ | Heartbeat
89
+ | ClientAnnounce
90
+ | PrimaryClaim
91
+ | PrimaryRelease;
92
+
93
+ /**
94
+ * Envelope for messages sent between tabs.
95
+ * @param id - Unique message ID.
96
+ * @param senderId - ID of the tab that sent the message.
97
+ * @param recipientId - ID of the tab that should receive the message. If not specified, the message will be broadcasted to all tabs.
98
+ * @param timestamp - Timestamp of the message.
99
+ * @param payload - Message payload.
100
+ */
101
+ export interface MessageEnvelope {
102
+ id: string;
103
+ senderId: string;
104
+ recipientId?: string;
105
+ timestamp: number;
106
+ payload: ChannelMessage;
107
+ }
@@ -148,3 +148,5 @@ export type SocketEventMap = {
148
148
 
149
149
  artistCancelConfirmation: ArtistCancelConfirmation;
150
150
  };
151
+
152
+ export type SocketEventName = keyof SocketEventMap;
@@ -1,7 +1,7 @@
1
1
  import { MessageType, SocketMessageMap } from './messages';
2
2
  import { SocketEventMap } from './events';
3
3
  import RestClient from '../../lib/RestClient';
4
- import { SupernetType } from './types';
4
+ import { IWebSocketClient, SupernetType } from './types';
5
5
  import WebSocket, { CloseEvent, ErrorEvent, MessageEvent } from 'isomorphic-ws';
6
6
  import { base64Decode, base64Encode } from '../../lib/base64';
7
7
  import isNodejs from '../../lib/isNodejs';
@@ -13,7 +13,7 @@ const PROTOCOL_VERSION = '3.0.0';
13
13
 
14
14
  const PING_INTERVAL = 15000;
15
15
 
16
- class WebSocketClient extends RestClient<SocketEventMap> {
16
+ class WebSocketClient extends RestClient<SocketEventMap> implements IWebSocketClient {
17
17
  appId: string;
18
18
  baseUrl: string;
19
19
  private socket: WebSocket | null = null;
@@ -1 +1,17 @@
1
+ import { MessageType, SocketMessageMap } from './messages';
2
+ import RestClient from '../../lib/RestClient';
3
+ import { SocketEventMap } from './events';
4
+
1
5
  export type SupernetType = 'relaxed' | 'fast';
6
+
7
+ export interface IWebSocketClient extends RestClient<SocketEventMap> {
8
+ appId: string;
9
+ baseUrl: string;
10
+ isConnected: boolean;
11
+ supernetType: SupernetType;
12
+
13
+ connect(): Promise<void>;
14
+ disconnect(): void;
15
+ send<T extends MessageType>(messageType: T, data: SocketMessageMap[T]): Promise<void>;
16
+ switchNetwork(supernetType: SupernetType): Promise<SupernetType>;
17
+ }
@@ -3,12 +3,14 @@ import WebSocketClient from './WebSocketClient';
3
3
  import TypedEventEmitter from '../lib/TypedEventEmitter';
4
4
  import { ApiClientEvents } from './events';
5
5
  import { ServerConnectData, ServerDisconnectData } from './WebSocketClient/events';
6
- import { isNotRecoverable } from './WebSocketClient/ErrorCode';
6
+ import { ErrorCode, isNotRecoverable } from './WebSocketClient/ErrorCode';
7
7
  import { JSONValue } from '../types/json';
8
- import { SupernetType } from './WebSocketClient/types';
8
+ import { IWebSocketClient, SupernetType } from './WebSocketClient/types';
9
9
  import { Logger } from '../lib/DefaultLogger';
10
10
  import CookieAuthManager from '../lib/AuthManager/CookieAuthManager';
11
11
  import { AuthManager, TokenAuthManager } from '../lib/AuthManager';
12
+ import isNodejs from '../lib/isNodejs';
13
+ import BrowserWebSocketClient from './WebSocketClient/BrowserWebSocketClient';
12
14
 
13
15
  const WS_RECONNECT_ATTEMPTS = 5;
14
16
 
@@ -48,7 +50,7 @@ class ApiClient extends TypedEventEmitter<ApiClientEvents> {
48
50
  readonly appId: string;
49
51
  readonly logger: Logger;
50
52
  private _rest: RestClient;
51
- private _socket: WebSocketClient;
53
+ private _socket: IWebSocketClient;
52
54
  private _auth: AuthManager;
53
55
  private _reconnectAttempts = WS_RECONNECT_ATTEMPTS;
54
56
  private _disableSocket: boolean = false;
@@ -68,7 +70,12 @@ class ApiClient extends TypedEventEmitter<ApiClientEvents> {
68
70
  this._auth =
69
71
  authType === 'token' ? new TokenAuthManager(baseUrl, logger) : new CookieAuthManager(logger);
70
72
  this._rest = new RestClient(baseUrl, this._auth, logger);
71
- this._socket = new WebSocketClient(socketUrl, this._auth, appId, networkType, logger);
73
+ // Use coordinated WebSocket client in browser, regular in Node.js
74
+ if (this._auth instanceof TokenAuthManager || isNodejs) {
75
+ this._socket = new WebSocketClient(socketUrl, this._auth, appId, networkType, logger);
76
+ } else {
77
+ this._socket = new BrowserWebSocketClient(socketUrl, this._auth, appId, networkType, logger);
78
+ }
72
79
  this._disableSocket = disableSocket;
73
80
  this._auth.on('updated', this.handleAuthUpdated.bind(this));
74
81
  this._socket.on('connected', this.handleSocketConnect.bind(this));
@@ -83,7 +90,7 @@ class ApiClient extends TypedEventEmitter<ApiClientEvents> {
83
90
  return this._auth;
84
91
  }
85
92
 
86
- get socket(): WebSocketClient {
93
+ get socket(): IWebSocketClient {
87
94
  return this._socket;
88
95
  }
89
96
 
@@ -101,14 +108,26 @@ class ApiClient extends TypedEventEmitter<ApiClientEvents> {
101
108
  }
102
109
 
103
110
  handleSocketDisconnect(data: ServerDisconnectData) {
111
+ // If user is not authenticated, we don't need to reconnect
112
+ if (!this.auth.isAuthenticated) {
113
+ this.emit('disconnected', data);
114
+ return;
115
+ }
104
116
  if (!data.code || isNotRecoverable(data.code)) {
117
+ // If this is browser, another tab is probably claiming the connection, so we don't need to reconnect
118
+ if (
119
+ this._socket instanceof BrowserWebSocketClient &&
120
+ data.code === ErrorCode.SWITCH_CONNECTION
121
+ ) {
122
+ this.logger.debug('Switching network connection, not reconnecting');
123
+ return;
124
+ }
105
125
  this.auth.clear();
106
126
  this.emit('disconnected', data);
107
127
  this.logger.error('Not recoverable socket error', data);
108
128
  return;
109
129
  }
110
130
  if (this._reconnectAttempts <= 0) {
111
- this.auth.clear();
112
131
  this.emit('disconnected', data);
113
132
  this._reconnectAttempts = WS_RECONNECT_ATTEMPTS;
114
133
  return;
package/src/index.ts CHANGED
@@ -63,7 +63,7 @@ export interface SogniClientConfig {
63
63
  socketEndpoint?: string;
64
64
  /**
65
65
  * Disable WebSocket connection. Useful for testing or when WebSocket is not needed.
66
- * Note that many may not work without WebSocket connection.
66
+ * Note that many APIs may not work without WebSocket connection.
67
67
  * @experimental
68
68
  * @internal
69
69
  */
@@ -141,9 +141,13 @@ export class SogniClient {
141
141
  throw Error('This method should only be called when using cookie auth');
142
142
  }
143
143
  try {
144
- await this.apiClient.rest.get<ApiResponse<MeData>>('/v1/account/me');
144
+ const res = await this.apiClient.rest.get<ApiResponse<MeData>>('/v1/account/me');
145
145
  await auth.authenticate();
146
- await this.account.me();
146
+ this.currentAccount._update({
147
+ username: res.data.username,
148
+ email: res.data.currentEmail,
149
+ walletAddress: res.data.walletAddress
150
+ });
147
151
  return true;
148
152
  } catch (e) {
149
153
  this.apiClient.logger.info('Client is not authenticated');
@@ -25,9 +25,11 @@ abstract class DataEntity<D, E extends EntityEvents = EntityEvents> extends Type
25
25
  _update(delta: Partial<D>) {
26
26
  //@ts-ignore
27
27
  const changedKeys = Object.keys(delta).filter((key) => this.data[key] !== delta[key]);
28
- this.data = { ...this.data, ...delta };
29
28
  this.lastUpdated = new Date();
30
- this.emit('updated', changedKeys);
29
+ if (changedKeys.length > 0) {
30
+ this.data = { ...this.data, ...delta };
31
+ this.emit('updated', changedKeys);
32
+ }
31
33
  }
32
34
 
33
35
  /**