@botonic/webchat-core 2.23.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/CHANGELOG.md +247 -0
- package/PR-LOCALE-SUPPORT.md +156 -0
- package/README.md +11 -0
- package/package.json +29 -0
- package/src/index.d.ts +11 -0
- package/src/index.js +15 -0
- package/src/index.js.map +1 -0
- package/src/lib/controllers/index.d.ts +2 -0
- package/src/lib/controllers/index.js +3 -0
- package/src/lib/controllers/index.js.map +1 -0
- package/src/lib/controllers/public.d.ts +383 -0
- package/src/lib/controllers/public.js +194 -0
- package/src/lib/controllers/public.js.map +1 -0
- package/src/lib/controllers/webchat.d.ts +414 -0
- package/src/lib/controllers/webchat.js +255 -0
- package/src/lib/controllers/webchat.js.map +1 -0
- package/src/lib/domain/index.d.ts +50 -0
- package/src/lib/domain/index.js +11 -0
- package/src/lib/domain/index.js.map +1 -0
- package/src/lib/infra/clients/hubtype-api.d.ts +53 -0
- package/src/lib/infra/clients/hubtype-api.js +189 -0
- package/src/lib/infra/clients/hubtype-api.js.map +1 -0
- package/src/lib/infra/clients/index.d.ts +3 -0
- package/src/lib/infra/clients/index.js +4 -0
- package/src/lib/infra/clients/index.js.map +1 -0
- package/src/lib/infra/clients/realtime.d.ts +92 -0
- package/src/lib/infra/clients/realtime.js +277 -0
- package/src/lib/infra/clients/realtime.js.map +1 -0
- package/src/lib/infra/clients/types.d.ts +21 -0
- package/src/lib/infra/clients/types.js +2 -0
- package/src/lib/infra/clients/types.js.map +1 -0
- package/src/lib/infra/repositories/index.d.ts +1 -0
- package/src/lib/infra/repositories/index.js +2 -0
- package/src/lib/infra/repositories/index.js.map +1 -0
- package/src/lib/infra/repositories/storage/base-storage.d.ts +23 -0
- package/src/lib/infra/repositories/storage/base-storage.js +102 -0
- package/src/lib/infra/repositories/storage/base-storage.js.map +1 -0
- package/src/lib/infra/repositories/storage/browser-storage.d.ts +9 -0
- package/src/lib/infra/repositories/storage/browser-storage.js +46 -0
- package/src/lib/infra/repositories/storage/browser-storage.js.map +1 -0
- package/src/lib/infra/repositories/storage/factory.d.ts +2 -0
- package/src/lib/infra/repositories/storage/factory.js +20 -0
- package/src/lib/infra/repositories/storage/factory.js.map +1 -0
- package/src/lib/infra/repositories/storage/in-memory.d.ts +9 -0
- package/src/lib/infra/repositories/storage/in-memory.js +21 -0
- package/src/lib/infra/repositories/storage/in-memory.js.map +1 -0
- package/src/lib/infra/repositories/storage/index.d.ts +7 -0
- package/src/lib/infra/repositories/storage/index.js +7 -0
- package/src/lib/infra/repositories/storage/index.js.map +1 -0
- package/src/lib/infra/repositories/storage/local-storage.d.ts +4 -0
- package/src/lib/infra/repositories/storage/local-storage.js +7 -0
- package/src/lib/infra/repositories/storage/local-storage.js.map +1 -0
- package/src/lib/infra/repositories/storage/session-storage.d.ts +4 -0
- package/src/lib/infra/repositories/storage/session-storage.js +7 -0
- package/src/lib/infra/repositories/storage/session-storage.js.map +1 -0
- package/src/lib/infra/repositories/storage/types.d.ts +30 -0
- package/src/lib/infra/repositories/storage/types.js +2 -0
- package/src/lib/infra/repositories/storage/types.js.map +1 -0
- package/src/lib/machines/message.d.ts +200 -0
- package/src/lib/machines/message.js +210 -0
- package/src/lib/machines/message.js.map +1 -0
- package/src/lib/machines/overlay.d.ts +299 -0
- package/src/lib/machines/overlay.js +217 -0
- package/src/lib/machines/overlay.js.map +1 -0
- package/src/lib/machines/webchat.d.ts +641 -0
- package/src/lib/machines/webchat.js +768 -0
- package/src/lib/machines/webchat.js.map +1 -0
- package/src/lib/services/webchat-service.d.ts +125 -0
- package/src/lib/services/webchat-service.js +339 -0
- package/src/lib/services/webchat-service.js.map +1 -0
- package/src/lib/utils/locale-detection.d.ts +2 -0
- package/src/lib/utils/locale-detection.js +20 -0
- package/src/lib/utils/locale-detection.js.map +1 -0
- package/src/lib/utils/timezone-to-country.d.ts +1 -0
- package/src/lib/utils/timezone-to-country.js +105 -0
- package/src/lib/utils/timezone-to-country.js.map +1 -0
- package/src/lib/webview/constants.d.ts +10 -0
- package/src/lib/webview/constants.js +10 -0
- package/src/lib/webview/constants.js.map +1 -0
- package/src/lib/webview/index.d.ts +3 -0
- package/src/lib/webview/index.js +4 -0
- package/src/lib/webview/index.js.map +1 -0
- package/src/lib/webview/types.d.ts +35 -0
- package/src/lib/webview/types.js +16 -0
- package/src/lib/webview/types.js.map +1 -0
- package/src/lib/webview/utils.d.ts +9 -0
- package/src/lib/webview/utils.js +33 -0
- package/src/lib/webview/utils.js.map +1 -0
|
@@ -0,0 +1,768 @@
|
|
|
1
|
+
import { __awaiter } from "tslib";
|
|
2
|
+
import { MessageSentBy, WebchatMessageFactory, } from '@botonic/shared';
|
|
3
|
+
import { v7 as uuidv7 } from 'uuid';
|
|
4
|
+
import { and, assertEvent, assign, createActor, emit, fromPromise, setup, } from 'xstate';
|
|
5
|
+
import { createStorageRepository, } from '../infra/repositories';
|
|
6
|
+
import { WebchatService, } from '../services/webchat-service';
|
|
7
|
+
import { detectUserLocale } from '../utils/locale-detection';
|
|
8
|
+
import { createWebchatMessageMachine, WebchatMessageStatus, } from './message';
|
|
9
|
+
import { OverlayMachine } from './overlay';
|
|
10
|
+
// Helper function to generate anonymous users with locale props
|
|
11
|
+
function generateAnonymousUser(localeProps, extraData) {
|
|
12
|
+
const userId = `${uuidv7()}`;
|
|
13
|
+
const userLocale = detectUserLocale(localeProps);
|
|
14
|
+
return Object.assign(Object.assign({ id: userId, name: 'Anonymous User' }, userLocale), { extra_data: extraData });
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Helper function to create a postMessage actor for sending messages to the backend
|
|
18
|
+
*/
|
|
19
|
+
function createPostMessageActor(webchatService) {
|
|
20
|
+
return fromPromise((_a) => __awaiter(this, [_a], void 0, function* ({ input: { message, user }, }) {
|
|
21
|
+
if (!webchatService) {
|
|
22
|
+
throw new Error('Webchat service not initialized');
|
|
23
|
+
}
|
|
24
|
+
const response = yield webchatService.sendMessage(message, user);
|
|
25
|
+
if (!response.success) {
|
|
26
|
+
throw new Error(response.error || `Failed to send message`);
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
message: Promise.resolve({ detail: 'sent' }),
|
|
30
|
+
};
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Main webchat machine that handles the complete lifecycle
|
|
35
|
+
*
|
|
36
|
+
* Flow:
|
|
37
|
+
* 1. idle → initializing (setup services, load session)
|
|
38
|
+
* 2. initializing → operational (ready for messaging)
|
|
39
|
+
* 3. operational → (handle messages, connections, UI state)
|
|
40
|
+
*/
|
|
41
|
+
export const WebchatMachine = setup({
|
|
42
|
+
types: {},
|
|
43
|
+
actors: {
|
|
44
|
+
// Initialize webchat service with all dependencies
|
|
45
|
+
initializeWebchatService: fromPromise((_a) => __awaiter(void 0, [_a], void 0, function* ({ input }) {
|
|
46
|
+
const { config } = input;
|
|
47
|
+
// Initialize storage repository with config
|
|
48
|
+
const storageRepository = createStorageRepository(config.storage);
|
|
49
|
+
const webchatServiceConfig = {
|
|
50
|
+
appId: config.appId,
|
|
51
|
+
apiUrl: config.apiUrl,
|
|
52
|
+
timeout: 10000,
|
|
53
|
+
};
|
|
54
|
+
const webchatService = new WebchatService(webchatServiceConfig);
|
|
55
|
+
return { webchatService, storageRepository };
|
|
56
|
+
})),
|
|
57
|
+
// Load messages from storage
|
|
58
|
+
loadMessages: fromPromise((_a) => __awaiter(void 0, [_a], void 0, function* ({ input, }) {
|
|
59
|
+
const { storageRepository, appId, userId } = input;
|
|
60
|
+
return yield storageRepository.findMessagesByConversation(appId, userId);
|
|
61
|
+
})),
|
|
62
|
+
// Load last user from storage
|
|
63
|
+
loadLastUser: fromPromise((_a) => __awaiter(void 0, [_a], void 0, function* ({ input, }) {
|
|
64
|
+
const { storageRepository, appId } = input;
|
|
65
|
+
const session = yield storageRepository.findLastUserSession(appId);
|
|
66
|
+
return session || null;
|
|
67
|
+
})),
|
|
68
|
+
// Initialize realtime connection
|
|
69
|
+
initializeRealtimeConnection: fromPromise((_a) => __awaiter(void 0, [_a], void 0, function* ({ input, }) {
|
|
70
|
+
const { webchatService, user, messages } = input;
|
|
71
|
+
const result = yield webchatService.connect(user, messages);
|
|
72
|
+
if (!result.success) {
|
|
73
|
+
throw new Error(result.error || 'Failed to initialize realtime connection');
|
|
74
|
+
}
|
|
75
|
+
return { success: true };
|
|
76
|
+
})),
|
|
77
|
+
// Reconnect to realtime
|
|
78
|
+
reconnectToRealtime: fromPromise((_a) => __awaiter(void 0, [_a], void 0, function* ({ input, }) {
|
|
79
|
+
const { webchatService, user, messages, attempt } = input;
|
|
80
|
+
// Calculate backoff delay
|
|
81
|
+
const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
|
|
82
|
+
// Wait before attempting reconnection
|
|
83
|
+
if (attempt > 0) {
|
|
84
|
+
yield new Promise(resolve => setTimeout(resolve, delay));
|
|
85
|
+
}
|
|
86
|
+
// Attempt to reconnect
|
|
87
|
+
const result = yield webchatService.connect(user, messages);
|
|
88
|
+
if (!result.success) {
|
|
89
|
+
throw new Error(result.error || 'Failed to reconnect');
|
|
90
|
+
}
|
|
91
|
+
return { success: true };
|
|
92
|
+
})),
|
|
93
|
+
},
|
|
94
|
+
actions: {
|
|
95
|
+
// Actions extracted from connection machine
|
|
96
|
+
assignWebchatService: assign({
|
|
97
|
+
webchatService: ({ event }) => event.output.webchatService,
|
|
98
|
+
storageRepository: ({ event }) => event.output.storageRepository,
|
|
99
|
+
appId: ({ context }) => context.config.appId,
|
|
100
|
+
}),
|
|
101
|
+
assignLoadedUser: assign({
|
|
102
|
+
user: ({ context, event }) => {
|
|
103
|
+
// If we already have a user in context (from createAnonymousUser), use it
|
|
104
|
+
if (context.user)
|
|
105
|
+
return context.user;
|
|
106
|
+
// Otherwise use the loaded user from storage
|
|
107
|
+
return event.output;
|
|
108
|
+
},
|
|
109
|
+
}),
|
|
110
|
+
createAnonymousUser: assign({
|
|
111
|
+
user: ({ context }) => {
|
|
112
|
+
// Extract locale props and extra_data from config
|
|
113
|
+
const { locale, country, system_locale, extra_data } = context.config;
|
|
114
|
+
const newUser = generateAnonymousUser({ locale, country, system_locale }, extra_data);
|
|
115
|
+
return newUser;
|
|
116
|
+
},
|
|
117
|
+
}),
|
|
118
|
+
assignLoadedMessages: assign({
|
|
119
|
+
messages: ({ event }) => event.output,
|
|
120
|
+
}),
|
|
121
|
+
spawnPersistedMessageActors: assign({
|
|
122
|
+
messageActors: ({ context, spawn }) => {
|
|
123
|
+
var _a;
|
|
124
|
+
const actors = new Map();
|
|
125
|
+
(_a = context.messages) === null || _a === void 0 ? void 0 : _a.forEach(storedMessage => {
|
|
126
|
+
const message = WebchatMessageFactory.fromStoredMessage(storedMessage);
|
|
127
|
+
// Create API actors using helper functions
|
|
128
|
+
const postMessageActor = createPostMessageActor(context.webchatService);
|
|
129
|
+
// Use factory function to create machine logic and spawn with input
|
|
130
|
+
const actor = spawn(createWebchatMessageMachine(postMessageActor), {
|
|
131
|
+
input: {
|
|
132
|
+
message,
|
|
133
|
+
user: context.user,
|
|
134
|
+
status: storedMessage.status, // Preserve stored status!
|
|
135
|
+
},
|
|
136
|
+
systemId: message.id,
|
|
137
|
+
});
|
|
138
|
+
actors.set(message.id, actor);
|
|
139
|
+
});
|
|
140
|
+
return actors;
|
|
141
|
+
},
|
|
142
|
+
}),
|
|
143
|
+
saveUser: (_a) => __awaiter(void 0, [_a], void 0, function* ({ context }) {
|
|
144
|
+
var _b;
|
|
145
|
+
if (!context.storageRepository || !context.user)
|
|
146
|
+
return;
|
|
147
|
+
try {
|
|
148
|
+
const channelData = (_b = context.webchatService) === null || _b === void 0 ? void 0 : _b.getChannelData();
|
|
149
|
+
const userToSave = channelData
|
|
150
|
+
? Object.assign(Object.assign({}, context.user), { botId: channelData.botId, chatId: channelData.chatId }) : context.user;
|
|
151
|
+
yield context.storageRepository.saveUser(context.appId || context.config.appId, userToSave);
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
console.error('Failed to save user:', error);
|
|
155
|
+
}
|
|
156
|
+
}),
|
|
157
|
+
// Forward WEBVIEW.OPEN events from message actors to the overlay actor
|
|
158
|
+
forwardWebviewOpenToOverlay: ({ context, event }) => {
|
|
159
|
+
assertEvent(event, 'WEBVIEW.OPEN');
|
|
160
|
+
context.overlayActor.send({
|
|
161
|
+
type: 'OPEN_WEBVIEW',
|
|
162
|
+
url: event.url,
|
|
163
|
+
title: event.title,
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
// Subscribe to WebchatService message callbacks
|
|
167
|
+
subscribeToWebchatService: ({ context, self }) => {
|
|
168
|
+
if (!context.webchatService) {
|
|
169
|
+
console.warn('⚠️ No webchat service available for subscriptions');
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
// Subscribe to received messages from bot
|
|
173
|
+
const unsubscribeMessages = context.webchatService.onMessageReceived((message) => {
|
|
174
|
+
self.send({
|
|
175
|
+
type: 'MESSAGE_RECEIVED',
|
|
176
|
+
message,
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
// Subscribe to connection status changes (better than polling!)
|
|
180
|
+
const unsubscribeStatus = context.webchatService.onConnectionStatusChange(status => {
|
|
181
|
+
console.log(`🔄 Connection status changed: ${status}`);
|
|
182
|
+
self.send({
|
|
183
|
+
type: 'CONNECTION_STATUS_CHANGED',
|
|
184
|
+
status,
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
context._unsubscribeMessages = unsubscribeMessages;
|
|
188
|
+
context._unsubscribeStatus = unsubscribeStatus;
|
|
189
|
+
},
|
|
190
|
+
// Cleanup webchat service subscriptions
|
|
191
|
+
cleanupWebchatServiceSubscriptions: ({ context }) => {
|
|
192
|
+
if (context._unsubscribeMessages) {
|
|
193
|
+
;
|
|
194
|
+
context._unsubscribeMessages();
|
|
195
|
+
}
|
|
196
|
+
if (context._unsubscribeStatus) {
|
|
197
|
+
;
|
|
198
|
+
context._unsubscribeStatus();
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
// Toggle webchat open/close (direct handling)
|
|
202
|
+
toggleWebchat: assign({
|
|
203
|
+
isOpen: ({ context, event }) => {
|
|
204
|
+
assertEvent(event, 'TOGGLE_WEBCHAT');
|
|
205
|
+
return event.isOpen !== undefined ? event.isOpen : !context.isOpen;
|
|
206
|
+
},
|
|
207
|
+
}),
|
|
208
|
+
emitWebchatToggled: emit(({ context, event }) => {
|
|
209
|
+
assertEvent(event, 'TOGGLE_WEBCHAT');
|
|
210
|
+
const newIsOpen = event.isOpen !== undefined ? event.isOpen : !context.isOpen;
|
|
211
|
+
return newIsOpen
|
|
212
|
+
? { type: 'WEBCHAT_OPENED' }
|
|
213
|
+
: { type: 'WEBCHAT_CLOSED' };
|
|
214
|
+
}),
|
|
215
|
+
// Update connection status in context
|
|
216
|
+
updateConnectionStatus: assign({
|
|
217
|
+
connectionStatus: ({ event }) => {
|
|
218
|
+
assertEvent(event, 'CONNECTION_STATUS_CHANGED');
|
|
219
|
+
return event.status;
|
|
220
|
+
},
|
|
221
|
+
}),
|
|
222
|
+
// Add message by spawning actor (single source of truth)
|
|
223
|
+
addMessage: assign({
|
|
224
|
+
messageActors: ({ context, event, spawn }) => {
|
|
225
|
+
assertEvent(event, ['SEND_MESSAGE', 'MESSAGE_RECEIVED']);
|
|
226
|
+
console.log('addMessage', event.message.type);
|
|
227
|
+
const message = event.message;
|
|
228
|
+
// Get unique identifier for the message
|
|
229
|
+
// Note: After domain refactoring, different message types use different ID fields:
|
|
230
|
+
// - UserInputMessage uses provider_id
|
|
231
|
+
// - RealtimeResponseMessage uses message_id
|
|
232
|
+
const messageId = message.id;
|
|
233
|
+
// Check if actor already exists
|
|
234
|
+
const currentActors = context.messageActors || new Map();
|
|
235
|
+
if (currentActors.has(messageId)) {
|
|
236
|
+
return currentActors;
|
|
237
|
+
}
|
|
238
|
+
// Determine status based on event type
|
|
239
|
+
// IMPORTANT: After domain refactoring, UserInputMessage doesn't have sent_by property
|
|
240
|
+
// Only RealtimeResponseMessage (bot messages) have sent_by
|
|
241
|
+
// So we determine status purely from event type:
|
|
242
|
+
// - SEND_MESSAGE = user input that needs to be sent via API (UNSENT)
|
|
243
|
+
// - MESSAGE_RECEIVED = bot response already sent via Pusher (SENT)
|
|
244
|
+
const status = event.type === 'MESSAGE_RECEIVED'
|
|
245
|
+
? WebchatMessageStatus.SENT
|
|
246
|
+
: message.sent_by === MessageSentBy.User
|
|
247
|
+
? WebchatMessageStatus.UNSENT
|
|
248
|
+
: WebchatMessageStatus.SENT;
|
|
249
|
+
// Create actors for API operations using helper functions
|
|
250
|
+
const postMessageActor = createPostMessageActor(context.webchatService);
|
|
251
|
+
// Use factory function to create machine logic and spawn with input
|
|
252
|
+
const actor = spawn(createWebchatMessageMachine(postMessageActor), {
|
|
253
|
+
input: {
|
|
254
|
+
message,
|
|
255
|
+
user: context.user,
|
|
256
|
+
status,
|
|
257
|
+
},
|
|
258
|
+
systemId: message.id,
|
|
259
|
+
});
|
|
260
|
+
// Update actors registry using the appropriate ID as key
|
|
261
|
+
const newActors = new Map(currentActors);
|
|
262
|
+
newActors.set(messageId, actor);
|
|
263
|
+
return newActors;
|
|
264
|
+
},
|
|
265
|
+
}),
|
|
266
|
+
// Save all messages to storage (derived from messageActors)
|
|
267
|
+
saveMessages: (_a) => __awaiter(void 0, [_a], void 0, function* ({ context }) {
|
|
268
|
+
if (!context.storageRepository || !context.user) {
|
|
269
|
+
console.warn('💾 PERSISTENCE: Cannot save - missing storage repository or user');
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
// Derive current messages from messageActors (single source of truth)
|
|
274
|
+
const currentMessages = [];
|
|
275
|
+
for (const [messageId, actor] of context.messageActors || new Map()) {
|
|
276
|
+
const snapshot = actor.getSnapshot();
|
|
277
|
+
const message = snapshot.context.message;
|
|
278
|
+
const status = snapshot.context.status;
|
|
279
|
+
// Create simple StoredMessage object
|
|
280
|
+
currentMessages.push(Object.assign({ status }, message));
|
|
281
|
+
}
|
|
282
|
+
// Use simple strings for the repository call
|
|
283
|
+
const appId = context.appId || context.config.appId;
|
|
284
|
+
const userId = context.user.id;
|
|
285
|
+
yield context.storageRepository.saveAllMessages(appId, userId, currentMessages);
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
console.error('💾 PERSISTENCE: ❌ Failed to save messages:', error);
|
|
289
|
+
}
|
|
290
|
+
}),
|
|
291
|
+
// Emit message added event
|
|
292
|
+
emitMessageAdded: emit(({ context, event }) => {
|
|
293
|
+
assertEvent(event, ['SEND_MESSAGE', 'MESSAGE_RECEIVED']);
|
|
294
|
+
const message = event.message;
|
|
295
|
+
// Get ID based on message type (domain refactoring: different ID fields)
|
|
296
|
+
const messageId = message.id;
|
|
297
|
+
return {
|
|
298
|
+
type: 'MESSAGE_ADDED',
|
|
299
|
+
message: event.message,
|
|
300
|
+
// Lookup actor using the appropriate ID field
|
|
301
|
+
actor: (context.messageActors || new Map()).get(messageId),
|
|
302
|
+
};
|
|
303
|
+
}),
|
|
304
|
+
// New emit actions for enhanced event system
|
|
305
|
+
emitMessageSent: emit(({ event }) => {
|
|
306
|
+
assertEvent(event, 'SEND_MESSAGE');
|
|
307
|
+
return {
|
|
308
|
+
type: 'MESSAGE_SENT',
|
|
309
|
+
message: event.message,
|
|
310
|
+
};
|
|
311
|
+
}),
|
|
312
|
+
emitMessageReceived: emit(({ event }) => {
|
|
313
|
+
assertEvent(event, 'MESSAGE_RECEIVED');
|
|
314
|
+
return {
|
|
315
|
+
type: 'MESSAGE_RECEIVED',
|
|
316
|
+
message: event.message,
|
|
317
|
+
};
|
|
318
|
+
}),
|
|
319
|
+
emitMessageUpdated: emit(({ event }) => {
|
|
320
|
+
assertEvent(event, 'UPDATE_MESSAGE');
|
|
321
|
+
return {
|
|
322
|
+
type: 'MESSAGE_UPDATED',
|
|
323
|
+
messageId: event.messageId,
|
|
324
|
+
updates: event.updates,
|
|
325
|
+
};
|
|
326
|
+
}),
|
|
327
|
+
emitMessagesCleared: emit(() => ({
|
|
328
|
+
type: 'MESSAGES_CLEARED',
|
|
329
|
+
})),
|
|
330
|
+
emitUserUpdated: emit(({ event }) => {
|
|
331
|
+
assertEvent(event, 'UPDATE_USER');
|
|
332
|
+
return {
|
|
333
|
+
type: 'USER_UPDATED',
|
|
334
|
+
user: event.user,
|
|
335
|
+
};
|
|
336
|
+
}),
|
|
337
|
+
emitWebchatOpened: emit(() => ({
|
|
338
|
+
type: 'WEBCHAT_OPENED',
|
|
339
|
+
})),
|
|
340
|
+
emitWebchatClosed: emit(() => ({
|
|
341
|
+
type: 'WEBCHAT_CLOSED',
|
|
342
|
+
})),
|
|
343
|
+
// Initialization emit actions
|
|
344
|
+
emitInitializationStarted: emit(() => ({
|
|
345
|
+
type: 'INITIALIZATION_STARTED',
|
|
346
|
+
})),
|
|
347
|
+
emitInitializationCompleted: emit(({ context }) => ({
|
|
348
|
+
type: 'INITIALIZATION_COMPLETED',
|
|
349
|
+
context: context,
|
|
350
|
+
})),
|
|
351
|
+
emitInitializationFailed: emit(({ event }) => {
|
|
352
|
+
// Extract error from the event or provide a default
|
|
353
|
+
const error = event.error || 'Initialization failed';
|
|
354
|
+
return {
|
|
355
|
+
type: 'INITIALIZATION_FAILED',
|
|
356
|
+
error: typeof error === 'string' ? error : error.message || 'Unknown error',
|
|
357
|
+
};
|
|
358
|
+
}),
|
|
359
|
+
// Connection emit actions
|
|
360
|
+
emitConnectionChanged: emit(({ event }) => {
|
|
361
|
+
assertEvent(event, 'CONNECTION_STATUS_CHANGED');
|
|
362
|
+
return {
|
|
363
|
+
type: 'CONNECTION_CHANGED',
|
|
364
|
+
status: event.status,
|
|
365
|
+
};
|
|
366
|
+
}),
|
|
367
|
+
emitMessageDeleted: emit(({ event }) => {
|
|
368
|
+
assertEvent(event, 'DELETE_MESSAGE');
|
|
369
|
+
return {
|
|
370
|
+
type: 'MESSAGE_DELETED',
|
|
371
|
+
messageId: event.messageId,
|
|
372
|
+
};
|
|
373
|
+
}),
|
|
374
|
+
// New assign actions for state management
|
|
375
|
+
updateUser: assign({
|
|
376
|
+
user: ({ context, event }) => {
|
|
377
|
+
assertEvent(event, 'UPDATE_USER');
|
|
378
|
+
return Object.assign(Object.assign({}, context.user), event.user);
|
|
379
|
+
},
|
|
380
|
+
}),
|
|
381
|
+
clearMessages: assign({
|
|
382
|
+
messageActors: () => new Map(),
|
|
383
|
+
}),
|
|
384
|
+
updateMessage: assign({
|
|
385
|
+
messageActors: ({ context, event }) => {
|
|
386
|
+
assertEvent(event, 'UPDATE_MESSAGE');
|
|
387
|
+
const actors = new Map(context.messageActors);
|
|
388
|
+
const actor = actors.get(event.messageId);
|
|
389
|
+
// if (actor) {
|
|
390
|
+
// // Send update to the message actor
|
|
391
|
+
// actor.send({
|
|
392
|
+
// type: 'UPDATE_MESSAGE_INFO',
|
|
393
|
+
// message: {
|
|
394
|
+
// id: event.messageId,
|
|
395
|
+
// data: event.updates as any,
|
|
396
|
+
// },
|
|
397
|
+
// })
|
|
398
|
+
// }
|
|
399
|
+
return actors;
|
|
400
|
+
},
|
|
401
|
+
}),
|
|
402
|
+
deleteMessage: assign({
|
|
403
|
+
messageActors: ({ context, event }) => {
|
|
404
|
+
assertEvent(event, 'DELETE_MESSAGE');
|
|
405
|
+
const actors = new Map(context.messageActors);
|
|
406
|
+
actors.delete(event.messageId);
|
|
407
|
+
return actors;
|
|
408
|
+
},
|
|
409
|
+
}),
|
|
410
|
+
setTyping: assign({
|
|
411
|
+
isTypingOn: ({ event }) => {
|
|
412
|
+
assertEvent(event, 'MESSAGE_RECEIVED');
|
|
413
|
+
return event.message.isTypingOn;
|
|
414
|
+
},
|
|
415
|
+
}),
|
|
416
|
+
setTypingExternal: assign({
|
|
417
|
+
isTypingOn: ({ event }) => {
|
|
418
|
+
assertEvent(event, 'SET_TYPING');
|
|
419
|
+
return event.isTypingOn;
|
|
420
|
+
},
|
|
421
|
+
}),
|
|
422
|
+
setOnline: ({ context, event }) => {
|
|
423
|
+
assertEvent(event, 'SET_ONLINE');
|
|
424
|
+
// Handle online/offline status
|
|
425
|
+
if (context.webchatService) {
|
|
426
|
+
// TODO: Implement online status in WebchatService
|
|
427
|
+
// context.webchatService.setOnline(event.online)
|
|
428
|
+
}
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
guards: {
|
|
432
|
+
isInitialized: ({ context }) => !!context.user && !!context.webchatService,
|
|
433
|
+
hasLoadedUser: ({ event }) => {
|
|
434
|
+
// This is safe because this guard is only called in the context of loadLastUser's onDone
|
|
435
|
+
return event.output !== null;
|
|
436
|
+
},
|
|
437
|
+
isMaxReconnectAttempts: ({ context }) => (context.reconnectAttempt || 0) >= 5,
|
|
438
|
+
isTypingEvent: ({ event }) => {
|
|
439
|
+
assertEvent(event, ['MESSAGE_RECEIVED']);
|
|
440
|
+
return event.message.isTypingEvent();
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
}).createMachine({
|
|
444
|
+
/** @xstate-layout N4IgpgJg5mDOIC5QHcwCMDGALAhgFwDoBLCAGzAGIBJAOSoBUqBBAGSoC0BRAbQAYBdRKAAOAe1hE8RUQDshIAB6IAjACYArAQBs6gBy6ALAHYAnAGZel9Vq0AaEAE8V63kYImb6o731Gz65TMAXyD7VExcQiIZSSIcUiIAL2ioYhipeKSUgGUwACcANyIMOAoIWTA0gtEAa0rw7Hw02MzkmVToloS2qFzC4rgEaOqMfGkZPn5J+TEJKVl5JQQvAwI9Ay1eLVVdDVUTXXsnBGV1awI1VUsdU91edRMQsPRGqPS47pTmjM-2vqKSrAKPk8qI8gRhKR8AAzMEAWwIDUi3w+WXaKNaOXyAMGw1Eo3mEwE0yQIFmsQWpKWGkMBDMuxMqiMgQOZgM6iOKjMyguBkM9LMqgsrgMBieICRTU6PzRqVIohwECxsAksjKFSqtXqL2R0tRPQI8sVytVMiGMhGY1kkxJInEFLkVJU900m14yhMRjOWiMu1UnJOBjZBFMqlUxk9ljZYtCEp1UvemPRRqVfzgpvVMkqeLqiPjby6ssNCtTvXT43NlsJNuUglJ5MJi2dZ20lg9XusvrDAdOWlWylFGxMPh2ujU4slBZlBpTJvGwLyoPBkJh8LzEQThZnJbnskr+KtRKmAhm9sbToQGhMq3p+yZLN0bI5jhURjcntUOg7Pq0-l0E-zDFfjlHd2gAWXTHAYCBcos01XNJyAotZ3AyDoP3AlxhtE96zPcYmxOLRlB5LRdCMAwTEZPQB2sHsLC0Ol1A0LRKN4YctgAjcp31L4UKgCCVSg0oQTBCEoTwWE8gRRC9STEDjVQwT0LxTDrWJHC7TmfCL2UIiSLIiiqLHdk7BfQNmRDMwbDItRqMeWMZMTYCCFgYQcGQGJ2gABXyCRYDwSABNgISgVtMk8MpUBqVOXg1h8EwAl4GldHUAw6P2AhXGZMNfQCdl1E414kINDBZCzDApHaehRAAJTAeIpDhShYOzC0tXXIrZOc0qZHKyqoGquqGqIJqMMPbC600h0CJpG8GXvMxWXZHsmTcO4-AHDtTGCBzAK6oser6lJBvq0hGsoETl3EyTpL2pyDrKsAKuO2rTvOsbq3Uybwq0yLFEQWa6Xm5lFsfZazIHIU6TYyjRTuRbP0K5FDqewkADEcCIcgIAoGrOHoGqAE0AH0AGEAHkaBoThScYSmwobbSooBvRLN2HQ+TZIxwwDMc6TDExlFMX0WLDJGmjyJ7Hue9pM1a6oEMAyWUZlqAPqwr7T1+x1mcvci6WUSw+WIjn9DSiGqJDZRdCInQbFFa3xcIZXpf6hclzE1cpI65EXd61GUnVtTj2+xm-upPsDaNsddNSs2e0fMwCFFdQLHDQx7kd3auIIP2jtly7PYktdELzgP2iDo9uFrLXpovMN6WT31iPdCxh1SgMrkF9w7jOQWks5nbnhzsvVYoUmmBoUnOBYYm8YpqmafoBmIp1-6EHpd9eGN7x4v2DZO+31ZNh9E2kvIrQnYIURhHyQ94nHynqdpqhKeJ7J6CYegAFVsjJgAJSeABxTgAARFe2sCKb3cNvMcu9dCMmvKZY4qgWQEH0GoYc7IkrKCvjfO+hIH4L2fnTGg79P4-z-qTQBNAQHgJrrhSBF4LB82IsYMwFgDCG2tsg18XoCD7F-KGNifZDB4Nvnke+pBcY0yfkvCBdddbQKwXAywCD968MvKOIGLh2TXg4URAq2cir4MkYQ6R9ByZAKASwTgxMADqnAABC1Cv4KPPEogcPJt7Ch8XoQwmjY5JwMK4d014RzeFwcY5EpipEUGyJwGgoDiZgU4NkbITAQHuKZuvbkqdMpkW5J+D0VEzA9mMLoARZsGRESuBscRBDxgP1SekzJdj56cCoAANTAdk8OiBuRWQuC4Ji7pSK7CMHRXgSddKwxuFwhGDSzFNOkS0jJIC54006T0+hodV5QLZDyZkzdfzhl9G+Hs5g3AhP0acN8Y4NBLLiWstp5Cv6-wAcA3pGkfqKNyYbVQ7hU53GwR6XS-oIZXDWglJk3JSJcIMKoJ55iKDfy8qAr+djf6cBqn0teSw4WxTZAcIU60fTdghj4Bi1xLAiyYh4eyw8TESLiaTWxTAaopLSestJeKoEDiTn2Zkj4XD6GmZMsyVy4pXF-GYL02x-DIpWai9FmKuWtKyT8sO+KBkCuTkRN8wiDg2ADL6SplE-RbFTiwpF0SmixJRaAme+M7EvM1XsphnjRQwNIuREJewylmT7LFCiiKjC2ysmyf8drCAOuVQk+gxNKZsGpny5hulKmx3ymofw1qAyIqThw90HoHb6CZDGJlMSWUooTcTeghMvK0CAWmzxPoQz5UZD4AVTIAwBEqZsPk14zgUR0NGyt9rq3Kq8ji7IVAP6JOngQDJPT1U8pbf8ywFxhz7D8HpEWAZBSVJdNGQ9phPRKtkA-RxTiulUE4PYgg5Np00HXQS04qxPwILfElHQehO6ChDL3EljJDbGArXGHOcbL3SNoAwZgbAuCvpZpUvw7M45cx5hDX0gqPS-h9F4UwNgQixhkKICAcB5CTlrh49eABaTRtHNApQQXkt81hjCkSviQcg1GclLERT2UVFwnxfhSpRPQXH7o9F4-0y87I2afnQ8YTDxw2GrAorpH0XYsq2vHdxOSxUsT9EBDJnVl5zAKdNhh82qnU4mHQdtVK8KRRDwg51KTvFQJlhVDk7VBFHyAu8AlcNCUwMJUE6lTKlEPRypombSTW5PMKX4mhCjjC-lLBZDyPRfZiIBCKYEoMgWWJBgCOMoWCXpxfFcu5TyUAfJ5D8gFCAQUQqmYItbCwawnNbD8H2iFqnOH809F4Q29yUqVZ4uiFW-UTrDSau1+u4ZViCxsKcGLY5XDlI2D3Rk5ESkcPHDGggM3xgYyxpARbusdj622m2LYBbnyqf1vsAF28ErXnhlfUe-Urvr1pHKz7HhSKoKFiYTuqCmOkQ5oEMiZEL0yHiH96kCD7O6Q2KlbwVlryXJ2OgqMQoWLmFQdGkIQA */
|
|
445
|
+
id: 'webchat',
|
|
446
|
+
context: ({ input, spawn }) => ({
|
|
447
|
+
config: input,
|
|
448
|
+
// UI state
|
|
449
|
+
isOpen: false,
|
|
450
|
+
connectionStatus: 'disconnected',
|
|
451
|
+
// Operational state - messageActors is the single source of truth
|
|
452
|
+
messageActors: new Map(),
|
|
453
|
+
reconnectAttempt: 0,
|
|
454
|
+
// Initialized context properties (will be set after initialization)
|
|
455
|
+
user: undefined,
|
|
456
|
+
webchatService: undefined,
|
|
457
|
+
storageRepository: undefined,
|
|
458
|
+
appId: undefined,
|
|
459
|
+
isTypingOn: false,
|
|
460
|
+
// Overlay actor (spawned child machine)
|
|
461
|
+
overlayActor: spawn(OverlayMachine, {
|
|
462
|
+
systemId: 'overlay',
|
|
463
|
+
input: { initialState: null },
|
|
464
|
+
}),
|
|
465
|
+
}),
|
|
466
|
+
initial: 'idle',
|
|
467
|
+
states: {
|
|
468
|
+
idle: {
|
|
469
|
+
on: {
|
|
470
|
+
INITIALIZE: 'initializing',
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
initializing: {
|
|
474
|
+
initial: 'initializingServices',
|
|
475
|
+
entry: [
|
|
476
|
+
'emitInitializationStarted',
|
|
477
|
+
assign({
|
|
478
|
+
connectionStatus: () => 'connecting',
|
|
479
|
+
}),
|
|
480
|
+
],
|
|
481
|
+
states: {
|
|
482
|
+
initializingServices: {
|
|
483
|
+
invoke: {
|
|
484
|
+
src: 'initializeWebchatService',
|
|
485
|
+
input: ({ context }) => ({
|
|
486
|
+
config: context.config,
|
|
487
|
+
}),
|
|
488
|
+
onDone: {
|
|
489
|
+
target: 'loadingSession',
|
|
490
|
+
actions: 'assignWebchatService',
|
|
491
|
+
},
|
|
492
|
+
onError: {
|
|
493
|
+
target: '#webchat.connectionFailed',
|
|
494
|
+
},
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
loadingSession: {
|
|
498
|
+
invoke: {
|
|
499
|
+
src: 'loadLastUser',
|
|
500
|
+
input: ({ context }) => ({
|
|
501
|
+
storageRepository: context.storageRepository,
|
|
502
|
+
appId: context.config.appId,
|
|
503
|
+
}),
|
|
504
|
+
onDone: [
|
|
505
|
+
{
|
|
506
|
+
// Existing user found - load their messages
|
|
507
|
+
target: 'loadingMessages',
|
|
508
|
+
guard: 'hasLoadedUser',
|
|
509
|
+
actions: 'assignLoadedUser',
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
// No user found - create anonymous and skip to realtime
|
|
513
|
+
target: 'connectingToRealtime',
|
|
514
|
+
actions: ['createAnonymousUser', 'assignLoadedUser'],
|
|
515
|
+
},
|
|
516
|
+
],
|
|
517
|
+
onError: '#webchat.connectionFailed',
|
|
518
|
+
},
|
|
519
|
+
},
|
|
520
|
+
loadingMessages: {
|
|
521
|
+
invoke: {
|
|
522
|
+
src: 'loadMessages',
|
|
523
|
+
input: ({ context }) => ({
|
|
524
|
+
storageRepository: context.storageRepository,
|
|
525
|
+
appId: context.config.appId,
|
|
526
|
+
userId: context.user.id,
|
|
527
|
+
}),
|
|
528
|
+
onDone: {
|
|
529
|
+
target: 'spawningPersistedMessages',
|
|
530
|
+
actions: 'assignLoadedMessages',
|
|
531
|
+
},
|
|
532
|
+
onError: {
|
|
533
|
+
// If loading messages fails, continue without them
|
|
534
|
+
target: 'connectingToRealtime',
|
|
535
|
+
},
|
|
536
|
+
},
|
|
537
|
+
},
|
|
538
|
+
spawningPersistedMessages: {
|
|
539
|
+
entry: 'spawnPersistedMessageActors',
|
|
540
|
+
always: 'connectingToRealtime',
|
|
541
|
+
},
|
|
542
|
+
connectingToRealtime: {
|
|
543
|
+
invoke: {
|
|
544
|
+
src: 'initializeRealtimeConnection',
|
|
545
|
+
input: ({ context }) => {
|
|
546
|
+
var _a;
|
|
547
|
+
return ({
|
|
548
|
+
webchatService: context.webchatService,
|
|
549
|
+
user: context.user,
|
|
550
|
+
messages: ((_a = context.messages) !== null && _a !== void 0 ? _a : []).map(WebchatMessageFactory.fromStoredMessage),
|
|
551
|
+
});
|
|
552
|
+
},
|
|
553
|
+
onDone: {
|
|
554
|
+
target: '#webchat.operational',
|
|
555
|
+
actions: [
|
|
556
|
+
assign({
|
|
557
|
+
connectionStatus: () => 'connected',
|
|
558
|
+
}),
|
|
559
|
+
'saveUser',
|
|
560
|
+
'emitInitializationCompleted',
|
|
561
|
+
],
|
|
562
|
+
},
|
|
563
|
+
onError: {
|
|
564
|
+
// Continue without realtime if it fails (same as current behavior)
|
|
565
|
+
target: '#webchat.operational',
|
|
566
|
+
actions: [
|
|
567
|
+
assign({
|
|
568
|
+
connectionStatus: () => 'disconnected',
|
|
569
|
+
}),
|
|
570
|
+
'saveUser',
|
|
571
|
+
'emitInitializationCompleted',
|
|
572
|
+
({ event }) => {
|
|
573
|
+
console.warn('⚠️ Realtime connection failed, continuing without realtime:', event);
|
|
574
|
+
},
|
|
575
|
+
],
|
|
576
|
+
},
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
},
|
|
580
|
+
},
|
|
581
|
+
connectionFailed: {
|
|
582
|
+
on: {
|
|
583
|
+
RETRY_CONNECTION: 'initializing',
|
|
584
|
+
},
|
|
585
|
+
},
|
|
586
|
+
reconnecting: {
|
|
587
|
+
entry: assign({
|
|
588
|
+
connectionStatus: () => 'connecting',
|
|
589
|
+
}),
|
|
590
|
+
invoke: {
|
|
591
|
+
src: 'reconnectToRealtime',
|
|
592
|
+
input: ({ context }) => {
|
|
593
|
+
var _a;
|
|
594
|
+
return ({
|
|
595
|
+
webchatService: context.webchatService,
|
|
596
|
+
user: context.user,
|
|
597
|
+
messages: ((_a = context.messages) !== null && _a !== void 0 ? _a : []).map(WebchatMessageFactory.fromStoredMessage),
|
|
598
|
+
attempt: context.reconnectAttempt || 0,
|
|
599
|
+
});
|
|
600
|
+
},
|
|
601
|
+
onDone: {
|
|
602
|
+
target: 'operational',
|
|
603
|
+
actions: [
|
|
604
|
+
assign({
|
|
605
|
+
connectionStatus: () => 'connected',
|
|
606
|
+
reconnectAttempt: () => 0,
|
|
607
|
+
}),
|
|
608
|
+
() => {
|
|
609
|
+
console.log('✅ Reconnected successfully');
|
|
610
|
+
},
|
|
611
|
+
],
|
|
612
|
+
},
|
|
613
|
+
onError: [
|
|
614
|
+
{
|
|
615
|
+
// Max retries reached
|
|
616
|
+
guard: 'isMaxReconnectAttempts',
|
|
617
|
+
target: 'connectionFailed',
|
|
618
|
+
actions: [
|
|
619
|
+
({ event }) => {
|
|
620
|
+
console.error('❌ Max reconnection attempts reached:', event);
|
|
621
|
+
},
|
|
622
|
+
],
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
// Retry with backoff
|
|
626
|
+
target: 'reconnecting',
|
|
627
|
+
reenter: true,
|
|
628
|
+
actions: [
|
|
629
|
+
assign({
|
|
630
|
+
reconnectAttempt: ({ context }) => (context.reconnectAttempt || 0) + 1,
|
|
631
|
+
}),
|
|
632
|
+
({ context }) => {
|
|
633
|
+
const attempt = context.reconnectAttempt || 0;
|
|
634
|
+
const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
|
|
635
|
+
console.log(`⏳ Reconnection attempt ${attempt + 1} failed, retrying in ${delay}ms...`);
|
|
636
|
+
},
|
|
637
|
+
],
|
|
638
|
+
},
|
|
639
|
+
],
|
|
640
|
+
},
|
|
641
|
+
on: {
|
|
642
|
+
CANCEL_RECONNECT: {
|
|
643
|
+
target: 'operational',
|
|
644
|
+
actions: [
|
|
645
|
+
assign({
|
|
646
|
+
connectionStatus: () => 'disconnected',
|
|
647
|
+
reconnectAttempt: () => 0,
|
|
648
|
+
}),
|
|
649
|
+
],
|
|
650
|
+
},
|
|
651
|
+
},
|
|
652
|
+
},
|
|
653
|
+
operational: {
|
|
654
|
+
entry: 'subscribeToWebchatService',
|
|
655
|
+
exit: 'cleanupWebchatServiceSubscriptions',
|
|
656
|
+
on: {
|
|
657
|
+
// Connection monitoring events
|
|
658
|
+
CONNECTION_STATUS_CHANGED: [
|
|
659
|
+
{
|
|
660
|
+
// Auto-reconnect on disconnect
|
|
661
|
+
guard: ({ event }) => event.status === 'disconnected',
|
|
662
|
+
target: 'reconnecting',
|
|
663
|
+
actions: [
|
|
664
|
+
'updateConnectionStatus',
|
|
665
|
+
'emitConnectionChanged',
|
|
666
|
+
() => {
|
|
667
|
+
console.log('📡 Connection lost, attempting to reconnect...');
|
|
668
|
+
},
|
|
669
|
+
],
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
// Other status changes just update context
|
|
673
|
+
actions: [
|
|
674
|
+
'updateConnectionStatus',
|
|
675
|
+
'emitConnectionChanged',
|
|
676
|
+
({ event }) => {
|
|
677
|
+
console.log(`📡 Connection status updated: ${event.status}`);
|
|
678
|
+
},
|
|
679
|
+
],
|
|
680
|
+
},
|
|
681
|
+
],
|
|
682
|
+
// Allow manual reconnection trigger
|
|
683
|
+
RECONNECT: {
|
|
684
|
+
guard: 'isInitialized',
|
|
685
|
+
target: 'reconnecting',
|
|
686
|
+
},
|
|
687
|
+
// Handle operational events directly
|
|
688
|
+
TOGGLE_WEBCHAT: {
|
|
689
|
+
guard: 'isInitialized',
|
|
690
|
+
actions: ['toggleWebchat', 'emitWebchatToggled'],
|
|
691
|
+
},
|
|
692
|
+
SEND_MESSAGE: {
|
|
693
|
+
guard: 'isInitialized',
|
|
694
|
+
actions: [
|
|
695
|
+
'addMessage',
|
|
696
|
+
'saveMessages',
|
|
697
|
+
'emitMessageAdded',
|
|
698
|
+
'emitMessageSent',
|
|
699
|
+
],
|
|
700
|
+
},
|
|
701
|
+
MESSAGE_RECEIVED: [
|
|
702
|
+
{
|
|
703
|
+
guard: and(['isInitialized', 'isTypingEvent']),
|
|
704
|
+
actions: ['setTyping'],
|
|
705
|
+
},
|
|
706
|
+
{
|
|
707
|
+
guard: 'isInitialized',
|
|
708
|
+
actions: [
|
|
709
|
+
'addMessage',
|
|
710
|
+
'saveMessages',
|
|
711
|
+
'emitMessageAdded',
|
|
712
|
+
'emitMessageReceived',
|
|
713
|
+
],
|
|
714
|
+
},
|
|
715
|
+
],
|
|
716
|
+
MESSAGE_STATUS_CHANGED: {
|
|
717
|
+
guard: 'isInitialized',
|
|
718
|
+
actions: ['saveMessages'],
|
|
719
|
+
},
|
|
720
|
+
// User management
|
|
721
|
+
UPDATE_USER: {
|
|
722
|
+
guard: 'isInitialized',
|
|
723
|
+
actions: ['updateUser', 'saveUser', 'emitUserUpdated'],
|
|
724
|
+
},
|
|
725
|
+
// Message management
|
|
726
|
+
CLEAR_MESSAGES: {
|
|
727
|
+
guard: 'isInitialized',
|
|
728
|
+
actions: ['clearMessages', 'saveMessages', 'emitMessagesCleared'],
|
|
729
|
+
},
|
|
730
|
+
UPDATE_MESSAGE: {
|
|
731
|
+
guard: 'isInitialized',
|
|
732
|
+
actions: ['updateMessage', 'saveMessages', 'emitMessageUpdated'],
|
|
733
|
+
},
|
|
734
|
+
DELETE_MESSAGE: {
|
|
735
|
+
guard: 'isInitialized',
|
|
736
|
+
actions: ['deleteMessage', 'saveMessages', 'emitMessageDeleted'],
|
|
737
|
+
},
|
|
738
|
+
// Connection management
|
|
739
|
+
SET_ONLINE: {
|
|
740
|
+
guard: 'isInitialized',
|
|
741
|
+
actions: ['setOnline'],
|
|
742
|
+
},
|
|
743
|
+
// Typing indicator control
|
|
744
|
+
SET_TYPING: {
|
|
745
|
+
guard: 'isInitialized',
|
|
746
|
+
actions: ['setTypingExternal'],
|
|
747
|
+
},
|
|
748
|
+
// Listen for persistence requests from message actors
|
|
749
|
+
'PERSISTENCE.SAVE_MESSAGE': {
|
|
750
|
+
guard: 'isInitialized',
|
|
751
|
+
actions: ['saveMessages'],
|
|
752
|
+
},
|
|
753
|
+
// Forward WEBVIEW.OPEN from message actors to overlay actor
|
|
754
|
+
'WEBVIEW.OPEN': {
|
|
755
|
+
guard: 'isInitialized',
|
|
756
|
+
actions: ['forwardWebviewOpenToOverlay'],
|
|
757
|
+
},
|
|
758
|
+
// Allow re-initialization if needed
|
|
759
|
+
INITIALIZE: 'initializing',
|
|
760
|
+
},
|
|
761
|
+
},
|
|
762
|
+
},
|
|
763
|
+
});
|
|
764
|
+
// Factory function for creating webchat machine actors
|
|
765
|
+
export function createWebchatMachine(webchatConfig) {
|
|
766
|
+
return createActor(WebchatMachine, { input: webchatConfig });
|
|
767
|
+
}
|
|
768
|
+
//# sourceMappingURL=webchat.js.map
|