releasebird-javascript-sdk 1.0.89 → 1.0.91
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/build/index.js +1 -1
- package/package.json +2 -1
- package/published/1.0.89/index.js +1 -1
- package/published/1.0.90/index.js +1 -0
- package/published/1.0.91/index.js +1 -0
- package/published/latest/index.js +1 -1
- package/src/RbirdAutomationManager.js +186 -253
- package/src/RbirdWebsiteWidget.js +13 -14
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import RbirdSessionManager from "./RbirdSessionManager";
|
|
2
|
+
import RbirdWebsiteWidget from "./RbirdWebsiteWidget";
|
|
2
3
|
import { RbirdBannerManager } from "./RbirdBannerManager";
|
|
3
4
|
import { RbirdFormManager } from "./RbirdFormManager";
|
|
4
5
|
import { RbirdSurveyManager } from "./RbirdSurveyManager";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
|
-
* Manages automations
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Manages automations by sending events to the backend and executing commands.
|
|
9
|
+
* The actual workflow execution happens on the backend - this manager handles:
|
|
10
|
+
* - Sending events to the backend
|
|
11
|
+
* - Polling for pending commands
|
|
12
|
+
* - Executing UI commands (show banner, form, survey, send chat)
|
|
10
13
|
*/
|
|
11
14
|
export class RbirdAutomationManager {
|
|
12
15
|
static instance = null;
|
|
13
|
-
automations = [];
|
|
14
16
|
apiKey = null;
|
|
15
17
|
firstSeenTimestamp = null;
|
|
16
|
-
|
|
18
|
+
pollingInterval = null;
|
|
19
|
+
isPolling = false;
|
|
17
20
|
|
|
18
21
|
static getInstance() {
|
|
19
22
|
if (!this.instance) {
|
|
@@ -30,14 +33,18 @@ export class RbirdAutomationManager {
|
|
|
30
33
|
this.apiKey = apiKey;
|
|
31
34
|
this.firstSeenTimestamp = this.getFirstSeenTimestamp();
|
|
32
35
|
|
|
33
|
-
// Load automations from backend
|
|
34
|
-
await this.loadAutomations();
|
|
35
|
-
|
|
36
36
|
// Register event handlers
|
|
37
37
|
this.registerEventHandlers();
|
|
38
38
|
|
|
39
|
-
//
|
|
40
|
-
this.
|
|
39
|
+
// Start command polling
|
|
40
|
+
this.startCommandPolling();
|
|
41
|
+
|
|
42
|
+
// Send first_seen event
|
|
43
|
+
await this.sendEvent('first_seen', {
|
|
44
|
+
timeSinceFirstSeen: (Date.now() - this.firstSeenTimestamp) / 1000
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
console.log('[Rbird] Automation manager initialized');
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
/**
|
|
@@ -53,297 +60,251 @@ export class RbirdAutomationManager {
|
|
|
53
60
|
return now;
|
|
54
61
|
}
|
|
55
62
|
|
|
56
|
-
/**
|
|
57
|
-
* Load active automations from the backend
|
|
58
|
-
*/
|
|
59
|
-
async loadAutomations() {
|
|
60
|
-
try {
|
|
61
|
-
const sessionManager = RbirdSessionManager.getInstance();
|
|
62
|
-
const baseUrl = sessionManager.apiUrl || 'https://api.releasebird.com';
|
|
63
|
-
|
|
64
|
-
const response = await fetch(`${baseUrl}/papi/ewidget/automations`, {
|
|
65
|
-
method: 'GET',
|
|
66
|
-
headers: {
|
|
67
|
-
'Content-Type': 'application/json',
|
|
68
|
-
'apiKey': this.apiKey,
|
|
69
|
-
'languageCode': navigator.language?.split('-')[0] || 'en',
|
|
70
|
-
},
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
if (response.ok) {
|
|
74
|
-
this.automations = await response.json();
|
|
75
|
-
console.log('[Rbird] Loaded automations:', this.automations.length);
|
|
76
|
-
}
|
|
77
|
-
} catch (error) {
|
|
78
|
-
console.error('[Rbird] Error loading automations:', error);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
63
|
/**
|
|
83
64
|
* Register event handlers for automation triggers
|
|
84
65
|
*/
|
|
85
66
|
registerEventHandlers() {
|
|
86
|
-
// Listen for page visits
|
|
87
|
-
window.addEventListener('popstate', () => this.
|
|
67
|
+
// Listen for page visits (navigation)
|
|
68
|
+
window.addEventListener('popstate', () => this.sendPageVisitEvents());
|
|
69
|
+
|
|
70
|
+
// Also track initial page load
|
|
71
|
+
this.sendPageVisitEvents();
|
|
88
72
|
|
|
89
73
|
// Listen for identify events
|
|
90
74
|
document.addEventListener('rbird:identify', (e) => {
|
|
91
|
-
this.
|
|
75
|
+
this.sendEvent('user_identified', e.detail);
|
|
76
|
+
// Also send user_page_visit when user is identified
|
|
77
|
+
this.sendEvent('user_page_visit');
|
|
92
78
|
});
|
|
93
79
|
|
|
94
80
|
// Listen for form submissions
|
|
95
81
|
document.addEventListener('rbird:form_submitted', (e) => {
|
|
96
|
-
this.
|
|
82
|
+
this.sendEvent('form_submitted', e.detail);
|
|
97
83
|
});
|
|
98
84
|
|
|
99
85
|
// Listen for survey completions
|
|
100
86
|
document.addEventListener('rbird:survey_completed', (e) => {
|
|
101
|
-
this.
|
|
87
|
+
this.sendEvent('survey_completed', e.detail);
|
|
102
88
|
});
|
|
103
89
|
|
|
104
90
|
// Listen for custom events
|
|
105
91
|
document.addEventListener('rbird:track', (e) => {
|
|
106
|
-
this.
|
|
92
|
+
this.sendEvent(e.detail?.eventName, e.detail?.data);
|
|
107
93
|
});
|
|
108
94
|
}
|
|
109
95
|
|
|
110
96
|
/**
|
|
111
|
-
*
|
|
97
|
+
* Send page visit events based on user identification status.
|
|
98
|
+
* Sends:
|
|
99
|
+
* - page_visit: always (for backward compatibility)
|
|
100
|
+
* - visitor_page_visit: if user is NOT identified (anonymous)
|
|
101
|
+
* - user_page_visit: if user IS identified
|
|
112
102
|
*/
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
103
|
+
sendPageVisitEvents() {
|
|
104
|
+
// Always send generic page_visit for backward compatibility
|
|
105
|
+
this.sendEvent('page_visit');
|
|
106
|
+
|
|
107
|
+
// Send specific event based on identification status
|
|
108
|
+
const peopleId = this.getPeopleId();
|
|
109
|
+
if (peopleId) {
|
|
110
|
+
this.sendEvent('user_page_visit');
|
|
111
|
+
} else {
|
|
112
|
+
this.sendEvent('visitor_page_visit');
|
|
113
|
+
}
|
|
122
114
|
}
|
|
123
115
|
|
|
124
116
|
/**
|
|
125
|
-
*
|
|
117
|
+
* Send an event to the backend
|
|
126
118
|
* @param {string} eventType - The type of event
|
|
127
119
|
* @param {Object} eventData - Additional event data
|
|
128
120
|
*/
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
121
|
+
async sendEvent(eventType, eventData = {}) {
|
|
122
|
+
if (!eventType) return;
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const response = await fetch(`${API}/ewidget/events`, {
|
|
126
|
+
method: 'POST',
|
|
127
|
+
headers: {
|
|
128
|
+
'Content-Type': 'application/json',
|
|
129
|
+
'apiKey': this.apiKey,
|
|
130
|
+
'peopleId': this.getPeopleId() || '',
|
|
131
|
+
'anonymousId': this.getAnonymousId() || ''
|
|
132
|
+
},
|
|
133
|
+
body: JSON.stringify({
|
|
134
|
+
eventType,
|
|
135
|
+
context: {
|
|
136
|
+
...eventData,
|
|
137
|
+
pageUrl: window.location.href,
|
|
138
|
+
pagePath: window.location.pathname
|
|
139
|
+
},
|
|
140
|
+
firstSeenTimestamp: this.firstSeenTimestamp
|
|
141
|
+
})
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (!response.ok) {
|
|
145
|
+
console.warn('[Rbird] Failed to send event:', response.status);
|
|
146
|
+
} else {
|
|
147
|
+
console.log('[Rbird] Event sent:', eventType);
|
|
134
148
|
}
|
|
135
|
-
})
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.error('[Rbird] Error sending event:', error);
|
|
151
|
+
}
|
|
136
152
|
}
|
|
137
153
|
|
|
138
154
|
/**
|
|
139
|
-
*
|
|
140
|
-
* @param {Object} automation - The automation object
|
|
141
|
-
* @returns {Object|null} The trigger node or null
|
|
155
|
+
* Get the current people ID from session
|
|
142
156
|
*/
|
|
143
|
-
|
|
144
|
-
return
|
|
157
|
+
getPeopleId() {
|
|
158
|
+
return RbirdSessionManager.getInstance().getState()?.identify?.peopleId;
|
|
145
159
|
}
|
|
146
160
|
|
|
147
161
|
/**
|
|
148
|
-
*
|
|
149
|
-
* @param {Object} automation - The automation object
|
|
150
|
-
* @param {Object} triggerNode - The trigger node
|
|
151
|
-
* @param {Object} context - The event context
|
|
162
|
+
* Get the anonymous ID from session
|
|
152
163
|
*/
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Evaluate trigger conditions
|
|
160
|
-
if (!this.evaluateTriggerConditions(triggerNode, context)) {
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Mark as executed
|
|
165
|
-
this.executedAutomations.add(automation.id);
|
|
166
|
-
|
|
167
|
-
// Execute the workflow starting from the trigger node
|
|
168
|
-
this.executeWorkflow(automation, triggerNode.id, context);
|
|
164
|
+
getAnonymousId() {
|
|
165
|
+
return RbirdSessionManager.getInstance().getState()?.identify?.anonymousId ||
|
|
166
|
+
localStorage.getItem('rbird_anonymous_id') ||
|
|
167
|
+
this.generateAnonymousId();
|
|
169
168
|
}
|
|
170
169
|
|
|
171
170
|
/**
|
|
172
|
-
*
|
|
173
|
-
* @param {Object} triggerNode - The trigger node
|
|
174
|
-
* @param {Object} context - The event context
|
|
175
|
-
* @returns {boolean} Whether conditions are met
|
|
171
|
+
* Generate and store an anonymous ID
|
|
176
172
|
*/
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const value = this.getContextValue(condition.property, context);
|
|
182
|
-
|
|
183
|
-
switch (condition.operator) {
|
|
184
|
-
case 'less_than':
|
|
185
|
-
if (!(parseFloat(value) < parseFloat(condition.value))) return false;
|
|
186
|
-
break;
|
|
187
|
-
case 'greater_than':
|
|
188
|
-
if (!(parseFloat(value) > parseFloat(condition.value))) return false;
|
|
189
|
-
break;
|
|
190
|
-
case 'equals':
|
|
191
|
-
if (value !== condition.value) return false;
|
|
192
|
-
break;
|
|
193
|
-
case 'contains':
|
|
194
|
-
if (!String(value).includes(condition.value)) return false;
|
|
195
|
-
break;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return true;
|
|
173
|
+
generateAnonymousId() {
|
|
174
|
+
const id = 'anon_' + Math.random().toString(36).substr(2, 9);
|
|
175
|
+
localStorage.setItem('rbird_anonymous_id', id);
|
|
176
|
+
return id;
|
|
200
177
|
}
|
|
201
178
|
|
|
202
179
|
/**
|
|
203
|
-
*
|
|
204
|
-
* @param {string} property - The property path
|
|
205
|
-
* @param {Object} context - The context object
|
|
206
|
-
* @returns {any} The value
|
|
180
|
+
* Start polling for pending commands
|
|
207
181
|
*/
|
|
208
|
-
|
|
209
|
-
if (
|
|
210
|
-
return
|
|
182
|
+
startCommandPolling() {
|
|
183
|
+
if (this.pollingInterval) {
|
|
184
|
+
return; // Already polling
|
|
211
185
|
}
|
|
212
186
|
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
return value;
|
|
187
|
+
// Poll immediately once
|
|
188
|
+
this.pollCommands();
|
|
189
|
+
|
|
190
|
+
// Then poll every 3 seconds
|
|
191
|
+
this.pollingInterval = setInterval(() => this.pollCommands(), 3000);
|
|
220
192
|
}
|
|
221
193
|
|
|
222
194
|
/**
|
|
223
|
-
*
|
|
224
|
-
* @param {Object} automation - The automation object
|
|
225
|
-
* @param {string} startNodeId - The ID of the starting node
|
|
226
|
-
* @param {Object} context - The execution context
|
|
195
|
+
* Stop command polling
|
|
227
196
|
*/
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
for (const edge of nextEdges) {
|
|
233
|
-
const targetNode = automation.nodes?.find((node) => node.id === edge.target);
|
|
234
|
-
if (!targetNode) continue;
|
|
235
|
-
|
|
236
|
-
if (targetNode.type === 'condition') {
|
|
237
|
-
// Evaluate condition and follow appropriate path
|
|
238
|
-
const conditionResult = this.evaluateCondition(targetNode, context);
|
|
239
|
-
const handleToFollow = conditionResult ? 'true' : 'false';
|
|
240
|
-
|
|
241
|
-
// Find edges from this condition with matching handle
|
|
242
|
-
const conditionEdges = automation.edges?.filter(
|
|
243
|
-
(e) => e.source === targetNode.id && e.sourceHandle === handleToFollow
|
|
244
|
-
) || [];
|
|
245
|
-
|
|
246
|
-
for (const condEdge of conditionEdges) {
|
|
247
|
-
this.executeWorkflow(automation, condEdge.source, context);
|
|
248
|
-
}
|
|
249
|
-
} else if (targetNode.type === 'action') {
|
|
250
|
-
// Execute the action
|
|
251
|
-
this.executeAction(targetNode, context);
|
|
252
|
-
|
|
253
|
-
// Continue to next nodes if any
|
|
254
|
-
this.executeWorkflow(automation, targetNode.id, context);
|
|
255
|
-
}
|
|
197
|
+
stopCommandPolling() {
|
|
198
|
+
if (this.pollingInterval) {
|
|
199
|
+
clearInterval(this.pollingInterval);
|
|
200
|
+
this.pollingInterval = null;
|
|
256
201
|
}
|
|
257
202
|
}
|
|
258
203
|
|
|
259
204
|
/**
|
|
260
|
-
*
|
|
261
|
-
* @param {Object} conditionNode - The condition node
|
|
262
|
-
* @param {Object} context - The execution context
|
|
263
|
-
* @returns {boolean} The result of the condition
|
|
205
|
+
* Poll for pending commands from the backend
|
|
264
206
|
*/
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
|
|
207
|
+
async pollCommands() {
|
|
208
|
+
if (this.isPolling) return; // Prevent concurrent polls
|
|
209
|
+
this.isPolling = true;
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
const response = await fetch(`${API}/ewidget/commands`, {
|
|
213
|
+
method: 'GET',
|
|
214
|
+
headers: {
|
|
215
|
+
'Content-Type': 'application/json',
|
|
216
|
+
'apiKey': this.apiKey,
|
|
217
|
+
'peopleId': this.getPeopleId() || '',
|
|
218
|
+
'anonymousId': this.getAnonymousId() || ''
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
if (!response.ok) {
|
|
223
|
+
return;
|
|
280
224
|
}
|
|
281
|
-
} else {
|
|
282
|
-
actualValue = context[property];
|
|
283
|
-
}
|
|
284
225
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
226
|
+
const commands = await response.json();
|
|
227
|
+
|
|
228
|
+
// Execute each command
|
|
229
|
+
for (const cmd of commands) {
|
|
230
|
+
await this.executeCommand(cmd);
|
|
231
|
+
await this.markDelivered(cmd.id);
|
|
232
|
+
}
|
|
233
|
+
} catch (error) {
|
|
234
|
+
// Silently fail - polling will retry
|
|
235
|
+
} finally {
|
|
236
|
+
this.isPolling = false;
|
|
296
237
|
}
|
|
297
238
|
}
|
|
298
239
|
|
|
299
240
|
/**
|
|
300
|
-
* Execute
|
|
301
|
-
* @param {Object}
|
|
302
|
-
* @param {Object} context - The execution context
|
|
241
|
+
* Execute a command from the backend
|
|
242
|
+
* @param {Object} cmd - The command to execute
|
|
303
243
|
*/
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
console.log('[Rbird] Executing action:', actionType, config);
|
|
244
|
+
async executeCommand(cmd) {
|
|
245
|
+
console.log('[Rbird] Executing command:', cmd.type);
|
|
308
246
|
|
|
309
|
-
switch (
|
|
310
|
-
case 'send_chat':
|
|
311
|
-
this.executeSendChat(config?.message, context);
|
|
312
|
-
break;
|
|
247
|
+
switch (cmd.type) {
|
|
313
248
|
case 'show_banner':
|
|
314
|
-
this.executeShowBanner(
|
|
249
|
+
this.executeShowBanner(cmd.targetId);
|
|
315
250
|
break;
|
|
316
251
|
case 'show_form':
|
|
317
|
-
this.executeShowForm(
|
|
252
|
+
this.executeShowForm(cmd.targetId);
|
|
318
253
|
break;
|
|
319
254
|
case 'show_survey':
|
|
320
|
-
this.executeShowSurvey(
|
|
255
|
+
this.executeShowSurvey(cmd.targetId);
|
|
321
256
|
break;
|
|
322
|
-
case '
|
|
323
|
-
this.
|
|
257
|
+
case 'send_chat':
|
|
258
|
+
this.executeSendChat(cmd.message, cmd.config);
|
|
324
259
|
break;
|
|
260
|
+
default:
|
|
261
|
+
console.warn('[Rbird] Unknown command type:', cmd.type);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Mark a command as delivered
|
|
267
|
+
* @param {string} commandId - The command ID
|
|
268
|
+
*/
|
|
269
|
+
async markDelivered(commandId) {
|
|
270
|
+
try {
|
|
271
|
+
await fetch(`${API}/ewidget/commands/${commandId}/delivered`, {
|
|
272
|
+
method: 'POST',
|
|
273
|
+
headers: {
|
|
274
|
+
'Content-Type': 'application/json',
|
|
275
|
+
'apiKey': this.apiKey
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
} catch (error) {
|
|
279
|
+
console.warn('[Rbird] Failed to mark command as delivered:', error);
|
|
325
280
|
}
|
|
326
281
|
}
|
|
327
282
|
|
|
328
283
|
/**
|
|
329
|
-
* Execute send_chat
|
|
284
|
+
* Execute send_chat command
|
|
330
285
|
*/
|
|
331
|
-
executeSendChat(message,
|
|
286
|
+
executeSendChat(message, config) {
|
|
332
287
|
if (!message) return;
|
|
333
288
|
|
|
334
|
-
|
|
335
|
-
const processedMessage = this.processMessage(message, context);
|
|
289
|
+
console.log('[Rbird] Chat message triggered:', message);
|
|
336
290
|
|
|
337
|
-
//
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}));
|
|
291
|
+
// Open the widget and send the message
|
|
292
|
+
const widget = RbirdWebsiteWidget.getInstance();
|
|
293
|
+
widget.openWebsiteWidget();
|
|
341
294
|
|
|
342
|
-
|
|
295
|
+
// Send message to iframe to display automation message
|
|
296
|
+
setTimeout(() => {
|
|
297
|
+
if (widget.iframe) {
|
|
298
|
+
widget.iframe.contentWindow?.postMessage({
|
|
299
|
+
type: 'automationMessage',
|
|
300
|
+
message: message
|
|
301
|
+
}, '*');
|
|
302
|
+
}
|
|
303
|
+
}, 500); // Small delay to ensure iframe is ready
|
|
343
304
|
}
|
|
344
305
|
|
|
345
306
|
/**
|
|
346
|
-
* Execute show_banner
|
|
307
|
+
* Execute show_banner command
|
|
347
308
|
*/
|
|
348
309
|
executeShowBanner(bannerId) {
|
|
349
310
|
if (!bannerId) return;
|
|
@@ -351,7 +312,7 @@ export class RbirdAutomationManager {
|
|
|
351
312
|
}
|
|
352
313
|
|
|
353
314
|
/**
|
|
354
|
-
* Execute show_form
|
|
315
|
+
* Execute show_form command
|
|
355
316
|
*/
|
|
356
317
|
executeShowForm(formId) {
|
|
357
318
|
if (!formId) return;
|
|
@@ -359,48 +320,13 @@ export class RbirdAutomationManager {
|
|
|
359
320
|
}
|
|
360
321
|
|
|
361
322
|
/**
|
|
362
|
-
* Execute show_survey
|
|
323
|
+
* Execute show_survey command
|
|
363
324
|
*/
|
|
364
325
|
executeShowSurvey(surveyId) {
|
|
365
326
|
if (!surveyId) return;
|
|
366
327
|
RbirdSurveyManager.getInstance().showSurvey(surveyId, {});
|
|
367
328
|
}
|
|
368
329
|
|
|
369
|
-
/**
|
|
370
|
-
* Execute webhook action
|
|
371
|
-
*/
|
|
372
|
-
async executeWebhook(url, method, context) {
|
|
373
|
-
if (!url) return;
|
|
374
|
-
|
|
375
|
-
try {
|
|
376
|
-
const options = {
|
|
377
|
-
method: method,
|
|
378
|
-
headers: {
|
|
379
|
-
'Content-Type': 'application/json',
|
|
380
|
-
},
|
|
381
|
-
};
|
|
382
|
-
|
|
383
|
-
if (method === 'POST') {
|
|
384
|
-
options.body = JSON.stringify(context);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
await fetch(url, options);
|
|
388
|
-
console.log('[Rbird] Webhook executed:', url);
|
|
389
|
-
} catch (error) {
|
|
390
|
-
console.error('[Rbird] Webhook error:', error);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* Process message placeholders
|
|
396
|
-
*/
|
|
397
|
-
processMessage(message, context) {
|
|
398
|
-
return message
|
|
399
|
-
.replace(/\{\{user\.name\}\}/g, RbirdSessionManager.getInstance().getUser()?.name || '')
|
|
400
|
-
.replace(/\{\{user\.email\}\}/g, RbirdSessionManager.getInstance().getUser()?.email || '')
|
|
401
|
-
.replace(/\{\{page\.url\}\}/g, window.location.href);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
330
|
/**
|
|
405
331
|
* Track a custom event
|
|
406
332
|
* @param {string} eventName - The name of the event
|
|
@@ -411,6 +337,13 @@ export class RbirdAutomationManager {
|
|
|
411
337
|
detail: { eventName, data }
|
|
412
338
|
}));
|
|
413
339
|
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Cleanup when manager is destroyed
|
|
343
|
+
*/
|
|
344
|
+
destroy() {
|
|
345
|
+
this.stopCommandPolling();
|
|
346
|
+
}
|
|
414
347
|
}
|
|
415
348
|
|
|
416
349
|
export default RbirdAutomationManager;
|
|
@@ -990,6 +990,12 @@ export default class RbirdWebsiteWidget {
|
|
|
990
990
|
}, 50);
|
|
991
991
|
return true;
|
|
992
992
|
}
|
|
993
|
+
|
|
994
|
+
// Even without saved position, update hide button position after DOM is ready
|
|
995
|
+
setTimeout(() => {
|
|
996
|
+
this.updateRelatedElementsPosition();
|
|
997
|
+
}, 50);
|
|
998
|
+
|
|
993
999
|
return false;
|
|
994
1000
|
}
|
|
995
1001
|
|
|
@@ -1001,27 +1007,20 @@ export default class RbirdWebsiteWidget {
|
|
|
1001
1007
|
|
|
1002
1008
|
const buttonRect = this.websiteWidget.getBoundingClientRect();
|
|
1003
1009
|
|
|
1004
|
-
// Check if we have a custom position (not default)
|
|
1005
|
-
const savedPosition = this.loadWidgetPosition();
|
|
1006
|
-
if (!savedPosition) return; // Use default CSS positioning
|
|
1007
|
-
|
|
1008
|
-
// Update badge position
|
|
1009
|
-
if (this.countBadge) {
|
|
1010
|
-
this.countBadge.style.left = `${buttonRect.right - 10}px`;
|
|
1011
|
-
this.countBadge.style.right = 'unset';
|
|
1012
|
-
this.countBadge.style.bottom = `${window.innerHeight - buttonRect.top - 15}px`;
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
1010
|
// Update hide button position (top right of bubble)
|
|
1016
|
-
// Match CSS: bottom = spaceBottom + 45 = buttonRect.bottom + 45 - buttonRect.height
|
|
1017
|
-
// Since buttonRect.top = window.innerHeight - buttonRect.bottom, and bubble height is ~50px
|
|
1018
|
-
// bottom = window.innerHeight - buttonRect.top - 5 should equal spaceBottom + 45
|
|
1019
1011
|
if (this.hideWidgetButton) {
|
|
1020
1012
|
this.hideWidgetButton.style.left = `${buttonRect.right - 5}px`;
|
|
1021
1013
|
this.hideWidgetButton.style.right = 'unset';
|
|
1022
1014
|
this.hideWidgetButton.style.bottom = `${window.innerHeight - buttonRect.bottom + 55}px`;
|
|
1023
1015
|
}
|
|
1024
1016
|
|
|
1017
|
+
// Always update badge position based on current button position
|
|
1018
|
+
if (this.countBadge) {
|
|
1019
|
+
this.countBadge.style.left = `${buttonRect.right - 10}px`;
|
|
1020
|
+
this.countBadge.style.right = 'unset';
|
|
1021
|
+
this.countBadge.style.bottom = `${window.innerHeight - buttonRect.top - 15}px`;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1025
1024
|
// Update message bubbles container position
|
|
1026
1025
|
if (this.messageBubblesContainer) {
|
|
1027
1026
|
this.positionMessageBubbles();
|