@aomi-labs/widget-lib 0.2.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/README.md +310 -0
- package/dist/core/AomiChatWidget.d.ts +41 -0
- package/dist/core/AomiChatWidget.d.ts.map +1 -0
- package/dist/core/ChatManager.d.ts +81 -0
- package/dist/core/ChatManager.d.ts.map +1 -0
- package/dist/core/ThemeManager.d.ts +80 -0
- package/dist/core/ThemeManager.d.ts.map +1 -0
- package/dist/core/WalletManager.d.ts +105 -0
- package/dist/core/WalletManager.d.ts.map +1 -0
- package/dist/index.d.ts +816 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3289 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +3215 -0
- package/dist/index.mjs.map +1 -0
- package/dist/index.umd.js +3295 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/types/constants.d.ts +91 -0
- package/dist/types/constants.d.ts.map +1 -0
- package/dist/types/errors.d.ts +102 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/index.d.ts +263 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/utils/index.d.ts +99 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/package.json +89 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,3215 @@
|
|
|
1
|
+
function getDefaultExportFromCjs (x) {
|
|
2
|
+
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
var eventemitter3 = {exports: {}};
|
|
6
|
+
|
|
7
|
+
(function (module) {
|
|
8
|
+
|
|
9
|
+
var has = Object.prototype.hasOwnProperty
|
|
10
|
+
, prefix = '~';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Constructor to create a storage for our `EE` objects.
|
|
14
|
+
* An `Events` instance is a plain object whose properties are event names.
|
|
15
|
+
*
|
|
16
|
+
* @constructor
|
|
17
|
+
* @private
|
|
18
|
+
*/
|
|
19
|
+
function Events() {}
|
|
20
|
+
|
|
21
|
+
//
|
|
22
|
+
// We try to not inherit from `Object.prototype`. In some engines creating an
|
|
23
|
+
// instance in this way is faster than calling `Object.create(null)` directly.
|
|
24
|
+
// If `Object.create(null)` is not supported we prefix the event names with a
|
|
25
|
+
// character to make sure that the built-in object properties are not
|
|
26
|
+
// overridden or used as an attack vector.
|
|
27
|
+
//
|
|
28
|
+
if (Object.create) {
|
|
29
|
+
Events.prototype = Object.create(null);
|
|
30
|
+
|
|
31
|
+
//
|
|
32
|
+
// This hack is needed because the `__proto__` property is still inherited in
|
|
33
|
+
// some old browsers like Android 4, iPhone 5.1, Opera 11 and Safari 5.
|
|
34
|
+
//
|
|
35
|
+
if (!new Events().__proto__) prefix = false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Representation of a single event listener.
|
|
40
|
+
*
|
|
41
|
+
* @param {Function} fn The listener function.
|
|
42
|
+
* @param {*} context The context to invoke the listener with.
|
|
43
|
+
* @param {Boolean} [once=false] Specify if the listener is a one-time listener.
|
|
44
|
+
* @constructor
|
|
45
|
+
* @private
|
|
46
|
+
*/
|
|
47
|
+
function EE(fn, context, once) {
|
|
48
|
+
this.fn = fn;
|
|
49
|
+
this.context = context;
|
|
50
|
+
this.once = once || false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Add a listener for a given event.
|
|
55
|
+
*
|
|
56
|
+
* @param {EventEmitter} emitter Reference to the `EventEmitter` instance.
|
|
57
|
+
* @param {(String|Symbol)} event The event name.
|
|
58
|
+
* @param {Function} fn The listener function.
|
|
59
|
+
* @param {*} context The context to invoke the listener with.
|
|
60
|
+
* @param {Boolean} once Specify if the listener is a one-time listener.
|
|
61
|
+
* @returns {EventEmitter}
|
|
62
|
+
* @private
|
|
63
|
+
*/
|
|
64
|
+
function addListener(emitter, event, fn, context, once) {
|
|
65
|
+
if (typeof fn !== 'function') {
|
|
66
|
+
throw new TypeError('The listener must be a function');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
var listener = new EE(fn, context || emitter, once)
|
|
70
|
+
, evt = prefix ? prefix + event : event;
|
|
71
|
+
|
|
72
|
+
if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++;
|
|
73
|
+
else if (!emitter._events[evt].fn) emitter._events[evt].push(listener);
|
|
74
|
+
else emitter._events[evt] = [emitter._events[evt], listener];
|
|
75
|
+
|
|
76
|
+
return emitter;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Clear event by name.
|
|
81
|
+
*
|
|
82
|
+
* @param {EventEmitter} emitter Reference to the `EventEmitter` instance.
|
|
83
|
+
* @param {(String|Symbol)} evt The Event name.
|
|
84
|
+
* @private
|
|
85
|
+
*/
|
|
86
|
+
function clearEvent(emitter, evt) {
|
|
87
|
+
if (--emitter._eventsCount === 0) emitter._events = new Events();
|
|
88
|
+
else delete emitter._events[evt];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Minimal `EventEmitter` interface that is molded against the Node.js
|
|
93
|
+
* `EventEmitter` interface.
|
|
94
|
+
*
|
|
95
|
+
* @constructor
|
|
96
|
+
* @public
|
|
97
|
+
*/
|
|
98
|
+
function EventEmitter() {
|
|
99
|
+
this._events = new Events();
|
|
100
|
+
this._eventsCount = 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Return an array listing the events for which the emitter has registered
|
|
105
|
+
* listeners.
|
|
106
|
+
*
|
|
107
|
+
* @returns {Array}
|
|
108
|
+
* @public
|
|
109
|
+
*/
|
|
110
|
+
EventEmitter.prototype.eventNames = function eventNames() {
|
|
111
|
+
var names = []
|
|
112
|
+
, events
|
|
113
|
+
, name;
|
|
114
|
+
|
|
115
|
+
if (this._eventsCount === 0) return names;
|
|
116
|
+
|
|
117
|
+
for (name in (events = this._events)) {
|
|
118
|
+
if (has.call(events, name)) names.push(prefix ? name.slice(1) : name);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (Object.getOwnPropertySymbols) {
|
|
122
|
+
return names.concat(Object.getOwnPropertySymbols(events));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return names;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Return the listeners registered for a given event.
|
|
130
|
+
*
|
|
131
|
+
* @param {(String|Symbol)} event The event name.
|
|
132
|
+
* @returns {Array} The registered listeners.
|
|
133
|
+
* @public
|
|
134
|
+
*/
|
|
135
|
+
EventEmitter.prototype.listeners = function listeners(event) {
|
|
136
|
+
var evt = prefix ? prefix + event : event
|
|
137
|
+
, handlers = this._events[evt];
|
|
138
|
+
|
|
139
|
+
if (!handlers) return [];
|
|
140
|
+
if (handlers.fn) return [handlers.fn];
|
|
141
|
+
|
|
142
|
+
for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) {
|
|
143
|
+
ee[i] = handlers[i].fn;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return ee;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Return the number of listeners listening to a given event.
|
|
151
|
+
*
|
|
152
|
+
* @param {(String|Symbol)} event The event name.
|
|
153
|
+
* @returns {Number} The number of listeners.
|
|
154
|
+
* @public
|
|
155
|
+
*/
|
|
156
|
+
EventEmitter.prototype.listenerCount = function listenerCount(event) {
|
|
157
|
+
var evt = prefix ? prefix + event : event
|
|
158
|
+
, listeners = this._events[evt];
|
|
159
|
+
|
|
160
|
+
if (!listeners) return 0;
|
|
161
|
+
if (listeners.fn) return 1;
|
|
162
|
+
return listeners.length;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Calls each of the listeners registered for a given event.
|
|
167
|
+
*
|
|
168
|
+
* @param {(String|Symbol)} event The event name.
|
|
169
|
+
* @returns {Boolean} `true` if the event had listeners, else `false`.
|
|
170
|
+
* @public
|
|
171
|
+
*/
|
|
172
|
+
EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) {
|
|
173
|
+
var evt = prefix ? prefix + event : event;
|
|
174
|
+
|
|
175
|
+
if (!this._events[evt]) return false;
|
|
176
|
+
|
|
177
|
+
var listeners = this._events[evt]
|
|
178
|
+
, len = arguments.length
|
|
179
|
+
, args
|
|
180
|
+
, i;
|
|
181
|
+
|
|
182
|
+
if (listeners.fn) {
|
|
183
|
+
if (listeners.once) this.removeListener(event, listeners.fn, undefined, true);
|
|
184
|
+
|
|
185
|
+
switch (len) {
|
|
186
|
+
case 1: return listeners.fn.call(listeners.context), true;
|
|
187
|
+
case 2: return listeners.fn.call(listeners.context, a1), true;
|
|
188
|
+
case 3: return listeners.fn.call(listeners.context, a1, a2), true;
|
|
189
|
+
case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true;
|
|
190
|
+
case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true;
|
|
191
|
+
case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
for (i = 1, args = new Array(len -1); i < len; i++) {
|
|
195
|
+
args[i - 1] = arguments[i];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
listeners.fn.apply(listeners.context, args);
|
|
199
|
+
} else {
|
|
200
|
+
var length = listeners.length
|
|
201
|
+
, j;
|
|
202
|
+
|
|
203
|
+
for (i = 0; i < length; i++) {
|
|
204
|
+
if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true);
|
|
205
|
+
|
|
206
|
+
switch (len) {
|
|
207
|
+
case 1: listeners[i].fn.call(listeners[i].context); break;
|
|
208
|
+
case 2: listeners[i].fn.call(listeners[i].context, a1); break;
|
|
209
|
+
case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break;
|
|
210
|
+
case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break;
|
|
211
|
+
default:
|
|
212
|
+
if (!args) for (j = 1, args = new Array(len -1); j < len; j++) {
|
|
213
|
+
args[j - 1] = arguments[j];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
listeners[i].fn.apply(listeners[i].context, args);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return true;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Add a listener for a given event.
|
|
226
|
+
*
|
|
227
|
+
* @param {(String|Symbol)} event The event name.
|
|
228
|
+
* @param {Function} fn The listener function.
|
|
229
|
+
* @param {*} [context=this] The context to invoke the listener with.
|
|
230
|
+
* @returns {EventEmitter} `this`.
|
|
231
|
+
* @public
|
|
232
|
+
*/
|
|
233
|
+
EventEmitter.prototype.on = function on(event, fn, context) {
|
|
234
|
+
return addListener(this, event, fn, context, false);
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Add a one-time listener for a given event.
|
|
239
|
+
*
|
|
240
|
+
* @param {(String|Symbol)} event The event name.
|
|
241
|
+
* @param {Function} fn The listener function.
|
|
242
|
+
* @param {*} [context=this] The context to invoke the listener with.
|
|
243
|
+
* @returns {EventEmitter} `this`.
|
|
244
|
+
* @public
|
|
245
|
+
*/
|
|
246
|
+
EventEmitter.prototype.once = function once(event, fn, context) {
|
|
247
|
+
return addListener(this, event, fn, context, true);
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Remove the listeners of a given event.
|
|
252
|
+
*
|
|
253
|
+
* @param {(String|Symbol)} event The event name.
|
|
254
|
+
* @param {Function} fn Only remove the listeners that match this function.
|
|
255
|
+
* @param {*} context Only remove the listeners that have this context.
|
|
256
|
+
* @param {Boolean} once Only remove one-time listeners.
|
|
257
|
+
* @returns {EventEmitter} `this`.
|
|
258
|
+
* @public
|
|
259
|
+
*/
|
|
260
|
+
EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) {
|
|
261
|
+
var evt = prefix ? prefix + event : event;
|
|
262
|
+
|
|
263
|
+
if (!this._events[evt]) return this;
|
|
264
|
+
if (!fn) {
|
|
265
|
+
clearEvent(this, evt);
|
|
266
|
+
return this;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
var listeners = this._events[evt];
|
|
270
|
+
|
|
271
|
+
if (listeners.fn) {
|
|
272
|
+
if (
|
|
273
|
+
listeners.fn === fn &&
|
|
274
|
+
(!once || listeners.once) &&
|
|
275
|
+
(!context || listeners.context === context)
|
|
276
|
+
) {
|
|
277
|
+
clearEvent(this, evt);
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
for (var i = 0, events = [], length = listeners.length; i < length; i++) {
|
|
281
|
+
if (
|
|
282
|
+
listeners[i].fn !== fn ||
|
|
283
|
+
(once && !listeners[i].once) ||
|
|
284
|
+
(context && listeners[i].context !== context)
|
|
285
|
+
) {
|
|
286
|
+
events.push(listeners[i]);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
//
|
|
291
|
+
// Reset the array, or remove it completely if we have no more listeners.
|
|
292
|
+
//
|
|
293
|
+
if (events.length) this._events[evt] = events.length === 1 ? events[0] : events;
|
|
294
|
+
else clearEvent(this, evt);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return this;
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Remove all listeners, or those of the specified event.
|
|
302
|
+
*
|
|
303
|
+
* @param {(String|Symbol)} [event] The event name.
|
|
304
|
+
* @returns {EventEmitter} `this`.
|
|
305
|
+
* @public
|
|
306
|
+
*/
|
|
307
|
+
EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) {
|
|
308
|
+
var evt;
|
|
309
|
+
|
|
310
|
+
if (event) {
|
|
311
|
+
evt = prefix ? prefix + event : event;
|
|
312
|
+
if (this._events[evt]) clearEvent(this, evt);
|
|
313
|
+
} else {
|
|
314
|
+
this._events = new Events();
|
|
315
|
+
this._eventsCount = 0;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return this;
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
//
|
|
322
|
+
// Alias methods names because people roll like that.
|
|
323
|
+
//
|
|
324
|
+
EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
|
|
325
|
+
EventEmitter.prototype.addListener = EventEmitter.prototype.on;
|
|
326
|
+
|
|
327
|
+
//
|
|
328
|
+
// Expose the prefix.
|
|
329
|
+
//
|
|
330
|
+
EventEmitter.prefixed = prefix;
|
|
331
|
+
|
|
332
|
+
//
|
|
333
|
+
// Allow `EventEmitter` to be imported as module namespace.
|
|
334
|
+
//
|
|
335
|
+
EventEmitter.EventEmitter = EventEmitter;
|
|
336
|
+
|
|
337
|
+
//
|
|
338
|
+
// Expose the module.
|
|
339
|
+
//
|
|
340
|
+
{
|
|
341
|
+
module.exports = EventEmitter;
|
|
342
|
+
}
|
|
343
|
+
} (eventemitter3));
|
|
344
|
+
|
|
345
|
+
var eventemitter3Exports = eventemitter3.exports;
|
|
346
|
+
var EventEmitter = /*@__PURE__*/getDefaultExportFromCjs(eventemitter3Exports);
|
|
347
|
+
|
|
348
|
+
// Constants for the Aomi Chat Widget
|
|
349
|
+
/*
|
|
350
|
+
* ============================================================================
|
|
351
|
+
* WIDGET DEFAULTS
|
|
352
|
+
* ============================================================================
|
|
353
|
+
*/
|
|
354
|
+
const DEFAULT_WIDGET_WIDTH = '400px';
|
|
355
|
+
const DEFAULT_WIDGET_HEIGHT = '600px';
|
|
356
|
+
const DEFAULT_MAX_HEIGHT = 800;
|
|
357
|
+
const DEFAULT_MESSAGE_LENGTH = 2000;
|
|
358
|
+
const DEFAULT_RECONNECT_ATTEMPTS = 5;
|
|
359
|
+
const DEFAULT_RECONNECT_DELAY = 3000;
|
|
360
|
+
/*
|
|
361
|
+
* ============================================================================
|
|
362
|
+
* NETWORK CONSTANTS
|
|
363
|
+
* ============================================================================
|
|
364
|
+
*/
|
|
365
|
+
const SUPPORTED_CHAINS = {
|
|
366
|
+
1: 'Ethereum',
|
|
367
|
+
5: 'Goerli',
|
|
368
|
+
10: 'Optimism',
|
|
369
|
+
100: 'Gnosis',
|
|
370
|
+
1337: 'Localhost',
|
|
371
|
+
31337: 'Anvil',
|
|
372
|
+
137: 'Polygon',
|
|
373
|
+
42161: 'Arbitrum One',
|
|
374
|
+
59140: 'Linea Sepolia',
|
|
375
|
+
59144: 'Linea',
|
|
376
|
+
8453: 'Base',
|
|
377
|
+
11155111: 'Sepolia',
|
|
378
|
+
};
|
|
379
|
+
const DEFAULT_CHAIN_ID = 1;
|
|
380
|
+
/*
|
|
381
|
+
* ============================================================================
|
|
382
|
+
* THEME PALETTES
|
|
383
|
+
* ============================================================================
|
|
384
|
+
*/
|
|
385
|
+
const THEME_PALETTES = {
|
|
386
|
+
light: {
|
|
387
|
+
primary: '#007bff',
|
|
388
|
+
background: '#ffffff',
|
|
389
|
+
surface: '#f8f9fa',
|
|
390
|
+
text: '#212529',
|
|
391
|
+
textSecondary: '#6c757d',
|
|
392
|
+
border: '#dee2e6',
|
|
393
|
+
success: '#28a745',
|
|
394
|
+
error: '#dc3545',
|
|
395
|
+
warning: '#ffc107',
|
|
396
|
+
accent: '#6f42c1',
|
|
397
|
+
},
|
|
398
|
+
dark: {
|
|
399
|
+
primary: '#0d6efd',
|
|
400
|
+
background: '#121212',
|
|
401
|
+
surface: '#1e1e1e',
|
|
402
|
+
text: '#ffffff',
|
|
403
|
+
textSecondary: '#b3b3b3',
|
|
404
|
+
border: '#404040',
|
|
405
|
+
success: '#00d26a',
|
|
406
|
+
error: '#ff4757',
|
|
407
|
+
warning: '#ffa500',
|
|
408
|
+
accent: '#8b5cf6',
|
|
409
|
+
},
|
|
410
|
+
terminal: {
|
|
411
|
+
primary: '#00ff00',
|
|
412
|
+
background: '#0a0a0a',
|
|
413
|
+
surface: '#161b22',
|
|
414
|
+
text: '#00ff00',
|
|
415
|
+
textSecondary: '#22c55e',
|
|
416
|
+
border: '#30363d',
|
|
417
|
+
success: '#00ff88',
|
|
418
|
+
error: '#ff5555',
|
|
419
|
+
warning: '#ffff55',
|
|
420
|
+
accent: '#ff00ff',
|
|
421
|
+
},
|
|
422
|
+
neon: {
|
|
423
|
+
primary: '#ff007f',
|
|
424
|
+
background: '#0d0015',
|
|
425
|
+
surface: '#1a0033',
|
|
426
|
+
text: '#ffffff',
|
|
427
|
+
textSecondary: '#cc99ff',
|
|
428
|
+
border: '#4a0066',
|
|
429
|
+
success: '#00ffaa',
|
|
430
|
+
error: '#ff3366',
|
|
431
|
+
warning: '#ffaa00',
|
|
432
|
+
accent: '#00aaff',
|
|
433
|
+
},
|
|
434
|
+
minimal: {
|
|
435
|
+
primary: '#000000',
|
|
436
|
+
background: '#ffffff',
|
|
437
|
+
surface: '#fafafa',
|
|
438
|
+
text: '#333333',
|
|
439
|
+
textSecondary: '#888888',
|
|
440
|
+
border: '#e0e0e0',
|
|
441
|
+
success: '#4caf50',
|
|
442
|
+
error: '#f44336',
|
|
443
|
+
warning: '#ff9800',
|
|
444
|
+
accent: '#9c27b0',
|
|
445
|
+
},
|
|
446
|
+
};
|
|
447
|
+
/*
|
|
448
|
+
* ============================================================================
|
|
449
|
+
* COMPLETE THEME DEFINITIONS
|
|
450
|
+
* ============================================================================
|
|
451
|
+
*/
|
|
452
|
+
const PREDEFINED_THEMES = {
|
|
453
|
+
light: {
|
|
454
|
+
name: 'Light',
|
|
455
|
+
palette: THEME_PALETTES.light,
|
|
456
|
+
fonts: {
|
|
457
|
+
primary: '"Segoe UI", "Roboto", "Helvetica Neue", Arial, sans-serif',
|
|
458
|
+
monospace: '"SF Mono", "Monaco", "Inconsolata", "Roboto Mono", monospace',
|
|
459
|
+
},
|
|
460
|
+
spacing: {
|
|
461
|
+
xs: '4px',
|
|
462
|
+
sm: '8px',
|
|
463
|
+
md: '16px',
|
|
464
|
+
lg: '24px',
|
|
465
|
+
xl: '32px',
|
|
466
|
+
},
|
|
467
|
+
borderRadius: {
|
|
468
|
+
sm: '4px',
|
|
469
|
+
md: '8px',
|
|
470
|
+
lg: '12px',
|
|
471
|
+
},
|
|
472
|
+
shadows: {
|
|
473
|
+
sm: '0 1px 3px rgba(0, 0, 0, 0.1)',
|
|
474
|
+
md: '0 4px 12px rgba(0, 0, 0, 0.15)',
|
|
475
|
+
lg: '0 8px 24px rgba(0, 0, 0, 0.2)',
|
|
476
|
+
},
|
|
477
|
+
},
|
|
478
|
+
dark: {
|
|
479
|
+
name: 'Dark',
|
|
480
|
+
palette: THEME_PALETTES.dark,
|
|
481
|
+
fonts: {
|
|
482
|
+
primary: '"Segoe UI", "Roboto", "Helvetica Neue", Arial, sans-serif',
|
|
483
|
+
monospace: '"SF Mono", "Monaco", "Inconsolata", "Roboto Mono", monospace',
|
|
484
|
+
},
|
|
485
|
+
spacing: {
|
|
486
|
+
xs: '4px',
|
|
487
|
+
sm: '8px',
|
|
488
|
+
md: '16px',
|
|
489
|
+
lg: '24px',
|
|
490
|
+
xl: '32px',
|
|
491
|
+
},
|
|
492
|
+
borderRadius: {
|
|
493
|
+
sm: '4px',
|
|
494
|
+
md: '8px',
|
|
495
|
+
lg: '12px',
|
|
496
|
+
},
|
|
497
|
+
shadows: {
|
|
498
|
+
sm: '0 1px 3px rgba(0, 0, 0, 0.3)',
|
|
499
|
+
md: '0 4px 12px rgba(0, 0, 0, 0.4)',
|
|
500
|
+
lg: '0 8px 24px rgba(0, 0, 0, 0.5)',
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
terminal: {
|
|
504
|
+
name: 'Terminal',
|
|
505
|
+
palette: THEME_PALETTES.terminal,
|
|
506
|
+
fonts: {
|
|
507
|
+
primary: '"SF Mono", "Monaco", "Inconsolata", "Roboto Mono", monospace',
|
|
508
|
+
monospace: '"SF Mono", "Monaco", "Inconsolata", "Roboto Mono", monospace',
|
|
509
|
+
},
|
|
510
|
+
spacing: {
|
|
511
|
+
xs: '2px',
|
|
512
|
+
sm: '4px',
|
|
513
|
+
md: '8px',
|
|
514
|
+
lg: '16px',
|
|
515
|
+
xl: '24px',
|
|
516
|
+
},
|
|
517
|
+
borderRadius: {
|
|
518
|
+
sm: '0px',
|
|
519
|
+
md: '2px',
|
|
520
|
+
lg: '4px',
|
|
521
|
+
},
|
|
522
|
+
shadows: {
|
|
523
|
+
sm: '0 0 8px rgba(0, 255, 0, 0.3)',
|
|
524
|
+
md: '0 0 16px rgba(0, 255, 0, 0.5)',
|
|
525
|
+
lg: '0 0 24px rgba(0, 255, 0, 0.7)',
|
|
526
|
+
},
|
|
527
|
+
},
|
|
528
|
+
neon: {
|
|
529
|
+
name: 'Neon',
|
|
530
|
+
palette: THEME_PALETTES.neon,
|
|
531
|
+
fonts: {
|
|
532
|
+
primary: '"Orbitron", "Rajdhani", "Exo 2", sans-serif',
|
|
533
|
+
monospace: '"Share Tech Mono", "Courier New", monospace',
|
|
534
|
+
},
|
|
535
|
+
spacing: {
|
|
536
|
+
xs: '4px',
|
|
537
|
+
sm: '8px',
|
|
538
|
+
md: '16px',
|
|
539
|
+
lg: '24px',
|
|
540
|
+
xl: '32px',
|
|
541
|
+
},
|
|
542
|
+
borderRadius: {
|
|
543
|
+
sm: '2px',
|
|
544
|
+
md: '6px',
|
|
545
|
+
lg: '10px',
|
|
546
|
+
},
|
|
547
|
+
shadows: {
|
|
548
|
+
sm: '0 0 8px rgba(255, 0, 127, 0.4)',
|
|
549
|
+
md: '0 0 16px rgba(255, 0, 127, 0.6)',
|
|
550
|
+
lg: '0 0 24px rgba(255, 0, 127, 0.8)',
|
|
551
|
+
},
|
|
552
|
+
},
|
|
553
|
+
minimal: {
|
|
554
|
+
name: 'Minimal',
|
|
555
|
+
palette: THEME_PALETTES.minimal,
|
|
556
|
+
fonts: {
|
|
557
|
+
primary: '"Inter", "Helvetica Neue", Arial, sans-serif',
|
|
558
|
+
monospace: '"JetBrains Mono", "SF Mono", monospace',
|
|
559
|
+
},
|
|
560
|
+
spacing: {
|
|
561
|
+
xs: '4px',
|
|
562
|
+
sm: '8px',
|
|
563
|
+
md: '16px',
|
|
564
|
+
lg: '24px',
|
|
565
|
+
xl: '32px',
|
|
566
|
+
},
|
|
567
|
+
borderRadius: {
|
|
568
|
+
sm: '2px',
|
|
569
|
+
md: '4px',
|
|
570
|
+
lg: '6px',
|
|
571
|
+
},
|
|
572
|
+
shadows: {
|
|
573
|
+
sm: '0 1px 2px rgba(0, 0, 0, 0.05)',
|
|
574
|
+
md: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
|
575
|
+
lg: '0 4px 16px rgba(0, 0, 0, 0.15)',
|
|
576
|
+
},
|
|
577
|
+
},
|
|
578
|
+
};
|
|
579
|
+
/*
|
|
580
|
+
* ============================================================================
|
|
581
|
+
* ERROR CODES
|
|
582
|
+
* ============================================================================
|
|
583
|
+
*/
|
|
584
|
+
const ERROR_CODES = {
|
|
585
|
+
// Configuration errors
|
|
586
|
+
INVALID_CONFIG: 'INVALID_CONFIG',
|
|
587
|
+
MISSING_APP_CODE: 'MISSING_APP_CODE',
|
|
588
|
+
INVALID_THEME: 'INVALID_THEME',
|
|
589
|
+
INVALID_DIMENSIONS: 'INVALID_DIMENSIONS',
|
|
590
|
+
// Connection errors
|
|
591
|
+
CONNECTION_FAILED: 'CONNECTION_FAILED',
|
|
592
|
+
CONNECTION_TIMEOUT: 'CONNECTION_TIMEOUT',
|
|
593
|
+
BACKEND_UNAVAILABLE: 'BACKEND_UNAVAILABLE',
|
|
594
|
+
AUTHENTICATION_FAILED: 'AUTHENTICATION_FAILED',
|
|
595
|
+
// Wallet errors
|
|
596
|
+
WALLET_NOT_CONNECTED: 'WALLET_NOT_CONNECTED',
|
|
597
|
+
WALLET_CONNECTION_FAILED: 'WALLET_CONNECTION_FAILED',
|
|
598
|
+
UNSUPPORTED_NETWORK: 'UNSUPPORTED_NETWORK',
|
|
599
|
+
TRANSACTION_FAILED: 'TRANSACTION_FAILED',
|
|
600
|
+
TRANSACTION_REJECTED: 'TRANSACTION_REJECTED',
|
|
601
|
+
// Chat errors
|
|
602
|
+
MESSAGE_TOO_LONG: 'MESSAGE_TOO_LONG',
|
|
603
|
+
RATE_LIMITED: 'RATE_LIMITED',
|
|
604
|
+
INVALID_MESSAGE: 'INVALID_MESSAGE',
|
|
605
|
+
SESSION_EXPIRED: 'SESSION_EXPIRED',
|
|
606
|
+
// General errors
|
|
607
|
+
UNKNOWN_ERROR: 'UNKNOWN_ERROR',
|
|
608
|
+
INITIALIZATION_FAILED: 'INITIALIZATION_FAILED',
|
|
609
|
+
PROVIDER_ERROR: 'PROVIDER_ERROR',
|
|
610
|
+
};
|
|
611
|
+
/*
|
|
612
|
+
* ============================================================================
|
|
613
|
+
* EVENT NAMES
|
|
614
|
+
* ============================================================================
|
|
615
|
+
*/
|
|
616
|
+
const WIDGET_EVENTS = {
|
|
617
|
+
// Widget lifecycle
|
|
618
|
+
READY: 'ready',
|
|
619
|
+
DESTROY: 'destroy',
|
|
620
|
+
RESIZE: 'resize',
|
|
621
|
+
// Chat events
|
|
622
|
+
MESSAGE: 'message',
|
|
623
|
+
TYPING_CHANGE: 'typingChange',
|
|
624
|
+
PROCESSING_CHANGE: 'processingChange',
|
|
625
|
+
// Connection events
|
|
626
|
+
CONNECTION_CHANGE: 'connectionChange',
|
|
627
|
+
SESSION_START: 'sessionStart',
|
|
628
|
+
SESSION_END: 'sessionEnd',
|
|
629
|
+
// Wallet events
|
|
630
|
+
WALLET_CONNECT: 'walletConnect',
|
|
631
|
+
WALLET_DISCONNECT: 'walletDisconnect',
|
|
632
|
+
NETWORK_CHANGE: 'networkChange',
|
|
633
|
+
TRANSACTION_REQUEST: 'transactionRequest',
|
|
634
|
+
// Error events
|
|
635
|
+
ERROR: 'error',
|
|
636
|
+
};
|
|
637
|
+
/*
|
|
638
|
+
* ============================================================================
|
|
639
|
+
* CSS CLASS NAMES
|
|
640
|
+
* ============================================================================
|
|
641
|
+
*/
|
|
642
|
+
const CSS_CLASSES = {
|
|
643
|
+
// Root classes
|
|
644
|
+
WIDGET_ROOT: 'aomi-chat-widget',
|
|
645
|
+
WIDGET_CONTAINER: 'aomi-chat-container',
|
|
646
|
+
WIDGET_IFRAME: 'aomi-chat-iframe',
|
|
647
|
+
// Theme classes
|
|
648
|
+
THEME_LIGHT: 'aomi-theme-light',
|
|
649
|
+
THEME_DARK: 'aomi-theme-dark',
|
|
650
|
+
THEME_TERMINAL: 'aomi-theme-terminal',
|
|
651
|
+
THEME_NEON: 'aomi-theme-neon',
|
|
652
|
+
THEME_MINIMAL: 'aomi-theme-minimal',
|
|
653
|
+
// Mode classes
|
|
654
|
+
MODE_FULL: 'aomi-mode-full',
|
|
655
|
+
MODE_MINIMAL: 'aomi-mode-minimal',
|
|
656
|
+
MODE_COMPACT: 'aomi-mode-compact',
|
|
657
|
+
MODE_TERMINAL: 'aomi-mode-terminal',
|
|
658
|
+
// Component classes
|
|
659
|
+
CHAT_INTERFACE: 'aomi-chat-interface',
|
|
660
|
+
CHAT_HEADER: 'aomi-chat-header',
|
|
661
|
+
CHAT_TITLE: 'aomi-chat-title',
|
|
662
|
+
CHAT_BODY: 'aomi-chat-body',
|
|
663
|
+
STATUS_BADGE: 'aomi-status-badge',
|
|
664
|
+
MESSAGE_LIST: 'aomi-message-list',
|
|
665
|
+
MESSAGE_CONTAINER: 'aomi-message-container',
|
|
666
|
+
MESSAGE_BUBBLE: 'aomi-message',
|
|
667
|
+
MESSAGE_USER: 'aomi-message-user',
|
|
668
|
+
MESSAGE_ASSISTANT: 'aomi-message-assistant',
|
|
669
|
+
MESSAGE_SYSTEM: 'aomi-message-system',
|
|
670
|
+
ACTION_BAR: 'aomi-action-bar',
|
|
671
|
+
MESSAGE_INPUT: 'aomi-message-input',
|
|
672
|
+
INPUT_FORM: 'aomi-chat-input-form',
|
|
673
|
+
INPUT_FIELD: 'aomi-chat-input-field',
|
|
674
|
+
SEND_BUTTON: 'aomi-chat-send-button',
|
|
675
|
+
WALLET_STATUS: 'aomi-wallet-status',
|
|
676
|
+
TYPING_INDICATOR: 'aomi-typing-indicator',
|
|
677
|
+
// State classes
|
|
678
|
+
LOADING: 'aomi-loading',
|
|
679
|
+
ERROR: 'aomi-error',
|
|
680
|
+
DISABLED: 'aomi-disabled',
|
|
681
|
+
CONNECTED: 'aomi-connected',
|
|
682
|
+
DISCONNECTED: 'aomi-disconnected',
|
|
683
|
+
};
|
|
684
|
+
/*
|
|
685
|
+
* ============================================================================
|
|
686
|
+
* API ENDPOINTS
|
|
687
|
+
* ============================================================================
|
|
688
|
+
*/
|
|
689
|
+
const API_ENDPOINTS = {
|
|
690
|
+
CHAT: '/api/chat',
|
|
691
|
+
CHAT_STREAM: '/api/chat/stream',
|
|
692
|
+
STATE: '/api/state',
|
|
693
|
+
INTERRUPT: '/api/interrupt',
|
|
694
|
+
SYSTEM: '/api/system',
|
|
695
|
+
MCP_COMMAND: '/api/mcp-command',
|
|
696
|
+
HEALTH: '/health',
|
|
697
|
+
};
|
|
698
|
+
/*
|
|
699
|
+
* ============================================================================
|
|
700
|
+
* TIMING CONSTANTS
|
|
701
|
+
* ============================================================================
|
|
702
|
+
*/
|
|
703
|
+
const TIMING = {
|
|
704
|
+
TYPING_INDICATOR_DELAY: 100,
|
|
705
|
+
MESSAGE_ANIMATION_DURATION: 300,
|
|
706
|
+
CONNECTION_TIMEOUT: 10000,
|
|
707
|
+
RETRY_DELAY: 1000,
|
|
708
|
+
HEARTBEAT_INTERVAL: 30000,
|
|
709
|
+
SESSION_TIMEOUT: 3600000, // 1 hour
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
// Error types and classes for the Aomi Chat Widget
|
|
713
|
+
/*
|
|
714
|
+
* ============================================================================
|
|
715
|
+
* BASE ERROR CLASS
|
|
716
|
+
* ============================================================================
|
|
717
|
+
*/
|
|
718
|
+
class AomiChatError extends Error {
|
|
719
|
+
constructor(code, message, details) {
|
|
720
|
+
super(message);
|
|
721
|
+
this.name = 'AomiChatError';
|
|
722
|
+
this.code = code;
|
|
723
|
+
this.timestamp = new Date();
|
|
724
|
+
this.details = details;
|
|
725
|
+
// Maintains proper stack trace for where our error was thrown (only available on V8)
|
|
726
|
+
if (Error.captureStackTrace) {
|
|
727
|
+
Error.captureStackTrace(this, AomiChatError);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
toJSON() {
|
|
731
|
+
return {
|
|
732
|
+
name: this.name,
|
|
733
|
+
code: this.code,
|
|
734
|
+
message: this.message,
|
|
735
|
+
timestamp: this.timestamp.toISOString(),
|
|
736
|
+
details: this.details,
|
|
737
|
+
stack: this.stack,
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
toString() {
|
|
741
|
+
return `${this.name} [${this.code}]: ${this.message}`;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
/*
|
|
745
|
+
* ============================================================================
|
|
746
|
+
* SPECIFIC ERROR CLASSES
|
|
747
|
+
* ============================================================================
|
|
748
|
+
*/
|
|
749
|
+
class ConfigurationError extends AomiChatError {
|
|
750
|
+
constructor(message, details) {
|
|
751
|
+
super(ERROR_CODES.INVALID_CONFIG, message, details);
|
|
752
|
+
this.name = 'ConfigurationError';
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
class ConnectionError extends AomiChatError {
|
|
756
|
+
constructor(message, details) {
|
|
757
|
+
super(ERROR_CODES.CONNECTION_FAILED, message, details);
|
|
758
|
+
this.name = 'ConnectionError';
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
class WalletError extends AomiChatError {
|
|
762
|
+
constructor(code, message, details) {
|
|
763
|
+
super(code, message, details);
|
|
764
|
+
this.name = 'WalletError';
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
class TransactionError extends WalletError {
|
|
768
|
+
constructor(message, details) {
|
|
769
|
+
super(ERROR_CODES.TRANSACTION_FAILED, message, details);
|
|
770
|
+
this.name = 'TransactionError';
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
class ChatError extends AomiChatError {
|
|
774
|
+
constructor(code, message, details) {
|
|
775
|
+
super(code, message, details);
|
|
776
|
+
this.name = 'ChatError';
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
class RateLimitError extends ChatError {
|
|
780
|
+
constructor(message, details) {
|
|
781
|
+
super(ERROR_CODES.RATE_LIMITED, message, details);
|
|
782
|
+
this.name = 'RateLimitError';
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
class SessionError extends AomiChatError {
|
|
786
|
+
constructor(message, details) {
|
|
787
|
+
super(ERROR_CODES.SESSION_EXPIRED, message, details);
|
|
788
|
+
this.name = 'SessionError';
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
/*
|
|
792
|
+
* ============================================================================
|
|
793
|
+
* ERROR FACTORY FUNCTIONS
|
|
794
|
+
* ============================================================================
|
|
795
|
+
*/
|
|
796
|
+
function createConfigurationError(message, details) {
|
|
797
|
+
return new ConfigurationError(message, details);
|
|
798
|
+
}
|
|
799
|
+
function createConnectionError(message, details) {
|
|
800
|
+
return new ConnectionError(message, details);
|
|
801
|
+
}
|
|
802
|
+
function createWalletError(code, message, details) {
|
|
803
|
+
return new WalletError(code, message, details);
|
|
804
|
+
}
|
|
805
|
+
function createTransactionError(message, details) {
|
|
806
|
+
return new TransactionError(message, details);
|
|
807
|
+
}
|
|
808
|
+
function createChatError(code, message, details) {
|
|
809
|
+
return new ChatError(code, message, details);
|
|
810
|
+
}
|
|
811
|
+
function createRateLimitError(message, details) {
|
|
812
|
+
return new RateLimitError(message, details);
|
|
813
|
+
}
|
|
814
|
+
function createSessionError(message, details) {
|
|
815
|
+
return new SessionError(message, details);
|
|
816
|
+
}
|
|
817
|
+
/*
|
|
818
|
+
* ============================================================================
|
|
819
|
+
* ERROR HANDLING UTILITIES
|
|
820
|
+
* ============================================================================
|
|
821
|
+
*/
|
|
822
|
+
function isAomiChatError(error) {
|
|
823
|
+
return error instanceof AomiChatError;
|
|
824
|
+
}
|
|
825
|
+
function isConfigurationError(error) {
|
|
826
|
+
return error instanceof ConfigurationError;
|
|
827
|
+
}
|
|
828
|
+
function isConnectionError(error) {
|
|
829
|
+
return error instanceof ConnectionError;
|
|
830
|
+
}
|
|
831
|
+
function isWalletError(error) {
|
|
832
|
+
return error instanceof WalletError;
|
|
833
|
+
}
|
|
834
|
+
function isTransactionError(error) {
|
|
835
|
+
return error instanceof TransactionError;
|
|
836
|
+
}
|
|
837
|
+
function isChatError(error) {
|
|
838
|
+
return error instanceof ChatError;
|
|
839
|
+
}
|
|
840
|
+
function isRateLimitError(error) {
|
|
841
|
+
return error instanceof RateLimitError;
|
|
842
|
+
}
|
|
843
|
+
function isSessionError(error) {
|
|
844
|
+
return error instanceof SessionError;
|
|
845
|
+
}
|
|
846
|
+
/*
|
|
847
|
+
* ============================================================================
|
|
848
|
+
* ERROR SEVERITY LEVELS
|
|
849
|
+
* ============================================================================
|
|
850
|
+
*/
|
|
851
|
+
/* eslint-disable no-unused-vars, @typescript-eslint/no-unused-vars */
|
|
852
|
+
var ErrorSeverity;
|
|
853
|
+
(function (ErrorSeverity) {
|
|
854
|
+
ErrorSeverity["LOW"] = "low";
|
|
855
|
+
ErrorSeverity["MEDIUM"] = "medium";
|
|
856
|
+
ErrorSeverity["HIGH"] = "high";
|
|
857
|
+
ErrorSeverity["CRITICAL"] = "critical";
|
|
858
|
+
})(ErrorSeverity || (ErrorSeverity = {}));
|
|
859
|
+
/*
|
|
860
|
+
* ============================================================================
|
|
861
|
+
* ERROR MESSAGES
|
|
862
|
+
* ============================================================================
|
|
863
|
+
*/
|
|
864
|
+
({
|
|
865
|
+
[ERROR_CODES.INVALID_CONFIG]: 'Invalid widget configuration provided',
|
|
866
|
+
[ERROR_CODES.MISSING_APP_CODE]: 'App code is required for widget initialization',
|
|
867
|
+
[ERROR_CODES.INVALID_THEME]: 'Invalid theme configuration',
|
|
868
|
+
[ERROR_CODES.INVALID_DIMENSIONS]: 'Invalid widget dimensions',
|
|
869
|
+
[ERROR_CODES.CONNECTION_FAILED]: 'Failed to connect to the chat backend',
|
|
870
|
+
[ERROR_CODES.CONNECTION_TIMEOUT]: 'Connection timed out',
|
|
871
|
+
[ERROR_CODES.BACKEND_UNAVAILABLE]: 'Chat backend is currently unavailable',
|
|
872
|
+
[ERROR_CODES.AUTHENTICATION_FAILED]: 'Authentication failed',
|
|
873
|
+
[ERROR_CODES.WALLET_NOT_CONNECTED]: 'Wallet is not connected',
|
|
874
|
+
[ERROR_CODES.WALLET_CONNECTION_FAILED]: 'Failed to connect to wallet',
|
|
875
|
+
[ERROR_CODES.UNSUPPORTED_NETWORK]: 'Unsupported network',
|
|
876
|
+
[ERROR_CODES.TRANSACTION_FAILED]: 'Transaction failed',
|
|
877
|
+
[ERROR_CODES.TRANSACTION_REJECTED]: 'Transaction was rejected by user',
|
|
878
|
+
[ERROR_CODES.MESSAGE_TOO_LONG]: 'Message exceeds maximum length',
|
|
879
|
+
[ERROR_CODES.RATE_LIMITED]: 'Rate limit exceeded, please slow down',
|
|
880
|
+
[ERROR_CODES.INVALID_MESSAGE]: 'Invalid message format',
|
|
881
|
+
[ERROR_CODES.SESSION_EXPIRED]: 'Chat session has expired',
|
|
882
|
+
[ERROR_CODES.UNKNOWN_ERROR]: 'An unknown error occurred',
|
|
883
|
+
[ERROR_CODES.INITIALIZATION_FAILED]: 'Widget initialization failed',
|
|
884
|
+
[ERROR_CODES.PROVIDER_ERROR]: 'Provider error occurred',
|
|
885
|
+
});
|
|
886
|
+
/*
|
|
887
|
+
* ============================================================================
|
|
888
|
+
* ERROR RECOVERY STRATEGIES
|
|
889
|
+
* ============================================================================
|
|
890
|
+
*/
|
|
891
|
+
var RecoveryStrategy;
|
|
892
|
+
(function (RecoveryStrategy) {
|
|
893
|
+
RecoveryStrategy["RETRY"] = "retry";
|
|
894
|
+
RecoveryStrategy["RECONNECT"] = "reconnect";
|
|
895
|
+
RecoveryStrategy["REFRESH"] = "refresh";
|
|
896
|
+
RecoveryStrategy["FALLBACK"] = "fallback";
|
|
897
|
+
RecoveryStrategy["MANUAL"] = "manual";
|
|
898
|
+
RecoveryStrategy["NONE"] = "none";
|
|
899
|
+
})(RecoveryStrategy || (RecoveryStrategy = {}));
|
|
900
|
+
|
|
901
|
+
/* eslint-disable no-unused-vars, @typescript-eslint/no-unused-vars */
|
|
902
|
+
// Core type definitions for Aomi Chat Widget
|
|
903
|
+
/*
|
|
904
|
+
* ============================================================================
|
|
905
|
+
* WIDGET STATE TYPES
|
|
906
|
+
* ============================================================================
|
|
907
|
+
*/
|
|
908
|
+
var ConnectionStatus;
|
|
909
|
+
(function (ConnectionStatus) {
|
|
910
|
+
ConnectionStatus["CONNECTING"] = "connecting";
|
|
911
|
+
ConnectionStatus["CONNECTED"] = "connected";
|
|
912
|
+
ConnectionStatus["DISCONNECTED"] = "disconnected";
|
|
913
|
+
ConnectionStatus["ERROR"] = "error";
|
|
914
|
+
ConnectionStatus["RECONNECTING"] = "reconnecting";
|
|
915
|
+
})(ConnectionStatus || (ConnectionStatus = {}));
|
|
916
|
+
var ReadinessPhase;
|
|
917
|
+
(function (ReadinessPhase) {
|
|
918
|
+
ReadinessPhase["INITIALIZING"] = "initializing";
|
|
919
|
+
ReadinessPhase["CONNECTING_MCP"] = "connecting_mcp";
|
|
920
|
+
ReadinessPhase["VALIDATING_ANTHROPIC"] = "validating_anthropic";
|
|
921
|
+
ReadinessPhase["READY"] = "ready";
|
|
922
|
+
ReadinessPhase["MISSING_API_KEY"] = "missing_api_key";
|
|
923
|
+
ReadinessPhase["ERROR"] = "error";
|
|
924
|
+
})(ReadinessPhase || (ReadinessPhase = {}));
|
|
925
|
+
/* eslint-enable no-unused-vars, @typescript-eslint/no-unused-vars */
|
|
926
|
+
|
|
927
|
+
// Utility functions for the Aomi Chat Widget
|
|
928
|
+
/*
|
|
929
|
+
* ============================================================================
|
|
930
|
+
* CONFIGURATION UTILITIES
|
|
931
|
+
* ============================================================================
|
|
932
|
+
*/
|
|
933
|
+
/**
|
|
934
|
+
* Resolves a flexible configuration value based on chain ID and mode
|
|
935
|
+
*/
|
|
936
|
+
function resolveFlexibleConfig(config, chainId, mode) {
|
|
937
|
+
// If it's a simple value, return it
|
|
938
|
+
if (typeof config !== 'object' || config === null) {
|
|
939
|
+
return config;
|
|
940
|
+
}
|
|
941
|
+
// Check if it's a per-mode configuration
|
|
942
|
+
if (isPerModeConfig(config)) {
|
|
943
|
+
const modeValue = config[mode];
|
|
944
|
+
if (modeValue === undefined) {
|
|
945
|
+
return undefined;
|
|
946
|
+
}
|
|
947
|
+
// If the mode value is itself a per-network config
|
|
948
|
+
if (isPerNetworkConfig(modeValue)) {
|
|
949
|
+
return modeValue[chainId];
|
|
950
|
+
}
|
|
951
|
+
return modeValue;
|
|
952
|
+
}
|
|
953
|
+
// Check if it's a per-network configuration
|
|
954
|
+
if (isPerNetworkConfig(config)) {
|
|
955
|
+
const networkValue = config[chainId];
|
|
956
|
+
if (networkValue === undefined) {
|
|
957
|
+
return undefined;
|
|
958
|
+
}
|
|
959
|
+
// If the network value is itself a per-mode config
|
|
960
|
+
if (isPerModeConfig(networkValue)) {
|
|
961
|
+
return networkValue[mode];
|
|
962
|
+
}
|
|
963
|
+
return networkValue;
|
|
964
|
+
}
|
|
965
|
+
return config;
|
|
966
|
+
}
|
|
967
|
+
function isPerModeConfig(config) {
|
|
968
|
+
if (typeof config !== 'object' || config === null)
|
|
969
|
+
return false;
|
|
970
|
+
const modes = ['full', 'minimal', 'compact', 'terminal'];
|
|
971
|
+
const keys = Object.keys(config);
|
|
972
|
+
return keys.length > 0 && keys.every(key => modes.includes(key));
|
|
973
|
+
}
|
|
974
|
+
function isPerNetworkConfig(config) {
|
|
975
|
+
if (typeof config !== 'object' || config === null)
|
|
976
|
+
return false;
|
|
977
|
+
const chainIds = Object.keys(SUPPORTED_CHAINS).map(id => parseInt(id));
|
|
978
|
+
const keys = Object.keys(config).map(key => parseInt(key));
|
|
979
|
+
return keys.length > 0 && keys.every(key => chainIds.includes(key));
|
|
980
|
+
}
|
|
981
|
+
/*
|
|
982
|
+
* ============================================================================
|
|
983
|
+
* VALIDATION UTILITIES
|
|
984
|
+
* ============================================================================
|
|
985
|
+
*/
|
|
986
|
+
/**
|
|
987
|
+
* Validates widget configuration parameters
|
|
988
|
+
*/
|
|
989
|
+
function validateWidgetParams(params) {
|
|
990
|
+
const errors = [];
|
|
991
|
+
// Required fields
|
|
992
|
+
if (!params.appCode || typeof params.appCode !== 'string') {
|
|
993
|
+
errors.push('appCode is required and must be a string');
|
|
994
|
+
}
|
|
995
|
+
// Validate dimensions
|
|
996
|
+
if (params.width && !isValidDimension(params.width)) {
|
|
997
|
+
errors.push('width must be a valid CSS dimension (e.g., "400px", "100%")');
|
|
998
|
+
}
|
|
999
|
+
if (params.height && !isValidDimension(params.height)) {
|
|
1000
|
+
errors.push('height must be a valid CSS dimension (e.g., "600px", "100vh")');
|
|
1001
|
+
}
|
|
1002
|
+
if (params.maxHeight && (typeof params.maxHeight !== 'number' || params.maxHeight <= 0)) {
|
|
1003
|
+
errors.push('maxHeight must be a positive number');
|
|
1004
|
+
}
|
|
1005
|
+
// Validate chain ID
|
|
1006
|
+
if (params.chainId && !Object.keys(SUPPORTED_CHAINS).includes(params.chainId.toString())) {
|
|
1007
|
+
errors.push(`chainId must be one of: ${Object.keys(SUPPORTED_CHAINS).join(', ')}`);
|
|
1008
|
+
}
|
|
1009
|
+
// Validate supported chains
|
|
1010
|
+
if (params.supportedChains) {
|
|
1011
|
+
const invalidChains = params.supportedChains.filter(id => !Object.keys(SUPPORTED_CHAINS).includes(id.toString()));
|
|
1012
|
+
if (invalidChains.length > 0) {
|
|
1013
|
+
errors.push(`supportedChains contains invalid chain IDs: ${invalidChains.join(', ')}`);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
// Validate theme
|
|
1017
|
+
if (params.theme && !isValidTheme(params.theme)) {
|
|
1018
|
+
errors.push('theme must be a valid theme name or theme palette object');
|
|
1019
|
+
}
|
|
1020
|
+
// Validate mode
|
|
1021
|
+
if (params.mode && !['full', 'minimal', 'compact', 'terminal'].includes(params.mode)) {
|
|
1022
|
+
errors.push('mode must be one of: full, minimal, compact, terminal');
|
|
1023
|
+
}
|
|
1024
|
+
return errors;
|
|
1025
|
+
}
|
|
1026
|
+
function isValidDimension(dimension) {
|
|
1027
|
+
// Simple regex to validate CSS dimensions
|
|
1028
|
+
return /^(\d+(\.\d+)?(px|%|em|rem|vh|vw)|auto|inherit)$/.test(dimension);
|
|
1029
|
+
}
|
|
1030
|
+
function isValidTheme(theme) {
|
|
1031
|
+
if (typeof theme === 'string') {
|
|
1032
|
+
return Object.keys(PREDEFINED_THEMES).includes(theme);
|
|
1033
|
+
}
|
|
1034
|
+
if (typeof theme === 'object' && theme !== null) {
|
|
1035
|
+
return 'baseTheme' in theme && typeof theme.baseTheme === 'string';
|
|
1036
|
+
}
|
|
1037
|
+
return false;
|
|
1038
|
+
}
|
|
1039
|
+
/*
|
|
1040
|
+
* ============================================================================
|
|
1041
|
+
* SESSION UTILITIES
|
|
1042
|
+
* ============================================================================
|
|
1043
|
+
*/
|
|
1044
|
+
/**
|
|
1045
|
+
* Generates a unique session ID
|
|
1046
|
+
*/
|
|
1047
|
+
function generateSessionId() {
|
|
1048
|
+
// Use crypto.randomUUID if available (modern browsers)
|
|
1049
|
+
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
1050
|
+
return crypto.randomUUID();
|
|
1051
|
+
}
|
|
1052
|
+
// Fallback UUID v4 implementation
|
|
1053
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
1054
|
+
const r = Math.random() * 16 | 0;
|
|
1055
|
+
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
|
1056
|
+
return v.toString(16);
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
/**
|
|
1060
|
+
* Validates a session ID format
|
|
1061
|
+
*/
|
|
1062
|
+
function isValidSessionId(sessionId) {
|
|
1063
|
+
// UUID v4 format
|
|
1064
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
1065
|
+
return uuidRegex.test(sessionId);
|
|
1066
|
+
}
|
|
1067
|
+
/*
|
|
1068
|
+
* ============================================================================
|
|
1069
|
+
* URL UTILITIES
|
|
1070
|
+
* ============================================================================
|
|
1071
|
+
*/
|
|
1072
|
+
/**
|
|
1073
|
+
* Builds widget URL with parameters
|
|
1074
|
+
*/
|
|
1075
|
+
function buildWidgetUrl(baseUrl, params) {
|
|
1076
|
+
const url = new URL('/widget', baseUrl);
|
|
1077
|
+
// Add essential parameters
|
|
1078
|
+
url.searchParams.set('appCode', params.appCode);
|
|
1079
|
+
if (params.sessionId) {
|
|
1080
|
+
url.searchParams.set('sessionId', params.sessionId);
|
|
1081
|
+
}
|
|
1082
|
+
if (params.theme) {
|
|
1083
|
+
if (typeof params.theme === 'string') {
|
|
1084
|
+
url.searchParams.set('theme', params.theme);
|
|
1085
|
+
}
|
|
1086
|
+
else {
|
|
1087
|
+
// For complex theme objects, encode as JSON
|
|
1088
|
+
url.searchParams.set('palette', JSON.stringify(params.theme));
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
if (params.mode) {
|
|
1092
|
+
url.searchParams.set('mode', params.mode);
|
|
1093
|
+
}
|
|
1094
|
+
if (params.chainId) {
|
|
1095
|
+
url.searchParams.set('chainId', params.chainId.toString());
|
|
1096
|
+
}
|
|
1097
|
+
if (params.welcomeMessage) {
|
|
1098
|
+
url.searchParams.set('welcomeMessage', params.welcomeMessage);
|
|
1099
|
+
}
|
|
1100
|
+
if (params.placeholder) {
|
|
1101
|
+
url.searchParams.set('placeholder', params.placeholder);
|
|
1102
|
+
}
|
|
1103
|
+
// Boolean flags
|
|
1104
|
+
if (params.showWalletStatus === false) {
|
|
1105
|
+
url.searchParams.set('showWalletStatus', 'false');
|
|
1106
|
+
}
|
|
1107
|
+
if (params.showNetworkSelector === false) {
|
|
1108
|
+
url.searchParams.set('showNetworkSelector', 'false');
|
|
1109
|
+
}
|
|
1110
|
+
if (params.hideHeader) {
|
|
1111
|
+
url.searchParams.set('hideHeader', 'true');
|
|
1112
|
+
}
|
|
1113
|
+
if (params.hideFooter) {
|
|
1114
|
+
url.searchParams.set('hideFooter', 'true');
|
|
1115
|
+
}
|
|
1116
|
+
if (params.enableTransactions === false) {
|
|
1117
|
+
url.searchParams.set('enableTransactions', 'false');
|
|
1118
|
+
}
|
|
1119
|
+
if (params.requireWalletConnection) {
|
|
1120
|
+
url.searchParams.set('requireWalletConnection', 'true');
|
|
1121
|
+
}
|
|
1122
|
+
if (params.standaloneMode === false) {
|
|
1123
|
+
url.searchParams.set('standaloneMode', 'false');
|
|
1124
|
+
}
|
|
1125
|
+
return url.toString();
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Parses widget parameters from URL search params
|
|
1129
|
+
*/
|
|
1130
|
+
function parseWidgetParams(searchParams) {
|
|
1131
|
+
const params = {};
|
|
1132
|
+
const appCode = searchParams.get('appCode');
|
|
1133
|
+
if (appCode)
|
|
1134
|
+
params.appCode = appCode;
|
|
1135
|
+
const sessionId = searchParams.get('sessionId');
|
|
1136
|
+
if (sessionId)
|
|
1137
|
+
params.sessionId = sessionId;
|
|
1138
|
+
const theme = searchParams.get('theme');
|
|
1139
|
+
if (theme)
|
|
1140
|
+
params.theme = theme;
|
|
1141
|
+
const palette = searchParams.get('palette');
|
|
1142
|
+
if (palette) {
|
|
1143
|
+
try {
|
|
1144
|
+
params.theme = JSON.parse(palette);
|
|
1145
|
+
}
|
|
1146
|
+
catch {
|
|
1147
|
+
// Ignore invalid JSON
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
const mode = searchParams.get('mode');
|
|
1151
|
+
if (mode)
|
|
1152
|
+
params.mode = mode;
|
|
1153
|
+
const chainId = searchParams.get('chainId');
|
|
1154
|
+
if (chainId) {
|
|
1155
|
+
const parsed = parseInt(chainId);
|
|
1156
|
+
if (!isNaN(parsed))
|
|
1157
|
+
params.chainId = parsed;
|
|
1158
|
+
}
|
|
1159
|
+
const welcomeMessage = searchParams.get('welcomeMessage');
|
|
1160
|
+
if (welcomeMessage)
|
|
1161
|
+
params.welcomeMessage = welcomeMessage;
|
|
1162
|
+
const placeholder = searchParams.get('placeholder');
|
|
1163
|
+
if (placeholder)
|
|
1164
|
+
params.placeholder = placeholder;
|
|
1165
|
+
// Boolean parameters
|
|
1166
|
+
const showWalletStatus = searchParams.get('showWalletStatus');
|
|
1167
|
+
if (showWalletStatus === 'false')
|
|
1168
|
+
params.showWalletStatus = false;
|
|
1169
|
+
const showNetworkSelector = searchParams.get('showNetworkSelector');
|
|
1170
|
+
if (showNetworkSelector === 'false')
|
|
1171
|
+
params.showNetworkSelector = false;
|
|
1172
|
+
const hideHeader = searchParams.get('hideHeader');
|
|
1173
|
+
if (hideHeader === 'true')
|
|
1174
|
+
params.hideHeader = true;
|
|
1175
|
+
const hideFooter = searchParams.get('hideFooter');
|
|
1176
|
+
if (hideFooter === 'true')
|
|
1177
|
+
params.hideFooter = true;
|
|
1178
|
+
const enableTransactions = searchParams.get('enableTransactions');
|
|
1179
|
+
if (enableTransactions === 'false')
|
|
1180
|
+
params.enableTransactions = false;
|
|
1181
|
+
const requireWalletConnection = searchParams.get('requireWalletConnection');
|
|
1182
|
+
if (requireWalletConnection === 'true')
|
|
1183
|
+
params.requireWalletConnection = true;
|
|
1184
|
+
const standaloneMode = searchParams.get('standaloneMode');
|
|
1185
|
+
if (standaloneMode === 'false')
|
|
1186
|
+
params.standaloneMode = false;
|
|
1187
|
+
return params;
|
|
1188
|
+
}
|
|
1189
|
+
/*
|
|
1190
|
+
* ============================================================================
|
|
1191
|
+
* DOM UTILITIES
|
|
1192
|
+
* ============================================================================
|
|
1193
|
+
*/
|
|
1194
|
+
/**
|
|
1195
|
+
* Creates a DOM element with classes and attributes
|
|
1196
|
+
*/
|
|
1197
|
+
function createElement(tagName, options = {}) {
|
|
1198
|
+
const element = document.createElement(tagName);
|
|
1199
|
+
// Set classes
|
|
1200
|
+
if (options.className) {
|
|
1201
|
+
if (Array.isArray(options.className)) {
|
|
1202
|
+
element.classList.add(...options.className);
|
|
1203
|
+
}
|
|
1204
|
+
else {
|
|
1205
|
+
element.className = options.className;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
// Set attributes
|
|
1209
|
+
if (options.attributes) {
|
|
1210
|
+
Object.entries(options.attributes).forEach(([key, value]) => {
|
|
1211
|
+
element.setAttribute(key, value);
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
// Set styles
|
|
1215
|
+
if (options.styles) {
|
|
1216
|
+
Object.assign(element.style, options.styles);
|
|
1217
|
+
}
|
|
1218
|
+
// Add children
|
|
1219
|
+
if (options.children) {
|
|
1220
|
+
options.children.forEach(child => {
|
|
1221
|
+
if (typeof child === 'string') {
|
|
1222
|
+
element.appendChild(document.createTextNode(child));
|
|
1223
|
+
}
|
|
1224
|
+
else {
|
|
1225
|
+
element.appendChild(child);
|
|
1226
|
+
}
|
|
1227
|
+
});
|
|
1228
|
+
}
|
|
1229
|
+
return element;
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Checks if an element is visible in the viewport
|
|
1233
|
+
*/
|
|
1234
|
+
function isElementVisible(element) {
|
|
1235
|
+
const rect = element.getBoundingClientRect();
|
|
1236
|
+
return (rect.top >= 0 &&
|
|
1237
|
+
rect.left >= 0 &&
|
|
1238
|
+
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
1239
|
+
rect.right <= (window.innerWidth || document.documentElement.clientWidth));
|
|
1240
|
+
}
|
|
1241
|
+
/*
|
|
1242
|
+
* ============================================================================
|
|
1243
|
+
* TYPE GUARDS
|
|
1244
|
+
* ============================================================================
|
|
1245
|
+
*/
|
|
1246
|
+
/**
|
|
1247
|
+
* Type guard to check if a value is a valid Ethereum address
|
|
1248
|
+
*/
|
|
1249
|
+
function isEthereumAddress(value) {
|
|
1250
|
+
if (typeof value !== 'string')
|
|
1251
|
+
return false;
|
|
1252
|
+
return /^0x[a-fA-F0-9]{40}$/.test(value);
|
|
1253
|
+
}
|
|
1254
|
+
/**
|
|
1255
|
+
* Type guard to check if a value is a valid transaction hash
|
|
1256
|
+
*/
|
|
1257
|
+
function isTransactionHash(value) {
|
|
1258
|
+
if (typeof value !== 'string')
|
|
1259
|
+
return false;
|
|
1260
|
+
return /^0x[a-fA-F0-9]{64}$/.test(value);
|
|
1261
|
+
}
|
|
1262
|
+
/**
|
|
1263
|
+
* Type guard to check if an object has a specific property
|
|
1264
|
+
*/
|
|
1265
|
+
function hasProperty(obj, prop) {
|
|
1266
|
+
return prop in obj;
|
|
1267
|
+
}
|
|
1268
|
+
/*
|
|
1269
|
+
* ============================================================================
|
|
1270
|
+
* ASYNC UTILITIES
|
|
1271
|
+
* ============================================================================
|
|
1272
|
+
*/
|
|
1273
|
+
/**
|
|
1274
|
+
* Delays execution for a specified number of milliseconds
|
|
1275
|
+
*/
|
|
1276
|
+
function delay(ms) {
|
|
1277
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* Creates a promise that rejects after a timeout
|
|
1281
|
+
*/
|
|
1282
|
+
function withTimeout(promise, timeoutMs) {
|
|
1283
|
+
return Promise.race([
|
|
1284
|
+
promise,
|
|
1285
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Operation timed out')), timeoutMs)),
|
|
1286
|
+
]);
|
|
1287
|
+
}
|
|
1288
|
+
/**
|
|
1289
|
+
* Retries a function with exponential backoff
|
|
1290
|
+
*/
|
|
1291
|
+
async function retry(fn, options = {}) {
|
|
1292
|
+
const { maxAttempts = 3, delay: initialDelay = 1000, backoffFactor = 2, shouldRetry = () => true, } = options;
|
|
1293
|
+
let lastError;
|
|
1294
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
1295
|
+
try {
|
|
1296
|
+
return await fn();
|
|
1297
|
+
}
|
|
1298
|
+
catch (error) {
|
|
1299
|
+
lastError = error;
|
|
1300
|
+
if (attempt === maxAttempts || !shouldRetry(error)) {
|
|
1301
|
+
throw error;
|
|
1302
|
+
}
|
|
1303
|
+
const delayMs = initialDelay * Math.pow(backoffFactor, attempt - 1);
|
|
1304
|
+
await delay(delayMs);
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
throw lastError;
|
|
1308
|
+
}
|
|
1309
|
+
/*
|
|
1310
|
+
* ============================================================================
|
|
1311
|
+
* ENVIRONMENT UTILITIES
|
|
1312
|
+
* ============================================================================
|
|
1313
|
+
*/
|
|
1314
|
+
/**
|
|
1315
|
+
* Detects if running in a browser environment
|
|
1316
|
+
*/
|
|
1317
|
+
function isBrowser() {
|
|
1318
|
+
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1321
|
+
* Detects if running on a mobile device
|
|
1322
|
+
*/
|
|
1323
|
+
function isMobile() {
|
|
1324
|
+
if (!isBrowser())
|
|
1325
|
+
return false;
|
|
1326
|
+
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
1327
|
+
}
|
|
1328
|
+
/**
|
|
1329
|
+
* Gets the current viewport dimensions
|
|
1330
|
+
*/
|
|
1331
|
+
function getViewportDimensions() {
|
|
1332
|
+
if (!isBrowser()) {
|
|
1333
|
+
return { width: 0, height: 0 };
|
|
1334
|
+
}
|
|
1335
|
+
return {
|
|
1336
|
+
width: window.innerWidth || document.documentElement.clientWidth,
|
|
1337
|
+
height: window.innerHeight || document.documentElement.clientHeight,
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
/*
|
|
1341
|
+
* ============================================================================
|
|
1342
|
+
* FORMATTING UTILITIES
|
|
1343
|
+
* ============================================================================
|
|
1344
|
+
*/
|
|
1345
|
+
/**
|
|
1346
|
+
* Formats a timestamp as a human-readable string
|
|
1347
|
+
*/
|
|
1348
|
+
function formatTimestamp(timestamp) {
|
|
1349
|
+
const now = new Date();
|
|
1350
|
+
const diff = now.getTime() - timestamp.getTime();
|
|
1351
|
+
if (diff < 60000) { // Less than 1 minute
|
|
1352
|
+
return 'just now';
|
|
1353
|
+
}
|
|
1354
|
+
else if (diff < 3600000) { // Less than 1 hour
|
|
1355
|
+
const minutes = Math.floor(diff / 60000);
|
|
1356
|
+
return `${minutes} minute${minutes > 1 ? 's' : ''} ago`;
|
|
1357
|
+
}
|
|
1358
|
+
else if (diff < 86400000) { // Less than 1 day
|
|
1359
|
+
const hours = Math.floor(diff / 3600000);
|
|
1360
|
+
return `${hours} hour${hours > 1 ? 's' : ''} ago`;
|
|
1361
|
+
}
|
|
1362
|
+
else {
|
|
1363
|
+
return timestamp.toLocaleDateString();
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
/**
|
|
1367
|
+
* Truncates an Ethereum address for display
|
|
1368
|
+
*/
|
|
1369
|
+
function truncateAddress(address, startChars = 6, endChars = 4) {
|
|
1370
|
+
if (!isEthereumAddress(address))
|
|
1371
|
+
return address;
|
|
1372
|
+
if (address.length <= startChars + endChars)
|
|
1373
|
+
return address;
|
|
1374
|
+
return `${address.slice(0, startChars)}...${address.slice(-endChars)}`;
|
|
1375
|
+
}
|
|
1376
|
+
/**
|
|
1377
|
+
* Formats a number with appropriate decimal places
|
|
1378
|
+
*/
|
|
1379
|
+
function formatNumber(value, options = {}) {
|
|
1380
|
+
const { decimals = 2, compact = false, currency } = options;
|
|
1381
|
+
const formatter = new Intl.NumberFormat('en-US', {
|
|
1382
|
+
minimumFractionDigits: decimals,
|
|
1383
|
+
maximumFractionDigits: decimals,
|
|
1384
|
+
notation: compact ? 'compact' : 'standard',
|
|
1385
|
+
style: currency ? 'currency' : 'decimal',
|
|
1386
|
+
currency: currency || 'USD',
|
|
1387
|
+
});
|
|
1388
|
+
return formatter.format(value);
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
/*
|
|
1392
|
+
* ChatManager - Aligns widget behaviour with the updated frontend core logic
|
|
1393
|
+
*/
|
|
1394
|
+
class ChatManager extends EventEmitter {
|
|
1395
|
+
constructor(config = {}) {
|
|
1396
|
+
super();
|
|
1397
|
+
this.eventSource = null;
|
|
1398
|
+
this.reconnectAttempt = 0;
|
|
1399
|
+
this.reconnectTimer = null;
|
|
1400
|
+
this.heartbeatTimer = null;
|
|
1401
|
+
this.lastPendingTransactionRaw = null;
|
|
1402
|
+
this.config = {
|
|
1403
|
+
backendUrl: config.backendUrl || 'http://localhost:8080',
|
|
1404
|
+
sessionId: config.sessionId || generateSessionId(),
|
|
1405
|
+
maxMessageLength: config.maxMessageLength || 2000,
|
|
1406
|
+
reconnectAttempts: config.reconnectAttempts || 5,
|
|
1407
|
+
reconnectDelay: config.reconnectDelay || 3000,
|
|
1408
|
+
};
|
|
1409
|
+
this.state = {
|
|
1410
|
+
messages: [],
|
|
1411
|
+
isTyping: false,
|
|
1412
|
+
isProcessing: false,
|
|
1413
|
+
connectionStatus: ConnectionStatus.DISCONNECTED,
|
|
1414
|
+
readiness: {
|
|
1415
|
+
phase: ReadinessPhase.INITIALIZING,
|
|
1416
|
+
},
|
|
1417
|
+
walletState: {
|
|
1418
|
+
isConnected: false,
|
|
1419
|
+
},
|
|
1420
|
+
sessionId: this.config.sessionId,
|
|
1421
|
+
pendingTransaction: undefined,
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
/*
|
|
1425
|
+
* ============================================================================
|
|
1426
|
+
* PUBLIC API
|
|
1427
|
+
* ============================================================================
|
|
1428
|
+
*/
|
|
1429
|
+
getState() {
|
|
1430
|
+
return {
|
|
1431
|
+
...this.state,
|
|
1432
|
+
messages: [...this.state.messages],
|
|
1433
|
+
walletState: { ...this.state.walletState },
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
getSessionId() {
|
|
1437
|
+
return this.config.sessionId;
|
|
1438
|
+
}
|
|
1439
|
+
setSessionId(sessionId) {
|
|
1440
|
+
this.config.sessionId = sessionId;
|
|
1441
|
+
this.state.sessionId = sessionId;
|
|
1442
|
+
if (this.state.connectionStatus === ConnectionStatus.CONNECTED) {
|
|
1443
|
+
void this.connectSSE();
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
async connectSSE() {
|
|
1447
|
+
this.setConnectionStatus(ConnectionStatus.CONNECTING);
|
|
1448
|
+
this.updateReadiness({ phase: ReadinessPhase.CONNECTING_MCP });
|
|
1449
|
+
this.teardownEventSource();
|
|
1450
|
+
try {
|
|
1451
|
+
const streamUrl = `${this.config.backendUrl}${API_ENDPOINTS.CHAT_STREAM}?session_id=${encodeURIComponent(this.config.sessionId)}`;
|
|
1452
|
+
this.eventSource = new EventSource(streamUrl);
|
|
1453
|
+
this.eventSource.onopen = () => {
|
|
1454
|
+
this.setConnectionStatus(ConnectionStatus.CONNECTED);
|
|
1455
|
+
this.reconnectAttempt = 0;
|
|
1456
|
+
this.startHeartbeat();
|
|
1457
|
+
void this.refreshState();
|
|
1458
|
+
};
|
|
1459
|
+
this.eventSource.onmessage = (event) => {
|
|
1460
|
+
try {
|
|
1461
|
+
const payload = JSON.parse(event.data);
|
|
1462
|
+
this.processBackendPayload(payload);
|
|
1463
|
+
}
|
|
1464
|
+
catch (error) {
|
|
1465
|
+
console.error('Failed to parse SSE payload:', error);
|
|
1466
|
+
this.emit('error', createChatError(ERROR_CODES.INVALID_MESSAGE, 'Invalid message format'));
|
|
1467
|
+
}
|
|
1468
|
+
};
|
|
1469
|
+
this.eventSource.onerror = (event) => {
|
|
1470
|
+
console.error('SSE connection error:', event);
|
|
1471
|
+
if (this.state.isProcessing) {
|
|
1472
|
+
this.state.isProcessing = false;
|
|
1473
|
+
this.state.isTyping = false;
|
|
1474
|
+
this.emitStateChange();
|
|
1475
|
+
}
|
|
1476
|
+
this.handleConnectionError();
|
|
1477
|
+
void this.refreshState();
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
catch (error) {
|
|
1481
|
+
console.error('Failed to create SSE connection:', error);
|
|
1482
|
+
this.emit('error', createConnectionError('Failed to establish connection'));
|
|
1483
|
+
this.handleConnectionError();
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
disconnectSSE() {
|
|
1487
|
+
this.teardownEventSource();
|
|
1488
|
+
this.setConnectionStatus(ConnectionStatus.DISCONNECTED);
|
|
1489
|
+
}
|
|
1490
|
+
async sendMessage(message) {
|
|
1491
|
+
const content = message.trim();
|
|
1492
|
+
if (!content) {
|
|
1493
|
+
throw createChatError(ERROR_CODES.INVALID_MESSAGE, 'Message cannot be empty');
|
|
1494
|
+
}
|
|
1495
|
+
if (content.length > (this.config.maxMessageLength ?? 0)) {
|
|
1496
|
+
throw createChatError(ERROR_CODES.MESSAGE_TOO_LONG, `Message exceeds ${this.config.maxMessageLength} characters`);
|
|
1497
|
+
}
|
|
1498
|
+
if (this.state.connectionStatus !== ConnectionStatus.CONNECTED) {
|
|
1499
|
+
throw createConnectionError('Not connected to backend');
|
|
1500
|
+
}
|
|
1501
|
+
try {
|
|
1502
|
+
const response = await this.postToBackend(API_ENDPOINTS.CHAT, {
|
|
1503
|
+
message: content,
|
|
1504
|
+
session_id: this.config.sessionId,
|
|
1505
|
+
});
|
|
1506
|
+
this.processBackendPayload(response);
|
|
1507
|
+
}
|
|
1508
|
+
catch (error) {
|
|
1509
|
+
console.error('Failed to send message:', error);
|
|
1510
|
+
throw createChatError(ERROR_CODES.UNKNOWN_ERROR, 'Failed to send message');
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
async sendSystemMessage(message) {
|
|
1514
|
+
try {
|
|
1515
|
+
const response = await this.postToBackend(API_ENDPOINTS.SYSTEM, {
|
|
1516
|
+
message,
|
|
1517
|
+
session_id: this.config.sessionId,
|
|
1518
|
+
});
|
|
1519
|
+
this.processBackendPayload(response);
|
|
1520
|
+
}
|
|
1521
|
+
catch (error) {
|
|
1522
|
+
console.error('Failed to send system message:', error);
|
|
1523
|
+
throw createChatError(ERROR_CODES.UNKNOWN_ERROR, 'Failed to send system message');
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
async interrupt() {
|
|
1527
|
+
try {
|
|
1528
|
+
const response = await this.postToBackend(API_ENDPOINTS.INTERRUPT, {
|
|
1529
|
+
session_id: this.config.sessionId,
|
|
1530
|
+
});
|
|
1531
|
+
this.processBackendPayload(response);
|
|
1532
|
+
}
|
|
1533
|
+
catch (error) {
|
|
1534
|
+
console.error('Failed to interrupt:', error);
|
|
1535
|
+
throw createChatError(ERROR_CODES.UNKNOWN_ERROR, 'Failed to interrupt processing');
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
async sendNetworkSwitchRequest(networkName) {
|
|
1539
|
+
try {
|
|
1540
|
+
await this.sendSystemMessage(`Detected user's wallet connected to ${networkName} network`);
|
|
1541
|
+
return {
|
|
1542
|
+
success: true,
|
|
1543
|
+
message: `Network switch system message sent for ${networkName}`,
|
|
1544
|
+
data: { network: networkName },
|
|
1545
|
+
};
|
|
1546
|
+
}
|
|
1547
|
+
catch (error) {
|
|
1548
|
+
const err = error instanceof Error ? error.message : String(error);
|
|
1549
|
+
return {
|
|
1550
|
+
success: false,
|
|
1551
|
+
message: err,
|
|
1552
|
+
};
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
async sendTransactionResult(success, transactionHash, error) {
|
|
1556
|
+
const message = success
|
|
1557
|
+
? `Transaction sent: ${transactionHash}`
|
|
1558
|
+
: `Transaction rejected by user${error ? `: ${error}` : ''}`;
|
|
1559
|
+
try {
|
|
1560
|
+
await this.sendSystemMessage(message);
|
|
1561
|
+
}
|
|
1562
|
+
catch (err) {
|
|
1563
|
+
console.error('Failed to send transaction result:', err);
|
|
1564
|
+
}
|
|
1565
|
+
finally {
|
|
1566
|
+
this.clearPendingTransaction();
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
clearPendingTransaction() {
|
|
1570
|
+
if (this.state.pendingTransaction) {
|
|
1571
|
+
this.state.pendingTransaction = undefined;
|
|
1572
|
+
this.lastPendingTransactionRaw = null;
|
|
1573
|
+
this.emitStateChange();
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
clearMessages() {
|
|
1577
|
+
if (this.state.messages.length === 0)
|
|
1578
|
+
return;
|
|
1579
|
+
this.state.messages = [];
|
|
1580
|
+
this.emitStateChange();
|
|
1581
|
+
}
|
|
1582
|
+
updateWalletState(walletState) {
|
|
1583
|
+
this.state.walletState = { ...this.state.walletState, ...walletState };
|
|
1584
|
+
this.emitStateChange();
|
|
1585
|
+
}
|
|
1586
|
+
destroy() {
|
|
1587
|
+
this.disconnectSSE();
|
|
1588
|
+
this.removeAllListeners();
|
|
1589
|
+
}
|
|
1590
|
+
/*
|
|
1591
|
+
* ============================================================================
|
|
1592
|
+
* INTERNAL STATE HANDLING
|
|
1593
|
+
* ============================================================================
|
|
1594
|
+
*/
|
|
1595
|
+
processBackendPayload(payload) {
|
|
1596
|
+
if (!payload) {
|
|
1597
|
+
return;
|
|
1598
|
+
}
|
|
1599
|
+
const previousMessages = this.state.messages;
|
|
1600
|
+
let stateChanged = false;
|
|
1601
|
+
if (Array.isArray(payload.messages)) {
|
|
1602
|
+
const nextMessages = this.buildMessages(payload.messages, previousMessages);
|
|
1603
|
+
this.state.messages = nextMessages;
|
|
1604
|
+
stateChanged = true;
|
|
1605
|
+
nextMessages.forEach((message, index) => {
|
|
1606
|
+
const previous = previousMessages[index];
|
|
1607
|
+
const isNewMessage = !previous || previous.id !== message.id;
|
|
1608
|
+
const contentChanged = previous && previous.content !== message.content;
|
|
1609
|
+
const toolStreamChanged = previous && !areToolStreamsEqual(previous.toolStream, message.toolStream);
|
|
1610
|
+
if (isNewMessage || contentChanged || toolStreamChanged) {
|
|
1611
|
+
this.emit('message', message);
|
|
1612
|
+
}
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1615
|
+
else if (payload.messages === null) {
|
|
1616
|
+
if (previousMessages.length > 0) {
|
|
1617
|
+
this.state.messages = [];
|
|
1618
|
+
stateChanged = true;
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
else if (payload.messages !== undefined) {
|
|
1622
|
+
console.error('Received malformed messages payload from backend:', payload.messages);
|
|
1623
|
+
}
|
|
1624
|
+
if (payload.is_processing !== undefined) {
|
|
1625
|
+
const processing = Boolean(payload.is_processing);
|
|
1626
|
+
if (processing !== this.state.isProcessing) {
|
|
1627
|
+
this.state.isProcessing = processing;
|
|
1628
|
+
stateChanged = true;
|
|
1629
|
+
}
|
|
1630
|
+
if (this.state.isTyping !== processing) {
|
|
1631
|
+
this.state.isTyping = processing;
|
|
1632
|
+
stateChanged = true;
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
if (payload.readiness !== undefined) {
|
|
1636
|
+
const readiness = this.normaliseReadiness(payload.readiness);
|
|
1637
|
+
if (readiness) {
|
|
1638
|
+
const current = this.state.readiness;
|
|
1639
|
+
if (current.phase !== readiness.phase ||
|
|
1640
|
+
current.detail !== readiness.detail) {
|
|
1641
|
+
this.state.readiness = readiness;
|
|
1642
|
+
stateChanged = true;
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
if (payload.pending_wallet_tx !== undefined) {
|
|
1647
|
+
if (payload.pending_wallet_tx === null) {
|
|
1648
|
+
if (this.state.pendingTransaction) {
|
|
1649
|
+
this.state.pendingTransaction = undefined;
|
|
1650
|
+
this.lastPendingTransactionRaw = null;
|
|
1651
|
+
stateChanged = true;
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
else if (payload.pending_wallet_tx !== this.lastPendingTransactionRaw) {
|
|
1655
|
+
try {
|
|
1656
|
+
const transaction = JSON.parse(payload.pending_wallet_tx);
|
|
1657
|
+
this.state.pendingTransaction = transaction;
|
|
1658
|
+
this.lastPendingTransactionRaw = payload.pending_wallet_tx;
|
|
1659
|
+
this.emit('transactionRequest', transaction);
|
|
1660
|
+
stateChanged = true;
|
|
1661
|
+
}
|
|
1662
|
+
catch (error) {
|
|
1663
|
+
console.error('Failed to parse wallet transaction payload:', error);
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
if ('pending_wallet_tx' in payload) {
|
|
1668
|
+
const raw = payload.pending_wallet_tx ?? null;
|
|
1669
|
+
if (raw === null) {
|
|
1670
|
+
if (this.state.pendingTransaction) {
|
|
1671
|
+
this.state.pendingTransaction = undefined;
|
|
1672
|
+
this.lastPendingTransactionRaw = null;
|
|
1673
|
+
stateChanged = true;
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
else if (raw !== this.lastPendingTransactionRaw) {
|
|
1677
|
+
this.lastPendingTransactionRaw = raw;
|
|
1678
|
+
try {
|
|
1679
|
+
const parsed = JSON.parse(raw);
|
|
1680
|
+
this.state.pendingTransaction = parsed;
|
|
1681
|
+
stateChanged = true;
|
|
1682
|
+
this.emit('transactionRequest', parsed);
|
|
1683
|
+
}
|
|
1684
|
+
catch (error) {
|
|
1685
|
+
console.error('Failed to parse wallet transaction request:', error);
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
if (stateChanged) {
|
|
1690
|
+
this.emitStateChange();
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
buildMessages(messages, previousMessages) {
|
|
1694
|
+
return messages
|
|
1695
|
+
.filter((msg) => Boolean(msg))
|
|
1696
|
+
.map((msg, index) => {
|
|
1697
|
+
const type = this.normaliseSender(msg.sender);
|
|
1698
|
+
const content = msg.content ?? '';
|
|
1699
|
+
const timestamp = this.parseTimestamp(msg.timestamp);
|
|
1700
|
+
const toolStream = normaliseToolStream(msg.tool_stream);
|
|
1701
|
+
const previous = previousMessages[index];
|
|
1702
|
+
const id = previous ? previous.id : generateSessionId();
|
|
1703
|
+
return {
|
|
1704
|
+
id,
|
|
1705
|
+
type,
|
|
1706
|
+
content,
|
|
1707
|
+
timestamp,
|
|
1708
|
+
toolStream,
|
|
1709
|
+
};
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
normaliseSender(sender) {
|
|
1713
|
+
switch (sender) {
|
|
1714
|
+
case 'user':
|
|
1715
|
+
return 'user';
|
|
1716
|
+
case 'system':
|
|
1717
|
+
return 'system';
|
|
1718
|
+
default:
|
|
1719
|
+
return 'assistant';
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
parseTimestamp(timestamp) {
|
|
1723
|
+
if (!timestamp) {
|
|
1724
|
+
return new Date();
|
|
1725
|
+
}
|
|
1726
|
+
const parsed = new Date(timestamp);
|
|
1727
|
+
if (Number.isNaN(parsed.valueOf())) {
|
|
1728
|
+
return new Date();
|
|
1729
|
+
}
|
|
1730
|
+
return parsed;
|
|
1731
|
+
}
|
|
1732
|
+
normaliseReadiness(payload) {
|
|
1733
|
+
if (!payload || typeof payload.phase !== 'string') {
|
|
1734
|
+
return null;
|
|
1735
|
+
}
|
|
1736
|
+
const detailRaw = typeof payload.detail === 'string' && payload.detail.trim().length > 0
|
|
1737
|
+
? payload.detail.trim()
|
|
1738
|
+
: typeof payload.message === 'string' && payload.message.trim().length > 0
|
|
1739
|
+
? payload.message.trim()
|
|
1740
|
+
: undefined;
|
|
1741
|
+
switch (payload.phase) {
|
|
1742
|
+
case 'connecting_mcp':
|
|
1743
|
+
return { phase: ReadinessPhase.CONNECTING_MCP, detail: detailRaw };
|
|
1744
|
+
case 'validating_anthropic':
|
|
1745
|
+
return { phase: ReadinessPhase.VALIDATING_ANTHROPIC, detail: detailRaw };
|
|
1746
|
+
case 'ready':
|
|
1747
|
+
return { phase: ReadinessPhase.READY, detail: detailRaw };
|
|
1748
|
+
case 'missing_api_key':
|
|
1749
|
+
return { phase: ReadinessPhase.MISSING_API_KEY, detail: detailRaw };
|
|
1750
|
+
case 'error':
|
|
1751
|
+
return { phase: ReadinessPhase.ERROR, detail: detailRaw };
|
|
1752
|
+
default:
|
|
1753
|
+
return { phase: ReadinessPhase.ERROR, detail: detailRaw };
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
updateReadiness(readiness) {
|
|
1757
|
+
this.state.readiness = readiness;
|
|
1758
|
+
this.emitStateChange();
|
|
1759
|
+
}
|
|
1760
|
+
setConnectionStatus(status) {
|
|
1761
|
+
if (this.state.connectionStatus !== status) {
|
|
1762
|
+
this.state.connectionStatus = status;
|
|
1763
|
+
this.emit('connectionChange', status);
|
|
1764
|
+
this.emitStateChange();
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
emitStateChange() {
|
|
1768
|
+
this.emit('stateChange', this.getState());
|
|
1769
|
+
}
|
|
1770
|
+
/*
|
|
1771
|
+
* ============================================================================
|
|
1772
|
+
* BACKEND COMMUNICATION
|
|
1773
|
+
* ============================================================================
|
|
1774
|
+
*/
|
|
1775
|
+
async postToBackend(endpoint, data) {
|
|
1776
|
+
const url = `${this.config.backendUrl}${endpoint}`;
|
|
1777
|
+
try {
|
|
1778
|
+
const response = await withTimeout(fetch(url, {
|
|
1779
|
+
method: 'POST',
|
|
1780
|
+
headers: {
|
|
1781
|
+
'Content-Type': 'application/json',
|
|
1782
|
+
},
|
|
1783
|
+
body: JSON.stringify(data),
|
|
1784
|
+
}), TIMING.CONNECTION_TIMEOUT);
|
|
1785
|
+
if (!response.ok) {
|
|
1786
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
1787
|
+
}
|
|
1788
|
+
return await response.json();
|
|
1789
|
+
}
|
|
1790
|
+
catch (error) {
|
|
1791
|
+
if (error instanceof Error && error.message.includes('timed out')) {
|
|
1792
|
+
throw createConnectionError('Request timed out');
|
|
1793
|
+
}
|
|
1794
|
+
throw createConnectionError(`Request failed: ${error}`);
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
async refreshState() {
|
|
1798
|
+
try {
|
|
1799
|
+
const response = await withTimeout(fetch(`${this.config.backendUrl}${API_ENDPOINTS.STATE}?session_id=${encodeURIComponent(this.config.sessionId)}`), TIMING.CONNECTION_TIMEOUT);
|
|
1800
|
+
if (!response.ok) {
|
|
1801
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
1802
|
+
}
|
|
1803
|
+
const payload = await response.json();
|
|
1804
|
+
this.processBackendPayload(payload);
|
|
1805
|
+
}
|
|
1806
|
+
catch (error) {
|
|
1807
|
+
console.warn('Failed to refresh chat state:', error);
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
/*
|
|
1811
|
+
* ============================================================================
|
|
1812
|
+
* CONNECTION MANAGEMENT
|
|
1813
|
+
* ============================================================================
|
|
1814
|
+
*/
|
|
1815
|
+
handleConnectionError() {
|
|
1816
|
+
this.setConnectionStatus(ConnectionStatus.ERROR);
|
|
1817
|
+
this.stopHeartbeat();
|
|
1818
|
+
if (this.reconnectAttempt < (this.config.reconnectAttempts ?? 0)) {
|
|
1819
|
+
this.scheduleReconnect();
|
|
1820
|
+
}
|
|
1821
|
+
else {
|
|
1822
|
+
this.updateReadiness({ phase: ReadinessPhase.ERROR, detail: 'Connection lost' });
|
|
1823
|
+
this.setConnectionStatus(ConnectionStatus.DISCONNECTED);
|
|
1824
|
+
this.emit('error', createConnectionError('Max reconnection attempts reached'));
|
|
1825
|
+
this.setConnectionStatus(ConnectionStatus.DISCONNECTED);
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
scheduleReconnect() {
|
|
1829
|
+
this.clearReconnectTimer();
|
|
1830
|
+
this.setConnectionStatus(ConnectionStatus.RECONNECTING);
|
|
1831
|
+
this.updateReadiness({ phase: ReadinessPhase.CONNECTING_MCP });
|
|
1832
|
+
const delay = (this.config.reconnectDelay ?? 0) * Math.pow(2, this.reconnectAttempt);
|
|
1833
|
+
this.reconnectAttempt += 1;
|
|
1834
|
+
this.reconnectTimer = setTimeout(() => {
|
|
1835
|
+
void this.connectSSE();
|
|
1836
|
+
}, delay);
|
|
1837
|
+
}
|
|
1838
|
+
clearReconnectTimer() {
|
|
1839
|
+
if (this.reconnectTimer) {
|
|
1840
|
+
clearTimeout(this.reconnectTimer);
|
|
1841
|
+
this.reconnectTimer = null;
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
startHeartbeat() {
|
|
1845
|
+
this.stopHeartbeat();
|
|
1846
|
+
this.heartbeatTimer = setInterval(() => {
|
|
1847
|
+
if (this.state.connectionStatus === ConnectionStatus.CONNECTED && this.eventSource) {
|
|
1848
|
+
if (this.eventSource.readyState !== EventSource.OPEN) {
|
|
1849
|
+
console.warn('SSE connection lost, attempting to reconnect');
|
|
1850
|
+
this.handleConnectionError();
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
}, TIMING.HEARTBEAT_INTERVAL);
|
|
1854
|
+
}
|
|
1855
|
+
stopHeartbeat() {
|
|
1856
|
+
if (this.heartbeatTimer) {
|
|
1857
|
+
clearInterval(this.heartbeatTimer);
|
|
1858
|
+
this.heartbeatTimer = null;
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
teardownEventSource() {
|
|
1862
|
+
if (this.eventSource) {
|
|
1863
|
+
this.eventSource.close();
|
|
1864
|
+
this.eventSource = null;
|
|
1865
|
+
}
|
|
1866
|
+
this.stopHeartbeat();
|
|
1867
|
+
this.clearReconnectTimer();
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
function normaliseToolStream(raw) {
|
|
1871
|
+
if (!raw) {
|
|
1872
|
+
return undefined;
|
|
1873
|
+
}
|
|
1874
|
+
if (Array.isArray(raw)) {
|
|
1875
|
+
const [topic, content] = raw;
|
|
1876
|
+
return typeof topic === 'string'
|
|
1877
|
+
? { topic, content: typeof content === 'string' ? content : '' }
|
|
1878
|
+
: undefined;
|
|
1879
|
+
}
|
|
1880
|
+
if (typeof raw === 'object') {
|
|
1881
|
+
const { topic, content } = raw;
|
|
1882
|
+
return typeof topic === 'string'
|
|
1883
|
+
? { topic, content: typeof content === 'string' ? content : '' }
|
|
1884
|
+
: undefined;
|
|
1885
|
+
}
|
|
1886
|
+
return undefined;
|
|
1887
|
+
}
|
|
1888
|
+
function areToolStreamsEqual(a, b) {
|
|
1889
|
+
if (!a && !b) {
|
|
1890
|
+
return true;
|
|
1891
|
+
}
|
|
1892
|
+
if (!a || !b) {
|
|
1893
|
+
return false;
|
|
1894
|
+
}
|
|
1895
|
+
return a.topic === b.topic && a.content === b.content;
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
// ThemeManager - Handles theme configuration and application
|
|
1899
|
+
/*
|
|
1900
|
+
* ============================================================================
|
|
1901
|
+
* THEME MANAGER CLASS
|
|
1902
|
+
* ============================================================================
|
|
1903
|
+
*/
|
|
1904
|
+
class ThemeManager {
|
|
1905
|
+
constructor(theme) {
|
|
1906
|
+
this.customPalette = null;
|
|
1907
|
+
this.currentTheme = this.resolveTheme(theme);
|
|
1908
|
+
}
|
|
1909
|
+
/*
|
|
1910
|
+
* ============================================================================
|
|
1911
|
+
* PUBLIC API
|
|
1912
|
+
* ============================================================================
|
|
1913
|
+
*/
|
|
1914
|
+
/**
|
|
1915
|
+
* Updates the current theme
|
|
1916
|
+
*/
|
|
1917
|
+
updateTheme(theme) {
|
|
1918
|
+
this.currentTheme = this.resolveTheme(theme);
|
|
1919
|
+
this.customPalette = this.isCustomPalette(theme) ? theme : null;
|
|
1920
|
+
}
|
|
1921
|
+
/**
|
|
1922
|
+
* Gets the current computed theme
|
|
1923
|
+
*/
|
|
1924
|
+
getComputedTheme() {
|
|
1925
|
+
if (this.customPalette) {
|
|
1926
|
+
return {
|
|
1927
|
+
...this.currentTheme,
|
|
1928
|
+
palette: this.customPalette,
|
|
1929
|
+
};
|
|
1930
|
+
}
|
|
1931
|
+
return this.currentTheme;
|
|
1932
|
+
}
|
|
1933
|
+
/**
|
|
1934
|
+
* Gets a specific color from the current theme
|
|
1935
|
+
*/
|
|
1936
|
+
getColor(colorKey) {
|
|
1937
|
+
const theme = this.getComputedTheme();
|
|
1938
|
+
return theme.palette[colorKey] || THEME_PALETTES.light[colorKey];
|
|
1939
|
+
}
|
|
1940
|
+
/**
|
|
1941
|
+
* Gets the CSS class name for the current theme
|
|
1942
|
+
*/
|
|
1943
|
+
getThemeClass() {
|
|
1944
|
+
const baseTheme = this.customPalette?.baseTheme || this.getBaseThemeName();
|
|
1945
|
+
switch (baseTheme) {
|
|
1946
|
+
case 'light':
|
|
1947
|
+
return CSS_CLASSES.THEME_LIGHT;
|
|
1948
|
+
case 'dark':
|
|
1949
|
+
return CSS_CLASSES.THEME_DARK;
|
|
1950
|
+
case 'terminal':
|
|
1951
|
+
return CSS_CLASSES.THEME_TERMINAL;
|
|
1952
|
+
case 'neon':
|
|
1953
|
+
return CSS_CLASSES.THEME_NEON;
|
|
1954
|
+
case 'minimal':
|
|
1955
|
+
return CSS_CLASSES.THEME_MINIMAL;
|
|
1956
|
+
default:
|
|
1957
|
+
return CSS_CLASSES.THEME_LIGHT;
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
/**
|
|
1961
|
+
* Gets the font family for the current theme
|
|
1962
|
+
*/
|
|
1963
|
+
getFontFamily() {
|
|
1964
|
+
return this.currentTheme.fonts?.primary || 'system-ui, sans-serif';
|
|
1965
|
+
}
|
|
1966
|
+
/**
|
|
1967
|
+
* Gets the monospace font family for the current theme
|
|
1968
|
+
*/
|
|
1969
|
+
getMonospaceFontFamily() {
|
|
1970
|
+
return this.currentTheme.fonts?.monospace || 'monospace';
|
|
1971
|
+
}
|
|
1972
|
+
/**
|
|
1973
|
+
* Gets a spacing value for the current theme
|
|
1974
|
+
*/
|
|
1975
|
+
getSpacing(size) {
|
|
1976
|
+
return this.currentTheme.spacing?.[size] || '8px';
|
|
1977
|
+
}
|
|
1978
|
+
/**
|
|
1979
|
+
* Gets a border radius value for the current theme
|
|
1980
|
+
*/
|
|
1981
|
+
getBorderRadius(size) {
|
|
1982
|
+
return this.currentTheme.borderRadius?.[size] || '4px';
|
|
1983
|
+
}
|
|
1984
|
+
/**
|
|
1985
|
+
* Gets a shadow value for the current theme
|
|
1986
|
+
*/
|
|
1987
|
+
getShadow(size) {
|
|
1988
|
+
return this.currentTheme.shadows?.[size] || 'none';
|
|
1989
|
+
}
|
|
1990
|
+
/**
|
|
1991
|
+
* Generates CSS custom properties for the current theme
|
|
1992
|
+
*/
|
|
1993
|
+
getCSSCustomProperties() {
|
|
1994
|
+
const theme = this.getComputedTheme();
|
|
1995
|
+
const properties = {};
|
|
1996
|
+
// Color properties
|
|
1997
|
+
Object.entries(theme.palette).forEach(([key, value]) => {
|
|
1998
|
+
properties[`--aomi-color-${key}`] = value;
|
|
1999
|
+
});
|
|
2000
|
+
// Font properties
|
|
2001
|
+
if (theme.fonts?.primary) {
|
|
2002
|
+
properties['--aomi-font-primary'] = theme.fonts.primary;
|
|
2003
|
+
}
|
|
2004
|
+
if (theme.fonts?.monospace) {
|
|
2005
|
+
properties['--aomi-font-monospace'] = theme.fonts.monospace;
|
|
2006
|
+
}
|
|
2007
|
+
// Spacing properties
|
|
2008
|
+
if (theme.spacing) {
|
|
2009
|
+
Object.entries(theme.spacing).forEach(([key, value]) => {
|
|
2010
|
+
properties[`--aomi-spacing-${key}`] = value;
|
|
2011
|
+
});
|
|
2012
|
+
}
|
|
2013
|
+
// Border radius properties
|
|
2014
|
+
if (theme.borderRadius) {
|
|
2015
|
+
Object.entries(theme.borderRadius).forEach(([key, value]) => {
|
|
2016
|
+
properties[`--aomi-radius-${key}`] = value;
|
|
2017
|
+
});
|
|
2018
|
+
}
|
|
2019
|
+
// Shadow properties
|
|
2020
|
+
if (theme.shadows) {
|
|
2021
|
+
Object.entries(theme.shadows).forEach(([key, value]) => {
|
|
2022
|
+
properties[`--aomi-shadow-${key}`] = value;
|
|
2023
|
+
});
|
|
2024
|
+
}
|
|
2025
|
+
return properties;
|
|
2026
|
+
}
|
|
2027
|
+
/**
|
|
2028
|
+
* Applies the theme to a DOM element
|
|
2029
|
+
*/
|
|
2030
|
+
applyThemeToElement(element) {
|
|
2031
|
+
const properties = this.getCSSCustomProperties();
|
|
2032
|
+
Object.entries(properties).forEach(([property, value]) => {
|
|
2033
|
+
element.style.setProperty(property, value);
|
|
2034
|
+
});
|
|
2035
|
+
// Add theme class
|
|
2036
|
+
element.classList.add(this.getThemeClass());
|
|
2037
|
+
}
|
|
2038
|
+
/**
|
|
2039
|
+
* Generates CSS string for the current theme
|
|
2040
|
+
*/
|
|
2041
|
+
generateCSS(selector = '.aomi-chat-widget') {
|
|
2042
|
+
const properties = this.getCSSCustomProperties();
|
|
2043
|
+
const cssRules = [];
|
|
2044
|
+
// Main theme CSS
|
|
2045
|
+
const mainRule = Object.entries(properties)
|
|
2046
|
+
.map(([property, value]) => ` ${property}: ${value};`)
|
|
2047
|
+
.join('\n');
|
|
2048
|
+
cssRules.push(`${selector} {\n${mainRule}\n}`);
|
|
2049
|
+
// Add component-specific styles
|
|
2050
|
+
cssRules.push(this.generateComponentCSS(selector));
|
|
2051
|
+
return cssRules.join('\n\n');
|
|
2052
|
+
}
|
|
2053
|
+
/**
|
|
2054
|
+
* Destroys the theme manager
|
|
2055
|
+
*/
|
|
2056
|
+
destroy() {
|
|
2057
|
+
// Clean up any resources if needed
|
|
2058
|
+
}
|
|
2059
|
+
/*
|
|
2060
|
+
* ============================================================================
|
|
2061
|
+
* PRIVATE METHODS
|
|
2062
|
+
* ============================================================================
|
|
2063
|
+
*/
|
|
2064
|
+
resolveTheme(theme) {
|
|
2065
|
+
if (!theme) {
|
|
2066
|
+
return PREDEFINED_THEMES.light;
|
|
2067
|
+
}
|
|
2068
|
+
if (typeof theme === 'string') {
|
|
2069
|
+
const predefinedTheme = PREDEFINED_THEMES[theme];
|
|
2070
|
+
if (!predefinedTheme) {
|
|
2071
|
+
throw createConfigurationError(`Unknown theme: ${theme}`);
|
|
2072
|
+
}
|
|
2073
|
+
return predefinedTheme;
|
|
2074
|
+
}
|
|
2075
|
+
if (this.isCustomPalette(theme)) {
|
|
2076
|
+
const baseTheme = PREDEFINED_THEMES[theme.baseTheme];
|
|
2077
|
+
if (!baseTheme) {
|
|
2078
|
+
throw createConfigurationError(`Unknown base theme: ${theme.baseTheme}`);
|
|
2079
|
+
}
|
|
2080
|
+
return {
|
|
2081
|
+
...baseTheme,
|
|
2082
|
+
palette: {
|
|
2083
|
+
primary: theme.primary,
|
|
2084
|
+
background: theme.background,
|
|
2085
|
+
surface: theme.surface,
|
|
2086
|
+
text: theme.text,
|
|
2087
|
+
textSecondary: theme.textSecondary,
|
|
2088
|
+
border: theme.border,
|
|
2089
|
+
success: theme.success,
|
|
2090
|
+
error: theme.error,
|
|
2091
|
+
warning: theme.warning,
|
|
2092
|
+
accent: theme.accent,
|
|
2093
|
+
},
|
|
2094
|
+
};
|
|
2095
|
+
}
|
|
2096
|
+
throw createConfigurationError('Invalid theme configuration');
|
|
2097
|
+
}
|
|
2098
|
+
isCustomPalette(theme) {
|
|
2099
|
+
if (typeof theme !== 'object' || theme === null)
|
|
2100
|
+
return false;
|
|
2101
|
+
const palette = theme;
|
|
2102
|
+
return (typeof palette.baseTheme === 'string' &&
|
|
2103
|
+
typeof palette.primary === 'string' &&
|
|
2104
|
+
typeof palette.background === 'string' &&
|
|
2105
|
+
typeof palette.surface === 'string' &&
|
|
2106
|
+
typeof palette.text === 'string' &&
|
|
2107
|
+
typeof palette.textSecondary === 'string' &&
|
|
2108
|
+
typeof palette.border === 'string' &&
|
|
2109
|
+
typeof palette.success === 'string' &&
|
|
2110
|
+
typeof palette.error === 'string' &&
|
|
2111
|
+
typeof palette.warning === 'string' &&
|
|
2112
|
+
typeof palette.accent === 'string');
|
|
2113
|
+
}
|
|
2114
|
+
getBaseThemeName() {
|
|
2115
|
+
// Find the base theme name by comparing palettes
|
|
2116
|
+
for (const [themeName, themeDefinition] of Object.entries(PREDEFINED_THEMES)) {
|
|
2117
|
+
if (this.isPaletteEqual(themeDefinition.palette, this.currentTheme.palette)) {
|
|
2118
|
+
return themeName;
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
return 'light'; // fallback
|
|
2122
|
+
}
|
|
2123
|
+
isPaletteEqual(palette1, palette2) {
|
|
2124
|
+
const keys = Object.keys(palette1);
|
|
2125
|
+
return keys.every(key => palette1[key] === palette2[key]);
|
|
2126
|
+
}
|
|
2127
|
+
generateComponentCSS(selector) {
|
|
2128
|
+
return `
|
|
2129
|
+
/* Chat Interface */
|
|
2130
|
+
${selector} .${CSS_CLASSES.CHAT_INTERFACE} {
|
|
2131
|
+
background-color: var(--aomi-color-background);
|
|
2132
|
+
color: var(--aomi-color-text);
|
|
2133
|
+
border: 1px solid var(--aomi-color-border);
|
|
2134
|
+
border-radius: var(--aomi-radius-md);
|
|
2135
|
+
font-family: var(--aomi-font-primary);
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
/* Message List */
|
|
2139
|
+
${selector} .${CSS_CLASSES.MESSAGE_LIST} {
|
|
2140
|
+
padding: var(--aomi-spacing-md);
|
|
2141
|
+
overflow-y: auto;
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
/* Message Input */
|
|
2145
|
+
${selector} .${CSS_CLASSES.MESSAGE_INPUT} {
|
|
2146
|
+
background-color: var(--aomi-color-surface);
|
|
2147
|
+
border: 1px solid var(--aomi-color-border);
|
|
2148
|
+
border-radius: var(--aomi-radius-sm);
|
|
2149
|
+
color: var(--aomi-color-text);
|
|
2150
|
+
padding: var(--aomi-spacing-sm);
|
|
2151
|
+
font-family: var(--aomi-font-primary);
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
${selector} .${CSS_CLASSES.MESSAGE_INPUT}:focus {
|
|
2155
|
+
outline: none;
|
|
2156
|
+
border-color: var(--aomi-color-primary);
|
|
2157
|
+
box-shadow: 0 0 0 2px var(--aomi-color-primary)25;
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
/* Wallet Status */
|
|
2161
|
+
${selector} .${CSS_CLASSES.WALLET_STATUS} {
|
|
2162
|
+
background-color: var(--aomi-color-surface);
|
|
2163
|
+
color: var(--aomi-color-textSecondary);
|
|
2164
|
+
padding: var(--aomi-spacing-sm);
|
|
2165
|
+
border-radius: var(--aomi-radius-sm);
|
|
2166
|
+
font-size: 12px;
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
${selector} .${CSS_CLASSES.WALLET_STATUS}.${CSS_CLASSES.CONNECTED} {
|
|
2170
|
+
color: var(--aomi-color-success);
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
${selector} .${CSS_CLASSES.WALLET_STATUS}.${CSS_CLASSES.DISCONNECTED} {
|
|
2174
|
+
color: var(--aomi-color-error);
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
/* Typing Indicator */
|
|
2178
|
+
${selector} .${CSS_CLASSES.TYPING_INDICATOR} {
|
|
2179
|
+
color: var(--aomi-color-textSecondary);
|
|
2180
|
+
font-style: italic;
|
|
2181
|
+
padding: var(--aomi-spacing-sm);
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
/* State Classes */
|
|
2185
|
+
${selector}.${CSS_CLASSES.LOADING} {
|
|
2186
|
+
opacity: 0.7;
|
|
2187
|
+
pointer-events: none;
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
${selector}.${CSS_CLASSES.ERROR} {
|
|
2191
|
+
border-color: var(--aomi-color-error);
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
${selector}.${CSS_CLASSES.DISABLED} {
|
|
2195
|
+
opacity: 0.5;
|
|
2196
|
+
pointer-events: none;
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
/* Terminal Theme Specific */
|
|
2200
|
+
${selector}.${CSS_CLASSES.THEME_TERMINAL} {
|
|
2201
|
+
font-family: var(--aomi-font-monospace);
|
|
2202
|
+
background-color: var(--aomi-color-background);
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
${selector}.${CSS_CLASSES.THEME_TERMINAL} .${CSS_CLASSES.CHAT_INTERFACE} {
|
|
2206
|
+
border: 1px solid var(--aomi-color-primary);
|
|
2207
|
+
box-shadow: var(--aomi-shadow-md);
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
/* Neon Theme Specific */
|
|
2211
|
+
${selector}.${CSS_CLASSES.THEME_NEON} {
|
|
2212
|
+
background: linear-gradient(135deg, var(--aomi-color-background), var(--aomi-color-surface));
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
${selector}.${CSS_CLASSES.THEME_NEON} .${CSS_CLASSES.CHAT_INTERFACE} {
|
|
2216
|
+
border: 1px solid var(--aomi-color-primary);
|
|
2217
|
+
box-shadow: 0 0 20px var(--aomi-color-primary)50;
|
|
2218
|
+
}`;
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
/*
|
|
2222
|
+
* ============================================================================
|
|
2223
|
+
* UTILITY FUNCTIONS
|
|
2224
|
+
* ============================================================================
|
|
2225
|
+
*/
|
|
2226
|
+
/**
|
|
2227
|
+
* Creates a theme manager instance
|
|
2228
|
+
*/
|
|
2229
|
+
function createThemeManager(theme) {
|
|
2230
|
+
return new ThemeManager(theme);
|
|
2231
|
+
}
|
|
2232
|
+
/**
|
|
2233
|
+
* Gets all available predefined themes
|
|
2234
|
+
*/
|
|
2235
|
+
function getAvailableThemes() {
|
|
2236
|
+
return { ...PREDEFINED_THEMES };
|
|
2237
|
+
}
|
|
2238
|
+
/**
|
|
2239
|
+
* Validates a custom palette
|
|
2240
|
+
*/
|
|
2241
|
+
function validateCustomPalette(palette) {
|
|
2242
|
+
if (typeof palette !== 'object' || palette === null)
|
|
2243
|
+
return false;
|
|
2244
|
+
const p = palette;
|
|
2245
|
+
const requiredKeys = [
|
|
2246
|
+
'baseTheme', 'primary', 'background', 'surface', 'text', 'textSecondary',
|
|
2247
|
+
'border', 'success', 'error', 'warning', 'accent',
|
|
2248
|
+
];
|
|
2249
|
+
return requiredKeys.every(key => typeof p[key] === 'string');
|
|
2250
|
+
}
|
|
2251
|
+
/**
|
|
2252
|
+
* Creates a custom palette from a base theme
|
|
2253
|
+
*/
|
|
2254
|
+
function createCustomPalette(baseTheme, overrides) {
|
|
2255
|
+
const basePalette = THEME_PALETTES[baseTheme];
|
|
2256
|
+
if (!basePalette) {
|
|
2257
|
+
throw createConfigurationError(`Unknown base theme: ${baseTheme}`);
|
|
2258
|
+
}
|
|
2259
|
+
return {
|
|
2260
|
+
baseTheme,
|
|
2261
|
+
...basePalette,
|
|
2262
|
+
...overrides,
|
|
2263
|
+
};
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
// WalletManager - Handles wallet connection and transactions
|
|
2267
|
+
/*
|
|
2268
|
+
* ============================================================================
|
|
2269
|
+
* WALLET MANAGER CLASS
|
|
2270
|
+
* ============================================================================
|
|
2271
|
+
*/
|
|
2272
|
+
class WalletManager extends EventEmitter {
|
|
2273
|
+
constructor(provider) {
|
|
2274
|
+
super();
|
|
2275
|
+
this.currentAccount = null;
|
|
2276
|
+
this.currentChainId = null;
|
|
2277
|
+
this.isConnected = false;
|
|
2278
|
+
this.provider = provider;
|
|
2279
|
+
this.setupEventListeners();
|
|
2280
|
+
this.initializeState();
|
|
2281
|
+
}
|
|
2282
|
+
/*
|
|
2283
|
+
* ============================================================================
|
|
2284
|
+
* PUBLIC API
|
|
2285
|
+
* ============================================================================
|
|
2286
|
+
*/
|
|
2287
|
+
/**
|
|
2288
|
+
* Gets the current connected account
|
|
2289
|
+
*/
|
|
2290
|
+
getCurrentAccount() {
|
|
2291
|
+
return this.currentAccount;
|
|
2292
|
+
}
|
|
2293
|
+
/**
|
|
2294
|
+
* Gets the current chain ID
|
|
2295
|
+
*/
|
|
2296
|
+
getCurrentChainId() {
|
|
2297
|
+
return this.currentChainId;
|
|
2298
|
+
}
|
|
2299
|
+
/**
|
|
2300
|
+
* Gets the current network name
|
|
2301
|
+
*/
|
|
2302
|
+
getCurrentNetworkName() {
|
|
2303
|
+
if (!this.currentChainId)
|
|
2304
|
+
return null;
|
|
2305
|
+
return SUPPORTED_CHAINS[this.currentChainId] || 'Unknown Network';
|
|
2306
|
+
}
|
|
2307
|
+
/**
|
|
2308
|
+
* Checks if wallet is connected
|
|
2309
|
+
*/
|
|
2310
|
+
getIsConnected() {
|
|
2311
|
+
return this.isConnected;
|
|
2312
|
+
}
|
|
2313
|
+
/**
|
|
2314
|
+
* Connects to the wallet
|
|
2315
|
+
*/
|
|
2316
|
+
async connect() {
|
|
2317
|
+
try {
|
|
2318
|
+
const accounts = await this.provider.request({
|
|
2319
|
+
id: Date.now(),
|
|
2320
|
+
method: 'eth_requestAccounts',
|
|
2321
|
+
params: [],
|
|
2322
|
+
});
|
|
2323
|
+
if (!accounts || accounts.length === 0) {
|
|
2324
|
+
throw createWalletError(ERROR_CODES.WALLET_CONNECTION_FAILED, 'No accounts returned from wallet');
|
|
2325
|
+
}
|
|
2326
|
+
const account = accounts[0];
|
|
2327
|
+
if (!isEthereumAddress(account)) {
|
|
2328
|
+
throw createWalletError(ERROR_CODES.WALLET_CONNECTION_FAILED, 'Invalid account address');
|
|
2329
|
+
}
|
|
2330
|
+
this.currentAccount = account;
|
|
2331
|
+
this.isConnected = true;
|
|
2332
|
+
// Get chain ID
|
|
2333
|
+
await this.updateChainId();
|
|
2334
|
+
this.emit('connect', account);
|
|
2335
|
+
return account;
|
|
2336
|
+
}
|
|
2337
|
+
catch (error) {
|
|
2338
|
+
const walletError = error instanceof Error
|
|
2339
|
+
? createWalletError(ERROR_CODES.WALLET_CONNECTION_FAILED, error.message)
|
|
2340
|
+
: createWalletError(ERROR_CODES.WALLET_CONNECTION_FAILED, 'Unknown error');
|
|
2341
|
+
this.emit('error', walletError);
|
|
2342
|
+
throw walletError;
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
/**
|
|
2346
|
+
* Disconnects from the wallet
|
|
2347
|
+
*/
|
|
2348
|
+
disconnect() {
|
|
2349
|
+
this.currentAccount = null;
|
|
2350
|
+
this.currentChainId = null;
|
|
2351
|
+
this.isConnected = false;
|
|
2352
|
+
this.emit('disconnect');
|
|
2353
|
+
}
|
|
2354
|
+
/**
|
|
2355
|
+
* Switches to a specific network
|
|
2356
|
+
*/
|
|
2357
|
+
async switchNetwork(chainId) {
|
|
2358
|
+
try {
|
|
2359
|
+
await this.provider.request({
|
|
2360
|
+
id: Date.now(),
|
|
2361
|
+
method: 'wallet_switchEthereumChain',
|
|
2362
|
+
params: [{ chainId: `0x${chainId.toString(16)}` }],
|
|
2363
|
+
});
|
|
2364
|
+
}
|
|
2365
|
+
catch (error) {
|
|
2366
|
+
// If the chain is not added, try to add it
|
|
2367
|
+
if (error.code === 4902) {
|
|
2368
|
+
await this.addNetwork(chainId);
|
|
2369
|
+
}
|
|
2370
|
+
else {
|
|
2371
|
+
throw createWalletError(ERROR_CODES.UNSUPPORTED_NETWORK, `Failed to switch to network ${chainId}: ${error.message}`);
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
/**
|
|
2376
|
+
* Sends a transaction
|
|
2377
|
+
*/
|
|
2378
|
+
async sendTransaction(transaction) {
|
|
2379
|
+
if (!this.isConnected || !this.currentAccount) {
|
|
2380
|
+
throw createWalletError(ERROR_CODES.WALLET_NOT_CONNECTED, 'Wallet is not connected');
|
|
2381
|
+
}
|
|
2382
|
+
try {
|
|
2383
|
+
// Validate transaction
|
|
2384
|
+
this.validateTransaction(transaction);
|
|
2385
|
+
// Prepare transaction parameters
|
|
2386
|
+
const txParams = {
|
|
2387
|
+
from: this.currentAccount,
|
|
2388
|
+
to: transaction.to,
|
|
2389
|
+
value: transaction.value,
|
|
2390
|
+
data: transaction.data,
|
|
2391
|
+
};
|
|
2392
|
+
if (transaction.gas) {
|
|
2393
|
+
txParams.gas = transaction.gas;
|
|
2394
|
+
}
|
|
2395
|
+
// Send transaction
|
|
2396
|
+
const txHash = await this.provider.request({
|
|
2397
|
+
id: Date.now(),
|
|
2398
|
+
method: 'eth_sendTransaction',
|
|
2399
|
+
params: [txParams],
|
|
2400
|
+
});
|
|
2401
|
+
if (!isTransactionHash(txHash)) {
|
|
2402
|
+
throw createTransactionError('Invalid transaction hash returned');
|
|
2403
|
+
}
|
|
2404
|
+
return txHash;
|
|
2405
|
+
}
|
|
2406
|
+
catch (error) {
|
|
2407
|
+
// Handle user rejection
|
|
2408
|
+
if (error.code === 4001) {
|
|
2409
|
+
throw createWalletError(ERROR_CODES.TRANSACTION_REJECTED, 'Transaction was rejected by user');
|
|
2410
|
+
}
|
|
2411
|
+
const message = error.message || 'Transaction failed';
|
|
2412
|
+
throw createTransactionError(message);
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
/**
|
|
2416
|
+
* Signs a message
|
|
2417
|
+
*/
|
|
2418
|
+
async signMessage(message) {
|
|
2419
|
+
if (!this.isConnected || !this.currentAccount) {
|
|
2420
|
+
throw createWalletError(ERROR_CODES.WALLET_NOT_CONNECTED, 'Wallet is not connected');
|
|
2421
|
+
}
|
|
2422
|
+
try {
|
|
2423
|
+
const signature = await this.provider.request({
|
|
2424
|
+
id: Date.now(),
|
|
2425
|
+
method: 'personal_sign',
|
|
2426
|
+
params: [message, this.currentAccount],
|
|
2427
|
+
});
|
|
2428
|
+
return signature;
|
|
2429
|
+
}
|
|
2430
|
+
catch (error) {
|
|
2431
|
+
if (error.code === 4001) {
|
|
2432
|
+
throw createWalletError(ERROR_CODES.TRANSACTION_REJECTED, 'Message signing was rejected by user');
|
|
2433
|
+
}
|
|
2434
|
+
throw createWalletError(ERROR_CODES.UNKNOWN_ERROR, `Failed to sign message: ${error.message}`);
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
/**
|
|
2438
|
+
* Gets account balance
|
|
2439
|
+
*/
|
|
2440
|
+
async getBalance(address) {
|
|
2441
|
+
const accountAddress = address || this.currentAccount;
|
|
2442
|
+
if (!accountAddress) {
|
|
2443
|
+
throw createWalletError(ERROR_CODES.WALLET_NOT_CONNECTED, 'No account available');
|
|
2444
|
+
}
|
|
2445
|
+
try {
|
|
2446
|
+
const balance = await this.provider.request({
|
|
2447
|
+
id: Date.now(),
|
|
2448
|
+
method: 'eth_getBalance',
|
|
2449
|
+
params: [accountAddress, 'latest'],
|
|
2450
|
+
});
|
|
2451
|
+
return balance;
|
|
2452
|
+
}
|
|
2453
|
+
catch (error) {
|
|
2454
|
+
throw createWalletError(ERROR_CODES.UNKNOWN_ERROR, `Failed to get balance: ${error.message}`);
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
/**
|
|
2458
|
+
* Updates the provider
|
|
2459
|
+
*/
|
|
2460
|
+
updateProvider(provider) {
|
|
2461
|
+
// Clean up old provider
|
|
2462
|
+
this.removeProviderListeners();
|
|
2463
|
+
// Set new provider
|
|
2464
|
+
this.provider = provider;
|
|
2465
|
+
// Setup new listeners
|
|
2466
|
+
this.setupEventListeners();
|
|
2467
|
+
// Re-initialize state
|
|
2468
|
+
this.initializeState();
|
|
2469
|
+
}
|
|
2470
|
+
/**
|
|
2471
|
+
* Destroys the wallet manager
|
|
2472
|
+
*/
|
|
2473
|
+
destroy() {
|
|
2474
|
+
this.removeProviderListeners();
|
|
2475
|
+
this.disconnect();
|
|
2476
|
+
this.removeAllListeners();
|
|
2477
|
+
}
|
|
2478
|
+
/*
|
|
2479
|
+
* ============================================================================
|
|
2480
|
+
* PRIVATE METHODS
|
|
2481
|
+
* ============================================================================
|
|
2482
|
+
*/
|
|
2483
|
+
async initializeState() {
|
|
2484
|
+
try {
|
|
2485
|
+
// Check if already connected
|
|
2486
|
+
const accounts = await this.provider.request({
|
|
2487
|
+
id: Date.now(),
|
|
2488
|
+
method: 'eth_accounts',
|
|
2489
|
+
params: [],
|
|
2490
|
+
});
|
|
2491
|
+
if (accounts && accounts.length > 0 && isEthereumAddress(accounts[0])) {
|
|
2492
|
+
this.currentAccount = accounts[0];
|
|
2493
|
+
this.isConnected = true;
|
|
2494
|
+
await this.updateChainId();
|
|
2495
|
+
this.emit('connect', accounts[0]);
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
catch (error) {
|
|
2499
|
+
// Ignore errors during initialization
|
|
2500
|
+
console.warn('Failed to initialize wallet state:', error);
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
async updateChainId() {
|
|
2504
|
+
try {
|
|
2505
|
+
const chainId = await this.provider.request({
|
|
2506
|
+
id: Date.now(),
|
|
2507
|
+
method: 'eth_chainId',
|
|
2508
|
+
params: [],
|
|
2509
|
+
});
|
|
2510
|
+
const numericChainId = parseInt(chainId, 16);
|
|
2511
|
+
if (this.currentChainId !== numericChainId) {
|
|
2512
|
+
this.currentChainId = numericChainId;
|
|
2513
|
+
this.emit('chainChange', numericChainId);
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
catch (error) {
|
|
2517
|
+
console.warn('Failed to get chain ID:', error);
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
setupEventListeners() {
|
|
2521
|
+
if (this.provider.on) {
|
|
2522
|
+
this.provider.on('accountsChanged', this.handleAccountsChanged.bind(this));
|
|
2523
|
+
this.provider.on('chainChanged', this.handleChainChanged.bind(this));
|
|
2524
|
+
this.provider.on('disconnect', this.handleDisconnect.bind(this));
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
removeProviderListeners() {
|
|
2528
|
+
if (this.provider.removeListener) {
|
|
2529
|
+
this.provider.removeListener?.('accountsChanged', this.handleAccountsChanged.bind(this));
|
|
2530
|
+
this.provider.removeListener?.('chainChanged', this.handleChainChanged.bind(this));
|
|
2531
|
+
this.provider.removeListener('disconnect', this.handleDisconnect.bind(this));
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
handleAccountsChanged(accounts) {
|
|
2535
|
+
if (!accounts || accounts.length === 0) {
|
|
2536
|
+
this.disconnect();
|
|
2537
|
+
}
|
|
2538
|
+
else if (accounts[0] !== this.currentAccount) {
|
|
2539
|
+
this.currentAccount = accounts[0];
|
|
2540
|
+
this.isConnected = true;
|
|
2541
|
+
this.emit('accountsChange', accounts);
|
|
2542
|
+
this.emit('connect', accounts[0]);
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
handleChainChanged(chainId) {
|
|
2546
|
+
const numericChainId = parseInt(chainId, 16);
|
|
2547
|
+
this.currentChainId = numericChainId;
|
|
2548
|
+
this.emit('chainChange', numericChainId);
|
|
2549
|
+
}
|
|
2550
|
+
handleDisconnect() {
|
|
2551
|
+
this.disconnect();
|
|
2552
|
+
}
|
|
2553
|
+
validateTransaction(transaction) {
|
|
2554
|
+
if (!isEthereumAddress(transaction.to)) {
|
|
2555
|
+
throw createTransactionError('Invalid recipient address');
|
|
2556
|
+
}
|
|
2557
|
+
// Validate value (should be hex string)
|
|
2558
|
+
if (!/^0x[0-9a-fA-F]*$/.test(transaction.value)) {
|
|
2559
|
+
throw createTransactionError('Invalid transaction value');
|
|
2560
|
+
}
|
|
2561
|
+
// Validate data (should be hex string)
|
|
2562
|
+
if (transaction.data && !/^0x[0-9a-fA-F]*$/.test(transaction.data)) {
|
|
2563
|
+
throw createTransactionError('Invalid transaction data');
|
|
2564
|
+
}
|
|
2565
|
+
// Validate gas (should be hex string if provided)
|
|
2566
|
+
if (transaction.gas && !/^0x[0-9a-fA-F]*$/.test(transaction.gas)) {
|
|
2567
|
+
throw createTransactionError('Invalid gas value');
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
async addNetwork(chainId) {
|
|
2571
|
+
const networkConfig = this.getNetworkConfig(chainId);
|
|
2572
|
+
if (!networkConfig) {
|
|
2573
|
+
throw createWalletError(ERROR_CODES.UNSUPPORTED_NETWORK, `Network ${chainId} is not supported`);
|
|
2574
|
+
}
|
|
2575
|
+
try {
|
|
2576
|
+
await this.provider.request({
|
|
2577
|
+
id: Date.now(),
|
|
2578
|
+
method: 'wallet_addEthereumChain',
|
|
2579
|
+
params: [networkConfig],
|
|
2580
|
+
});
|
|
2581
|
+
}
|
|
2582
|
+
catch (error) {
|
|
2583
|
+
throw createWalletError(ERROR_CODES.UNSUPPORTED_NETWORK, `Failed to add network ${chainId}: ${error.message}`);
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
getNetworkConfig(chainId) {
|
|
2587
|
+
const configs = {
|
|
2588
|
+
1: null, // Mainnet is built-in
|
|
2589
|
+
5: null, // Goerli is built-in
|
|
2590
|
+
10: {
|
|
2591
|
+
chainId: '0xa',
|
|
2592
|
+
chainName: 'Optimism',
|
|
2593
|
+
nativeCurrency: {
|
|
2594
|
+
name: 'Ether',
|
|
2595
|
+
symbol: 'ETH',
|
|
2596
|
+
decimals: 18,
|
|
2597
|
+
},
|
|
2598
|
+
rpcUrls: ['https://mainnet.optimism.io'],
|
|
2599
|
+
blockExplorerUrls: ['https://optimistic.etherscan.io'],
|
|
2600
|
+
},
|
|
2601
|
+
11155111: null, // Sepolia is built-in
|
|
2602
|
+
100: {
|
|
2603
|
+
chainId: '0x64',
|
|
2604
|
+
chainName: 'Gnosis',
|
|
2605
|
+
nativeCurrency: {
|
|
2606
|
+
name: 'xDai',
|
|
2607
|
+
symbol: 'XDAI',
|
|
2608
|
+
decimals: 18,
|
|
2609
|
+
},
|
|
2610
|
+
rpcUrls: ['https://rpc.gnosischain.com'],
|
|
2611
|
+
blockExplorerUrls: ['https://gnosisscan.io'],
|
|
2612
|
+
},
|
|
2613
|
+
1337: null, // Local development
|
|
2614
|
+
31337: null, // Anvil/Hardhat
|
|
2615
|
+
137: {
|
|
2616
|
+
chainId: '0x89',
|
|
2617
|
+
chainName: 'Polygon',
|
|
2618
|
+
nativeCurrency: {
|
|
2619
|
+
name: 'MATIC',
|
|
2620
|
+
symbol: 'MATIC',
|
|
2621
|
+
decimals: 18,
|
|
2622
|
+
},
|
|
2623
|
+
rpcUrls: ['https://polygon-rpc.com'],
|
|
2624
|
+
blockExplorerUrls: ['https://polygonscan.com'],
|
|
2625
|
+
},
|
|
2626
|
+
42161: {
|
|
2627
|
+
chainId: '0xa4b1',
|
|
2628
|
+
chainName: 'Arbitrum One',
|
|
2629
|
+
nativeCurrency: {
|
|
2630
|
+
name: 'Ether',
|
|
2631
|
+
symbol: 'ETH',
|
|
2632
|
+
decimals: 18,
|
|
2633
|
+
},
|
|
2634
|
+
rpcUrls: ['https://arb1.arbitrum.io/rpc'],
|
|
2635
|
+
blockExplorerUrls: ['https://arbiscan.io'],
|
|
2636
|
+
},
|
|
2637
|
+
8453: {
|
|
2638
|
+
chainId: '0x2105',
|
|
2639
|
+
chainName: 'Base',
|
|
2640
|
+
nativeCurrency: {
|
|
2641
|
+
name: 'Ether',
|
|
2642
|
+
symbol: 'ETH',
|
|
2643
|
+
decimals: 18,
|
|
2644
|
+
},
|
|
2645
|
+
rpcUrls: ['https://mainnet.base.org'],
|
|
2646
|
+
blockExplorerUrls: ['https://basescan.org'],
|
|
2647
|
+
},
|
|
2648
|
+
59140: {
|
|
2649
|
+
chainId: '0xe704',
|
|
2650
|
+
chainName: 'Linea Sepolia',
|
|
2651
|
+
nativeCurrency: {
|
|
2652
|
+
name: 'Linea Ether',
|
|
2653
|
+
symbol: 'ETH',
|
|
2654
|
+
decimals: 18,
|
|
2655
|
+
},
|
|
2656
|
+
rpcUrls: ['https://rpc-sepolia.linea.build'],
|
|
2657
|
+
blockExplorerUrls: ['https://sepolia.lineascan.build'],
|
|
2658
|
+
},
|
|
2659
|
+
59144: {
|
|
2660
|
+
chainId: '0xe708',
|
|
2661
|
+
chainName: 'Linea',
|
|
2662
|
+
nativeCurrency: {
|
|
2663
|
+
name: 'Linea Ether',
|
|
2664
|
+
symbol: 'ETH',
|
|
2665
|
+
decimals: 18,
|
|
2666
|
+
},
|
|
2667
|
+
rpcUrls: ['https://rpc.linea.build'],
|
|
2668
|
+
blockExplorerUrls: ['https://lineascan.build'],
|
|
2669
|
+
},
|
|
2670
|
+
};
|
|
2671
|
+
return configs[chainId];
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
/*
|
|
2675
|
+
* ============================================================================
|
|
2676
|
+
* UTILITY FUNCTIONS
|
|
2677
|
+
* ============================================================================
|
|
2678
|
+
*/
|
|
2679
|
+
/**
|
|
2680
|
+
* Creates a wallet manager instance
|
|
2681
|
+
*/
|
|
2682
|
+
function createWalletManager(provider) {
|
|
2683
|
+
return new WalletManager(provider);
|
|
2684
|
+
}
|
|
2685
|
+
/**
|
|
2686
|
+
* Checks if a provider supports the required methods
|
|
2687
|
+
*/
|
|
2688
|
+
function isValidProvider(provider) {
|
|
2689
|
+
if (!provider || typeof provider !== 'object')
|
|
2690
|
+
return false;
|
|
2691
|
+
const p = provider;
|
|
2692
|
+
return (typeof p.request === 'function' &&
|
|
2693
|
+
typeof p.on === 'function');
|
|
2694
|
+
}
|
|
2695
|
+
/**
|
|
2696
|
+
* Detects available wallets
|
|
2697
|
+
*/
|
|
2698
|
+
function detectWallets() {
|
|
2699
|
+
if (typeof window === 'undefined')
|
|
2700
|
+
return [];
|
|
2701
|
+
const wallets = [];
|
|
2702
|
+
// MetaMask
|
|
2703
|
+
if (window.ethereum?.isMetaMask) {
|
|
2704
|
+
wallets.push({
|
|
2705
|
+
name: 'MetaMask',
|
|
2706
|
+
provider: window.ethereum,
|
|
2707
|
+
});
|
|
2708
|
+
}
|
|
2709
|
+
// WalletConnect
|
|
2710
|
+
if (window.ethereum?.isWalletConnect) {
|
|
2711
|
+
wallets.push({
|
|
2712
|
+
name: 'WalletConnect',
|
|
2713
|
+
provider: window.ethereum,
|
|
2714
|
+
});
|
|
2715
|
+
}
|
|
2716
|
+
// Generic ethereum provider
|
|
2717
|
+
if (window.ethereum && !window.ethereum.isMetaMask && !window.ethereum.isWalletConnect) {
|
|
2718
|
+
wallets.push({
|
|
2719
|
+
name: 'Injected Wallet',
|
|
2720
|
+
provider: window.ethereum,
|
|
2721
|
+
});
|
|
2722
|
+
}
|
|
2723
|
+
return wallets;
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
// AomiChatWidget - Main widget factory and management class
|
|
2727
|
+
/*
|
|
2728
|
+
* ============================================================================
|
|
2729
|
+
* WIDGET HANDLER IMPLEMENTATION
|
|
2730
|
+
* ============================================================================
|
|
2731
|
+
*/
|
|
2732
|
+
class AomiChatWidgetHandlerImpl {
|
|
2733
|
+
constructor(container, config) {
|
|
2734
|
+
this.walletManager = null;
|
|
2735
|
+
this.widgetElement = null;
|
|
2736
|
+
this.isDestroyed = false;
|
|
2737
|
+
this.hasPromptedNetworkSwitch = false;
|
|
2738
|
+
this.eventEmitter = new EventEmitter();
|
|
2739
|
+
this.container = container;
|
|
2740
|
+
this.config = config;
|
|
2741
|
+
// Initialize managers
|
|
2742
|
+
this.chatManager = new ChatManager({
|
|
2743
|
+
backendUrl: config.params.baseUrl || 'http://localhost:8080',
|
|
2744
|
+
sessionId: config.params.sessionId || generateSessionId(),
|
|
2745
|
+
maxMessageLength: 2000,
|
|
2746
|
+
reconnectAttempts: 5,
|
|
2747
|
+
reconnectDelay: 3000,
|
|
2748
|
+
});
|
|
2749
|
+
this.themeManager = new ThemeManager(config.params.theme);
|
|
2750
|
+
// Initialize wallet manager if provider is available
|
|
2751
|
+
if (config.provider) {
|
|
2752
|
+
this.walletManager = new WalletManager(config.provider);
|
|
2753
|
+
}
|
|
2754
|
+
this.setupEventListeners();
|
|
2755
|
+
this.render();
|
|
2756
|
+
this.initialize();
|
|
2757
|
+
}
|
|
2758
|
+
/*
|
|
2759
|
+
* ============================================================================
|
|
2760
|
+
* PUBLIC API METHODS
|
|
2761
|
+
* ============================================================================
|
|
2762
|
+
*/
|
|
2763
|
+
async sendMessage(message) {
|
|
2764
|
+
if (this.isDestroyed) {
|
|
2765
|
+
throw new AomiChatError(ERROR_CODES.INITIALIZATION_FAILED, 'Widget has been destroyed');
|
|
2766
|
+
}
|
|
2767
|
+
return this.chatManager.sendMessage(message);
|
|
2768
|
+
}
|
|
2769
|
+
updateParams(params) {
|
|
2770
|
+
if (this.isDestroyed) {
|
|
2771
|
+
throw new AomiChatError(ERROR_CODES.INITIALIZATION_FAILED, 'Widget has been destroyed');
|
|
2772
|
+
}
|
|
2773
|
+
// Validate new parameters
|
|
2774
|
+
const previousParams = this.config.params;
|
|
2775
|
+
const mergedParams = { ...previousParams, ...params };
|
|
2776
|
+
const errors = validateWidgetParams(mergedParams);
|
|
2777
|
+
if (errors.length > 0) {
|
|
2778
|
+
throw createConfigurationError(`Invalid parameters: ${errors.join(', ')}`);
|
|
2779
|
+
}
|
|
2780
|
+
// Update configuration
|
|
2781
|
+
this.config.params = mergedParams;
|
|
2782
|
+
if (params.chainId && params.chainId !== previousParams.chainId) {
|
|
2783
|
+
this.hasPromptedNetworkSwitch = false;
|
|
2784
|
+
}
|
|
2785
|
+
// Update theme if changed
|
|
2786
|
+
if (params.theme) {
|
|
2787
|
+
this.themeManager.updateTheme(params.theme);
|
|
2788
|
+
this.applyTheme();
|
|
2789
|
+
}
|
|
2790
|
+
// Update dimensions if changed
|
|
2791
|
+
if (params.width || params.height) {
|
|
2792
|
+
this.updateDimensions();
|
|
2793
|
+
}
|
|
2794
|
+
// Re-render if necessary
|
|
2795
|
+
this.render();
|
|
2796
|
+
}
|
|
2797
|
+
updateProvider(provider) {
|
|
2798
|
+
if (this.isDestroyed) {
|
|
2799
|
+
throw new AomiChatError(ERROR_CODES.INITIALIZATION_FAILED, 'Widget has been destroyed');
|
|
2800
|
+
}
|
|
2801
|
+
this.config.provider = provider;
|
|
2802
|
+
this.hasPromptedNetworkSwitch = false;
|
|
2803
|
+
if (provider) {
|
|
2804
|
+
if (this.walletManager) {
|
|
2805
|
+
this.walletManager.updateProvider(provider);
|
|
2806
|
+
}
|
|
2807
|
+
else {
|
|
2808
|
+
this.walletManager = new WalletManager(provider);
|
|
2809
|
+
this.setupWalletEventListeners();
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
else {
|
|
2813
|
+
if (this.walletManager) {
|
|
2814
|
+
this.walletManager.destroy();
|
|
2815
|
+
this.walletManager = null;
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2819
|
+
getState() {
|
|
2820
|
+
return this.chatManager.getState();
|
|
2821
|
+
}
|
|
2822
|
+
getSessionId() {
|
|
2823
|
+
return this.chatManager.getSessionId();
|
|
2824
|
+
}
|
|
2825
|
+
isReady() {
|
|
2826
|
+
const state = this.getState();
|
|
2827
|
+
return state.connectionStatus === ConnectionStatus.CONNECTED;
|
|
2828
|
+
}
|
|
2829
|
+
on(event, listener) {
|
|
2830
|
+
this.eventEmitter.on(event, listener);
|
|
2831
|
+
return this;
|
|
2832
|
+
}
|
|
2833
|
+
off(event, listener) {
|
|
2834
|
+
this.eventEmitter.off(event, listener);
|
|
2835
|
+
return this;
|
|
2836
|
+
}
|
|
2837
|
+
clearChat() {
|
|
2838
|
+
if (this.isDestroyed)
|
|
2839
|
+
return;
|
|
2840
|
+
this.chatManager.clearMessages();
|
|
2841
|
+
}
|
|
2842
|
+
exportChat() {
|
|
2843
|
+
return this.getState().messages;
|
|
2844
|
+
}
|
|
2845
|
+
resize(width, height) {
|
|
2846
|
+
if (this.isDestroyed)
|
|
2847
|
+
return;
|
|
2848
|
+
if (width)
|
|
2849
|
+
this.config.params.width = width;
|
|
2850
|
+
if (height)
|
|
2851
|
+
this.config.params.height = height;
|
|
2852
|
+
this.updateDimensions();
|
|
2853
|
+
this.eventEmitter.emit(WIDGET_EVENTS.RESIZE, {
|
|
2854
|
+
width: parseInt(width || this.config.params.width || '0'),
|
|
2855
|
+
height: parseInt(height || this.config.params.height || '0'),
|
|
2856
|
+
});
|
|
2857
|
+
}
|
|
2858
|
+
focus() {
|
|
2859
|
+
if (this.isDestroyed || !this.widgetElement)
|
|
2860
|
+
return;
|
|
2861
|
+
const input = this.widgetElement.querySelector('input, textarea');
|
|
2862
|
+
if (input) {
|
|
2863
|
+
input.focus();
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
destroy() {
|
|
2867
|
+
if (this.isDestroyed)
|
|
2868
|
+
return;
|
|
2869
|
+
this.isDestroyed = true;
|
|
2870
|
+
// Clean up managers
|
|
2871
|
+
this.chatManager.destroy();
|
|
2872
|
+
this.themeManager.destroy();
|
|
2873
|
+
if (this.walletManager) {
|
|
2874
|
+
this.walletManager.destroy();
|
|
2875
|
+
}
|
|
2876
|
+
// Remove widget from DOM
|
|
2877
|
+
if (this.widgetElement && this.widgetElement.parentNode) {
|
|
2878
|
+
this.widgetElement.parentNode.removeChild(this.widgetElement);
|
|
2879
|
+
}
|
|
2880
|
+
// Clear container
|
|
2881
|
+
this.container.innerHTML = '';
|
|
2882
|
+
// Remove all event listeners
|
|
2883
|
+
this.eventEmitter.removeAllListeners();
|
|
2884
|
+
this.eventEmitter.emit(WIDGET_EVENTS.DESTROY);
|
|
2885
|
+
}
|
|
2886
|
+
/*
|
|
2887
|
+
* ============================================================================
|
|
2888
|
+
* PRIVATE METHODS
|
|
2889
|
+
* ============================================================================
|
|
2890
|
+
*/
|
|
2891
|
+
async initialize() {
|
|
2892
|
+
try {
|
|
2893
|
+
// Connect to backend
|
|
2894
|
+
await this.chatManager.connectSSE();
|
|
2895
|
+
// Send welcome message if configured
|
|
2896
|
+
if (this.config.params.welcomeMessage) {
|
|
2897
|
+
await this.chatManager.sendSystemMessage(this.config.params.welcomeMessage);
|
|
2898
|
+
}
|
|
2899
|
+
this.eventEmitter.emit(WIDGET_EVENTS.READY);
|
|
2900
|
+
}
|
|
2901
|
+
catch (error) {
|
|
2902
|
+
const chatError = error instanceof AomiChatError
|
|
2903
|
+
? error
|
|
2904
|
+
: createConnectionError('Failed to initialize widget');
|
|
2905
|
+
this.eventEmitter.emit(WIDGET_EVENTS.ERROR, chatError);
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
setupEventListeners() {
|
|
2909
|
+
// Chat manager events
|
|
2910
|
+
this.chatManager.on('stateChange', (state) => {
|
|
2911
|
+
// Forward state changes to widget listeners
|
|
2912
|
+
this.forwardStateEvents(state);
|
|
2913
|
+
});
|
|
2914
|
+
this.chatManager.on('message', (message) => {
|
|
2915
|
+
this.eventEmitter.emit(WIDGET_EVENTS.MESSAGE, message);
|
|
2916
|
+
});
|
|
2917
|
+
this.chatManager.on('error', (error) => {
|
|
2918
|
+
this.eventEmitter.emit(WIDGET_EVENTS.ERROR, error);
|
|
2919
|
+
});
|
|
2920
|
+
this.chatManager.on('connectionChange', (status) => {
|
|
2921
|
+
this.eventEmitter.emit(WIDGET_EVENTS.CONNECTION_CHANGE, status);
|
|
2922
|
+
});
|
|
2923
|
+
this.chatManager.on('transactionRequest', (transaction) => {
|
|
2924
|
+
this.eventEmitter.emit(WIDGET_EVENTS.TRANSACTION_REQUEST, transaction);
|
|
2925
|
+
this.handleTransactionRequest(transaction);
|
|
2926
|
+
});
|
|
2927
|
+
// Set up wallet event listeners if wallet manager exists
|
|
2928
|
+
if (this.walletManager) {
|
|
2929
|
+
this.setupWalletEventListeners();
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
setupWalletEventListeners() {
|
|
2933
|
+
if (!this.walletManager)
|
|
2934
|
+
return;
|
|
2935
|
+
this.walletManager.on('connect', (address) => {
|
|
2936
|
+
const chainId = this.walletManager?.getCurrentChainId() ?? null;
|
|
2937
|
+
const networkName = this.getNetworkName(chainId);
|
|
2938
|
+
this.chatManager.updateWalletState({
|
|
2939
|
+
isConnected: true,
|
|
2940
|
+
address,
|
|
2941
|
+
chainId: chainId ?? undefined,
|
|
2942
|
+
networkName,
|
|
2943
|
+
});
|
|
2944
|
+
this.eventEmitter.emit(WIDGET_EVENTS.WALLET_CONNECT, address);
|
|
2945
|
+
const chainLabel = chainId ?? 'unknown';
|
|
2946
|
+
const connectMessage = `User connected wallet with address ${address} on ${networkName} network ` +
|
|
2947
|
+
`(Chain ID: ${chainLabel}). Ready to help with transactions.`;
|
|
2948
|
+
void this.safeSendSystemMessage(connectMessage);
|
|
2949
|
+
void this.maybePromptNetworkSwitch();
|
|
2950
|
+
});
|
|
2951
|
+
this.walletManager.on('disconnect', () => {
|
|
2952
|
+
this.chatManager.updateWalletState({
|
|
2953
|
+
isConnected: false,
|
|
2954
|
+
address: undefined,
|
|
2955
|
+
chainId: undefined,
|
|
2956
|
+
networkName: undefined,
|
|
2957
|
+
});
|
|
2958
|
+
this.hasPromptedNetworkSwitch = false;
|
|
2959
|
+
this.eventEmitter.emit(WIDGET_EVENTS.WALLET_DISCONNECT);
|
|
2960
|
+
void this.safeSendSystemMessage('Wallet disconnected. Confirm to switch to testnet');
|
|
2961
|
+
});
|
|
2962
|
+
this.walletManager.on('chainChange', (chainId) => {
|
|
2963
|
+
const networkName = this.getNetworkName(chainId);
|
|
2964
|
+
this.chatManager.updateWalletState({ chainId, networkName });
|
|
2965
|
+
this.hasPromptedNetworkSwitch = false;
|
|
2966
|
+
this.eventEmitter.emit(WIDGET_EVENTS.NETWORK_CHANGE, chainId);
|
|
2967
|
+
void this.safeSendSystemMessage(`User switched wallet to ${networkName} network (Chain ID: ${chainId}).`);
|
|
2968
|
+
void this.maybePromptNetworkSwitch();
|
|
2969
|
+
});
|
|
2970
|
+
this.walletManager.on('error', (error) => {
|
|
2971
|
+
this.eventEmitter.emit(WIDGET_EVENTS.ERROR, error);
|
|
2972
|
+
});
|
|
2973
|
+
}
|
|
2974
|
+
forwardStateEvents(state) {
|
|
2975
|
+
// Forward typing and processing changes
|
|
2976
|
+
this.eventEmitter.emit(WIDGET_EVENTS.TYPING_CHANGE, state.isTyping);
|
|
2977
|
+
this.eventEmitter.emit(WIDGET_EVENTS.PROCESSING_CHANGE, state.isProcessing);
|
|
2978
|
+
}
|
|
2979
|
+
async handleTransactionRequest(transaction) {
|
|
2980
|
+
if (!this.walletManager) {
|
|
2981
|
+
await this.chatManager.sendTransactionResult(false, undefined, 'No wallet connected');
|
|
2982
|
+
return;
|
|
2983
|
+
}
|
|
2984
|
+
try {
|
|
2985
|
+
const txHash = await this.walletManager.sendTransaction({
|
|
2986
|
+
to: transaction.to,
|
|
2987
|
+
value: transaction.value,
|
|
2988
|
+
data: transaction.data,
|
|
2989
|
+
gas: transaction.gas,
|
|
2990
|
+
});
|
|
2991
|
+
await this.chatManager.sendTransactionResult(true, txHash);
|
|
2992
|
+
}
|
|
2993
|
+
catch (error) {
|
|
2994
|
+
const message = error instanceof Error ? error.message : 'Transaction failed';
|
|
2995
|
+
await this.chatManager.sendTransactionResult(false, undefined, message);
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
async safeSendSystemMessage(message) {
|
|
2999
|
+
try {
|
|
3000
|
+
await this.chatManager.sendSystemMessage(message);
|
|
3001
|
+
}
|
|
3002
|
+
catch (error) {
|
|
3003
|
+
this.eventEmitter.emit(WIDGET_EVENTS.ERROR, error);
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
async maybePromptNetworkSwitch() {
|
|
3007
|
+
if (!this.walletManager)
|
|
3008
|
+
return;
|
|
3009
|
+
const chainId = this.walletManager.getCurrentChainId();
|
|
3010
|
+
if (!chainId)
|
|
3011
|
+
return;
|
|
3012
|
+
if (this.hasPromptedNetworkSwitch)
|
|
3013
|
+
return;
|
|
3014
|
+
const walletNetwork = this.getNetworkName(chainId);
|
|
3015
|
+
const backendNetwork = this.getNetworkName(this.config.params.chainId ?? DEFAULT_CHAIN_ID);
|
|
3016
|
+
if (walletNetwork === backendNetwork)
|
|
3017
|
+
return;
|
|
3018
|
+
this.hasPromptedNetworkSwitch = true;
|
|
3019
|
+
const promptMessage = `New wallet connection: ${walletNetwork}, System configuration: ${backendNetwork}. ` +
|
|
3020
|
+
'Prompt user to confirm network switch';
|
|
3021
|
+
await this.safeSendSystemMessage(promptMessage);
|
|
3022
|
+
}
|
|
3023
|
+
getNetworkName(chainId) {
|
|
3024
|
+
switch (chainId) {
|
|
3025
|
+
case 1:
|
|
3026
|
+
return 'mainnet';
|
|
3027
|
+
case 5:
|
|
3028
|
+
case 11155111:
|
|
3029
|
+
case 1337:
|
|
3030
|
+
case 31337:
|
|
3031
|
+
return 'testnet';
|
|
3032
|
+
case 10:
|
|
3033
|
+
return 'optimism';
|
|
3034
|
+
case 100:
|
|
3035
|
+
return 'gnosis';
|
|
3036
|
+
case 137:
|
|
3037
|
+
return 'polygon';
|
|
3038
|
+
case 42161:
|
|
3039
|
+
return 'arbitrum';
|
|
3040
|
+
case 8453:
|
|
3041
|
+
return 'base';
|
|
3042
|
+
case 59140:
|
|
3043
|
+
return 'linea-sepolia';
|
|
3044
|
+
case 59144:
|
|
3045
|
+
return 'linea';
|
|
3046
|
+
default:
|
|
3047
|
+
return 'testnet';
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
render() {
|
|
3051
|
+
// Clear existing content
|
|
3052
|
+
this.container.innerHTML = '';
|
|
3053
|
+
// Create widget element
|
|
3054
|
+
this.widgetElement = createElement('div', {
|
|
3055
|
+
className: [
|
|
3056
|
+
CSS_CLASSES.WIDGET_ROOT,
|
|
3057
|
+
CSS_CLASSES.WIDGET_CONTAINER,
|
|
3058
|
+
this.themeManager.getThemeClass(),
|
|
3059
|
+
this.getModeClass(),
|
|
3060
|
+
],
|
|
3061
|
+
styles: {
|
|
3062
|
+
width: this.config.params.width || DEFAULT_WIDGET_WIDTH,
|
|
3063
|
+
height: this.config.params.height || DEFAULT_WIDGET_HEIGHT,
|
|
3064
|
+
position: 'relative',
|
|
3065
|
+
overflow: 'hidden',
|
|
3066
|
+
fontFamily: this.themeManager.getFontFamily(),
|
|
3067
|
+
},
|
|
3068
|
+
});
|
|
3069
|
+
// Apply theme
|
|
3070
|
+
this.applyTheme();
|
|
3071
|
+
// Render chat interface
|
|
3072
|
+
this.renderChatInterface();
|
|
3073
|
+
// Add to container
|
|
3074
|
+
this.container.appendChild(this.widgetElement);
|
|
3075
|
+
}
|
|
3076
|
+
renderChatInterface() {
|
|
3077
|
+
if (!this.widgetElement)
|
|
3078
|
+
return;
|
|
3079
|
+
/*
|
|
3080
|
+
* This would render the actual chat UI
|
|
3081
|
+
* For now, create a placeholder
|
|
3082
|
+
*/
|
|
3083
|
+
const chatInterface = createElement('div', {
|
|
3084
|
+
className: CSS_CLASSES.CHAT_INTERFACE,
|
|
3085
|
+
styles: {
|
|
3086
|
+
width: '100%',
|
|
3087
|
+
height: '100%',
|
|
3088
|
+
display: 'flex',
|
|
3089
|
+
flexDirection: 'column',
|
|
3090
|
+
backgroundColor: this.themeManager.getColor('background'),
|
|
3091
|
+
color: this.themeManager.getColor('text'),
|
|
3092
|
+
border: `1px solid ${this.themeManager.getColor('border')}`,
|
|
3093
|
+
borderRadius: '8px',
|
|
3094
|
+
},
|
|
3095
|
+
children: [
|
|
3096
|
+
createElement('div', {
|
|
3097
|
+
styles: {
|
|
3098
|
+
padding: '16px',
|
|
3099
|
+
textAlign: 'center',
|
|
3100
|
+
fontSize: '14px',
|
|
3101
|
+
color: this.themeManager.getColor('textSecondary'),
|
|
3102
|
+
},
|
|
3103
|
+
children: ['Chat interface will be implemented here'],
|
|
3104
|
+
}),
|
|
3105
|
+
],
|
|
3106
|
+
});
|
|
3107
|
+
this.widgetElement.appendChild(chatInterface);
|
|
3108
|
+
}
|
|
3109
|
+
applyTheme() {
|
|
3110
|
+
if (!this.widgetElement)
|
|
3111
|
+
return;
|
|
3112
|
+
const theme = this.themeManager.getComputedTheme();
|
|
3113
|
+
// Apply CSS custom properties for theme colors
|
|
3114
|
+
Object.entries(theme.palette).forEach(([key, value]) => {
|
|
3115
|
+
this.widgetElement.style.setProperty(`--aomi-${key}`, value);
|
|
3116
|
+
});
|
|
3117
|
+
// Apply theme class
|
|
3118
|
+
this.widgetElement.className = [
|
|
3119
|
+
CSS_CLASSES.WIDGET_ROOT,
|
|
3120
|
+
CSS_CLASSES.WIDGET_CONTAINER,
|
|
3121
|
+
this.themeManager.getThemeClass(),
|
|
3122
|
+
this.getModeClass(),
|
|
3123
|
+
].join(' ');
|
|
3124
|
+
}
|
|
3125
|
+
getModeClass() {
|
|
3126
|
+
switch (this.config.params.mode) {
|
|
3127
|
+
case 'minimal':
|
|
3128
|
+
return CSS_CLASSES.MODE_MINIMAL;
|
|
3129
|
+
case 'compact':
|
|
3130
|
+
return CSS_CLASSES.MODE_COMPACT;
|
|
3131
|
+
case 'terminal':
|
|
3132
|
+
return CSS_CLASSES.MODE_TERMINAL;
|
|
3133
|
+
default:
|
|
3134
|
+
return CSS_CLASSES.MODE_FULL;
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
updateDimensions() {
|
|
3138
|
+
if (!this.widgetElement)
|
|
3139
|
+
return;
|
|
3140
|
+
this.widgetElement.style.width = this.config.params.width || DEFAULT_WIDGET_WIDTH;
|
|
3141
|
+
this.widgetElement.style.height = this.config.params.height || DEFAULT_WIDGET_HEIGHT;
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
/*
|
|
3145
|
+
* ============================================================================
|
|
3146
|
+
* WIDGET FACTORY FUNCTION
|
|
3147
|
+
* ============================================================================
|
|
3148
|
+
*/
|
|
3149
|
+
/**
|
|
3150
|
+
* Creates and initializes an Aomi Chat Widget
|
|
3151
|
+
*/
|
|
3152
|
+
function createAomiChatWidget(container, config) {
|
|
3153
|
+
// Validate environment
|
|
3154
|
+
if (!isBrowser()) {
|
|
3155
|
+
throw createConfigurationError('Widget can only be created in a browser environment');
|
|
3156
|
+
}
|
|
3157
|
+
// Validate container
|
|
3158
|
+
if (!container || !(container instanceof HTMLElement)) {
|
|
3159
|
+
throw createConfigurationError('Container must be a valid HTML element');
|
|
3160
|
+
}
|
|
3161
|
+
// Validate configuration
|
|
3162
|
+
const errors = validateWidgetParams(config.params);
|
|
3163
|
+
if (errors.length > 0) {
|
|
3164
|
+
throw createConfigurationError(`Configuration errors: ${errors.join(', ')}`);
|
|
3165
|
+
}
|
|
3166
|
+
// Create and return widget handler
|
|
3167
|
+
return new AomiChatWidgetHandlerImpl(container, config);
|
|
3168
|
+
}
|
|
3169
|
+
|
|
3170
|
+
// Main entry point for the Aomi Chat Widget Library
|
|
3171
|
+
// Package version (this would be set during build)
|
|
3172
|
+
const VERSION = '0.1.0';
|
|
3173
|
+
/* eslint-enable no-unused-vars, @typescript-eslint/no-unused-vars */
|
|
3174
|
+
// Simple convenience function for basic usage
|
|
3175
|
+
function createChatWidget(containerId, options) {
|
|
3176
|
+
// Get container element
|
|
3177
|
+
const container = typeof containerId === 'string'
|
|
3178
|
+
? document.getElementById(containerId)
|
|
3179
|
+
: containerId;
|
|
3180
|
+
if (!container) {
|
|
3181
|
+
throw createConfigurationError(typeof containerId === 'string'
|
|
3182
|
+
? `Element with id "${containerId}" not found`
|
|
3183
|
+
: 'Invalid container element');
|
|
3184
|
+
}
|
|
3185
|
+
// Create widget configuration
|
|
3186
|
+
const config = {
|
|
3187
|
+
params: {
|
|
3188
|
+
appCode: options.appCode,
|
|
3189
|
+
theme: options.theme,
|
|
3190
|
+
width: options.width,
|
|
3191
|
+
height: options.height,
|
|
3192
|
+
baseUrl: options.baseUrl,
|
|
3193
|
+
},
|
|
3194
|
+
provider: options.provider,
|
|
3195
|
+
listeners: {
|
|
3196
|
+
onReady: options.onReady,
|
|
3197
|
+
onMessage: options.onMessage,
|
|
3198
|
+
onError: options.onError,
|
|
3199
|
+
},
|
|
3200
|
+
};
|
|
3201
|
+
return createAomiChatWidget(container, config);
|
|
3202
|
+
}
|
|
3203
|
+
// Default export for convenience
|
|
3204
|
+
var index = {
|
|
3205
|
+
createChatWidget,
|
|
3206
|
+
createAomiChatWidget,
|
|
3207
|
+
VERSION,
|
|
3208
|
+
SUPPORTED_CHAINS,
|
|
3209
|
+
PREDEFINED_THEMES,
|
|
3210
|
+
ERROR_CODES,
|
|
3211
|
+
WIDGET_EVENTS,
|
|
3212
|
+
};
|
|
3213
|
+
|
|
3214
|
+
export { API_ENDPOINTS, CSS_CLASSES, ChatError, ChatManager, ConfigurationError, ConnectionError, DEFAULT_CHAIN_ID, DEFAULT_MAX_HEIGHT, DEFAULT_MESSAGE_LENGTH, DEFAULT_RECONNECT_ATTEMPTS, DEFAULT_RECONNECT_DELAY, DEFAULT_WIDGET_HEIGHT, DEFAULT_WIDGET_WIDTH, ERROR_CODES, PREDEFINED_THEMES, RateLimitError, SUPPORTED_CHAINS, SessionError, THEME_PALETTES, TIMING, ThemeManager, TransactionError, VERSION, WIDGET_EVENTS, WalletError, WalletManager, buildWidgetUrl, createAomiChatWidget, createChatError, createChatWidget, createConfigurationError, createConnectionError, createCustomPalette, createElement, createRateLimitError, createSessionError, createThemeManager, createTransactionError, createWalletError, createWalletManager, index as default, delay, detectWallets, formatNumber, formatTimestamp, generateSessionId, getAvailableThemes, getViewportDimensions, hasProperty, isAomiChatError, isBrowser, isChatError, isConfigurationError, isConnectionError, isElementVisible, isEthereumAddress, isMobile, isRateLimitError, isSessionError, isTransactionError, isTransactionHash, isValidProvider, isValidSessionId, isWalletError, parseWidgetParams, resolveFlexibleConfig, retry, truncateAddress, validateCustomPalette, validateWidgetParams, withTimeout };
|
|
3215
|
+
//# sourceMappingURL=index.mjs.map
|