rio-assist-widget 0.1.8 → 0.1.10

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.
@@ -1,52 +1,69 @@
1
- const WEBSOCKET_URL = 'wss://ws.volkswagen.latam-sandbox.rio.cloud';
2
- const DEFAULT_AGENT_MODEL = 'eu.amazon.nova-pro-v1:0';
1
+ const WEBSOCKET_URL = 'wss://ws.volkswagen.latam-sandbox.rio.cloud';
2
+ const HEARTBEAT_INTERVAL_MS = 5 * 60_000; // keep-alive before the 10min idle timeout
3
3
 
4
- export type RioIncomingMessage = {
5
- text: string;
6
- raw: string;
7
- data: unknown;
8
- };
4
+ export type RioIncomingMessage = {
5
+ text: string;
6
+ raw: string;
7
+ data: unknown;
8
+ action?: string;
9
+ };
9
10
 
10
11
  export class RioWebsocketClient {
11
12
  readonly token: string;
12
13
 
13
14
  private socket: WebSocket | null = null;
14
15
 
15
- private connectPromise: Promise<void> | null = null;
16
-
17
- private readonly listeners = new Set<(message: RioIncomingMessage) => void>();
18
-
19
- constructor(token: string) {
20
- this.token = token;
21
- }
16
+ private connectPromise: Promise<void> | null = null;
17
+
18
+ private readonly listeners = new Set<(message: RioIncomingMessage) => void>();
19
+
20
+ private heartbeatId: number | null = null;
21
+
22
+ constructor(token: string) {
23
+ this.token = token;
24
+ }
22
25
 
23
26
  matchesToken(value: string) {
24
27
  return this.token === value;
25
28
  }
26
29
 
27
- async sendMessage(message: string) {
28
- const socket = await this.ensureConnection();
29
-
30
- const payload = {
31
- action: 'sendMessage',
32
- message,
33
- agentModel: DEFAULT_AGENT_MODEL,
34
- };
35
-
36
- socket.send(JSON.stringify(payload));
37
- }
38
-
39
- onMessage(listener: (message: RioIncomingMessage) => void) {
40
- this.listeners.add(listener);
41
- return () => this.listeners.delete(listener);
42
- }
43
-
44
- close() {
45
- if (this.socket && this.socket.readyState === WebSocket.OPEN) {
46
- this.socket.close();
47
- }
48
-
49
- this.connectPromise = null;
30
+ async sendMessage(message: string) {
31
+ const socket = await this.ensureConnection();
32
+
33
+ const payload = {
34
+ action: 'sendMessage',
35
+ message,
36
+ };
37
+
38
+ socket.send(JSON.stringify(payload));
39
+ }
40
+
41
+ async requestHistory(options: { conversationId?: string; limit?: number } = {}) {
42
+ const socket = await this.ensureConnection();
43
+ const payload: Record<string, unknown> = {
44
+ action: 'getHistory',
45
+ limit: options.limit ?? 50,
46
+ };
47
+
48
+ if (options.conversationId) {
49
+ payload.conversationId = options.conversationId;
50
+ }
51
+
52
+ socket.send(JSON.stringify(payload));
53
+ }
54
+
55
+ onMessage(listener: (message: RioIncomingMessage) => void) {
56
+ this.listeners.add(listener);
57
+ return () => this.listeners.delete(listener);
58
+ }
59
+
60
+ close() {
61
+ this.stopHeartbeat();
62
+ if (this.socket && this.socket.readyState === WebSocket.OPEN) {
63
+ this.socket.close();
64
+ }
65
+
66
+ this.connectPromise = null;
50
67
  this.socket = null;
51
68
  this.listeners.clear();
52
69
  }
@@ -65,27 +82,32 @@ export class RioWebsocketClient {
65
82
  `${WEBSOCKET_URL}?token=${encodeURIComponent(this.token)}`,
66
83
  );
67
84
 
68
- this.socket.addEventListener('message', (event) => this.handleMessage(event));
69
- this.socket.addEventListener('close', () => {
70
- this.connectPromise = null;
71
- this.socket = null;
72
- });
85
+ this.socket.addEventListener('message', (event) => this.handleMessage(event));
86
+ this.socket.addEventListener('close', () => {
87
+ this.connectPromise = null;
88
+ this.socket = null;
89
+ this.stopHeartbeat();
90
+ });
73
91
 
74
92
  this.connectPromise = new Promise((resolve, reject) => {
75
93
  if (!this.socket) {
76
94
  reject(new Error('Falha ao criar conexão WebSocket.'));
77
95
  return;
78
96
  }
79
-
80
- const handleOpen = () => {
81
- cleanup();
82
- resolve();
83
- };
84
-
85
- const handleError = () => {
86
- cleanup();
87
- this.socket?.close();
88
- this.socket = null;
97
+
98
+ const handleOpen = () => {
99
+ cleanup();
100
+ if (this.socket) {
101
+ this.startHeartbeat(this.socket);
102
+ }
103
+ resolve();
104
+ };
105
+
106
+ const handleError = () => {
107
+ cleanup();
108
+ this.stopHeartbeat();
109
+ this.socket?.close();
110
+ this.socket = null;
89
111
  this.connectPromise = null;
90
112
  reject(
91
113
  new Error(
@@ -109,33 +131,57 @@ export class RioWebsocketClient {
109
131
  throw new Error('Conexão WebSocket do Rio Insight não está pronta.');
110
132
  }
111
133
 
112
- return this.socket;
113
- }
114
-
115
- private async handleMessage(event: MessageEvent) {
116
- const raw = await this.readMessage(event.data);
117
- let parsed: unknown = null;
118
- let text = raw;
119
-
120
- try {
121
- parsed = JSON.parse(raw);
122
- if (typeof parsed === 'object' && parsed !== null) {
123
- const maybeText =
124
- (parsed as any).message ??
125
- (parsed as any).response ??
126
- (parsed as any).text ??
127
- (parsed as any).content;
134
+ return this.socket;
135
+ }
136
+
137
+ private startHeartbeat(socket: WebSocket) {
138
+ this.stopHeartbeat();
139
+ this.heartbeatId = window.setInterval(() => {
140
+ if (socket.readyState === WebSocket.OPEN) {
141
+ socket.send(JSON.stringify({ action: 'ping' }));
142
+ }
143
+ }, HEARTBEAT_INTERVAL_MS);
144
+ }
145
+
146
+ private stopHeartbeat() {
147
+ if (this.heartbeatId !== null) {
148
+ window.clearInterval(this.heartbeatId);
149
+ this.heartbeatId = null;
150
+ }
151
+ }
152
+
153
+ private async handleMessage(event: MessageEvent) {
154
+ const raw = await this.readMessage(event.data);
155
+ let parsed: unknown = null;
156
+ let text = raw;
157
+ let action: string | undefined;
158
+
159
+ try {
160
+ parsed = JSON.parse(raw);
161
+ if (typeof parsed === 'object' && parsed !== null) {
162
+ const maybeAction =
163
+ (parsed as any).action ?? (parsed as any).type ?? (parsed as any).event;
164
+
165
+ if (typeof maybeAction === 'string') {
166
+ action = maybeAction;
167
+ }
168
+
169
+ const maybeText =
170
+ (parsed as any).message ??
171
+ (parsed as any).response ??
172
+ (parsed as any).text ??
173
+ (parsed as any).content;
128
174
 
129
175
  if (typeof maybeText === 'string') {
130
176
  text = maybeText;
131
177
  }
132
178
  }
133
- } catch {
134
- parsed = null;
135
- }
136
-
137
- this.listeners.forEach((listener) => listener({ text, raw, data: parsed }));
138
- }
179
+ } catch {
180
+ parsed = null;
181
+ }
182
+
183
+ this.listeners.forEach((listener) => listener({ text, raw, data: parsed, action }));
184
+ }
139
185
 
140
186
  private async readMessage(
141
187
  data: MessageEvent['data'],
@@ -0,0 +1 @@
1
+ declare module 'markdown-it-task-lists';