@journyio/messaging-sdk 1.0.0 → 1.0.1
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 +30 -0
- package/dist/index.d.ts +1 -1
- package/dist/journy-messages.esm.js +238 -98
- package/dist/journy-messages.js +238 -98
- package/dist/journy-messages.min.js +3 -3
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
|
@@ -102,6 +102,28 @@ class MessageQueue {
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
const DEFAULT_API_ENDPOINT = 'https://analyze.journy.io';
|
|
106
|
+
const DEFAULT_POLLING_INTERVAL = 30000;
|
|
107
|
+
const ROOT_ELEMENT_ID = 'journy-messages-root';
|
|
108
|
+
const DEFAULT_STYLE_ID = 'journy-messages-default';
|
|
109
|
+
const REACT_CHECK_INTERVAL = 100;
|
|
110
|
+
const REACT_WAIT_TIMEOUT = 10000;
|
|
111
|
+
const MESSAGE_CLOSE_DELAY = 300;
|
|
112
|
+
const DEFAULT_WIDGET_SETTINGS = {
|
|
113
|
+
pollingInterval: DEFAULT_POLLING_INTERVAL,
|
|
114
|
+
showReadMessages: true,
|
|
115
|
+
autoExpandOnNew: true,
|
|
116
|
+
displayMode: 'widget',
|
|
117
|
+
apiEndpoint: DEFAULT_API_ENDPOINT,
|
|
118
|
+
styles: 'default',
|
|
119
|
+
};
|
|
120
|
+
const STORAGE_KEYS = {
|
|
121
|
+
WIDGET_SETTINGS: 'widget_settings',
|
|
122
|
+
WIDGET_VISIBLE: 'widget_visible',
|
|
123
|
+
WIDGET_COLLAPSED: 'widget_collapsed',
|
|
124
|
+
CURRENT_MESSAGE_ID: 'current_message_id',
|
|
125
|
+
};
|
|
126
|
+
|
|
105
127
|
const API_PATHS = {
|
|
106
128
|
IN_APP_MESSAGES: '/sdk/in-app-messages',
|
|
107
129
|
};
|
|
@@ -120,7 +142,7 @@ class ApiClient {
|
|
|
120
142
|
return headers;
|
|
121
143
|
}
|
|
122
144
|
buildUrl(endpoint) {
|
|
123
|
-
const baseUrl = (this.config.apiEndpoint ||
|
|
145
|
+
const baseUrl = (this.config.apiEndpoint || DEFAULT_API_ENDPOINT).replace(/\/$/, '');
|
|
124
146
|
const entityId = this.config.entityType === 'user'
|
|
125
147
|
? this.config.userId
|
|
126
148
|
: this.config.accountId;
|
|
@@ -181,14 +203,6 @@ var SDKEventType;
|
|
|
181
203
|
SDKEventType["MessageClosed"] = "In-App Message Closed";
|
|
182
204
|
SDKEventType["MessageLinkClicked"] = "In-App Message Link Clicked";
|
|
183
205
|
})(SDKEventType || (SDKEventType = {}));
|
|
184
|
-
const DEFAULT_WIDGET_SETTINGS = {
|
|
185
|
-
pollingInterval: 30000,
|
|
186
|
-
showReadMessages: true,
|
|
187
|
-
autoExpandOnNew: true,
|
|
188
|
-
displayMode: 'widget',
|
|
189
|
-
apiEndpoint: 'https://jtm.journy.io',
|
|
190
|
-
styles: 'default',
|
|
191
|
-
};
|
|
192
206
|
|
|
193
207
|
const STORAGE_PREFIX = 'journy_messages_';
|
|
194
208
|
function getStorageKey(key) {
|
|
@@ -326,17 +340,33 @@ class MessagingStore {
|
|
|
326
340
|
}
|
|
327
341
|
}
|
|
328
342
|
|
|
329
|
-
function
|
|
330
|
-
|
|
343
|
+
function getDebugFlags() {
|
|
344
|
+
if (typeof window === 'undefined')
|
|
345
|
+
return {};
|
|
346
|
+
const flag = window.__JOURNY_DEBUG__;
|
|
347
|
+
if (flag === true)
|
|
348
|
+
return { mockMessages: true, settings: true };
|
|
349
|
+
if (flag && typeof flag === 'object')
|
|
350
|
+
return flag;
|
|
351
|
+
return {};
|
|
352
|
+
}
|
|
353
|
+
function isDebugMockMessages() {
|
|
354
|
+
return getDebugFlags().mockMessages === true;
|
|
355
|
+
}
|
|
356
|
+
function isDebugSettings() {
|
|
357
|
+
return getDebugFlags().settings === true;
|
|
358
|
+
}
|
|
359
|
+
function createRootElement(id, doc = document) {
|
|
360
|
+
let element = doc.getElementById(id);
|
|
331
361
|
if (!element) {
|
|
332
|
-
element =
|
|
362
|
+
element = doc.createElement('div');
|
|
333
363
|
element.id = id;
|
|
334
|
-
|
|
364
|
+
doc.body.appendChild(element);
|
|
335
365
|
}
|
|
336
366
|
return element;
|
|
337
367
|
}
|
|
338
|
-
function removeRootElement(id) {
|
|
339
|
-
const element =
|
|
368
|
+
function removeRootElement(id, doc = document) {
|
|
369
|
+
const element = doc.getElementById(id);
|
|
340
370
|
if (element) {
|
|
341
371
|
element.remove();
|
|
342
372
|
}
|
|
@@ -344,47 +374,78 @@ function removeRootElement(id) {
|
|
|
344
374
|
const STYLES_LINK_ID = 'journy-messages-styles-link';
|
|
345
375
|
const STYLES_TAG_ID = 'journy-messages-custom';
|
|
346
376
|
const DEFAULT_STYLES_TAG_ID = 'journy-messages-default';
|
|
347
|
-
function injectStyleLink(href) {
|
|
348
|
-
if (
|
|
377
|
+
function injectStyleLink(href, doc = document) {
|
|
378
|
+
if (!doc || !doc.head)
|
|
349
379
|
return;
|
|
350
|
-
const existing =
|
|
380
|
+
const existing = doc.getElementById(STYLES_LINK_ID);
|
|
351
381
|
if (existing && existing instanceof HTMLLinkElement && existing.href === href)
|
|
352
382
|
return;
|
|
353
383
|
if (existing)
|
|
354
384
|
existing.remove();
|
|
355
|
-
const link =
|
|
385
|
+
const link = doc.createElement('link');
|
|
356
386
|
link.id = STYLES_LINK_ID;
|
|
357
387
|
link.rel = 'stylesheet';
|
|
358
388
|
link.href = href;
|
|
359
|
-
|
|
389
|
+
doc.head.appendChild(link);
|
|
360
390
|
}
|
|
361
|
-
function injectStyleTag(css, id = STYLES_TAG_ID) {
|
|
362
|
-
if (
|
|
391
|
+
function injectStyleTag(css, id = STYLES_TAG_ID, doc = document) {
|
|
392
|
+
if (!doc || !doc.head)
|
|
363
393
|
return;
|
|
364
|
-
let style =
|
|
394
|
+
let style = doc.getElementById(id);
|
|
365
395
|
if (style) {
|
|
366
396
|
style.textContent = css;
|
|
367
397
|
return;
|
|
368
398
|
}
|
|
369
|
-
style =
|
|
399
|
+
style = doc.createElement('style');
|
|
370
400
|
style.id = id;
|
|
371
401
|
style.textContent = css;
|
|
372
|
-
|
|
402
|
+
doc.head.appendChild(style);
|
|
373
403
|
}
|
|
374
|
-
function removeInjectedStyles() {
|
|
375
|
-
if (
|
|
404
|
+
function removeInjectedStyles(doc = document) {
|
|
405
|
+
if (!doc)
|
|
376
406
|
return;
|
|
377
|
-
const link =
|
|
407
|
+
const link = doc.getElementById(STYLES_LINK_ID);
|
|
378
408
|
if (link)
|
|
379
409
|
link.remove();
|
|
380
|
-
const style =
|
|
410
|
+
const style = doc.getElementById(STYLES_TAG_ID);
|
|
381
411
|
if (style)
|
|
382
412
|
style.remove();
|
|
383
|
-
const defaultStyle =
|
|
413
|
+
const defaultStyle = doc.getElementById(DEFAULT_STYLES_TAG_ID);
|
|
384
414
|
if (defaultStyle)
|
|
385
415
|
defaultStyle.remove();
|
|
386
416
|
}
|
|
387
417
|
|
|
418
|
+
function canAccessWindow(win) {
|
|
419
|
+
try {
|
|
420
|
+
// Accessing .document on a cross-origin window throws DOMException
|
|
421
|
+
return !!win.document;
|
|
422
|
+
}
|
|
423
|
+
catch {
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
function resolveRenderContext(target) {
|
|
428
|
+
const sourceWindow = window;
|
|
429
|
+
if (!target || target === 'self') {
|
|
430
|
+
return { targetWindow: window, targetDocument: document, isRemote: false, sourceWindow };
|
|
431
|
+
}
|
|
432
|
+
const candidateWindow = target === 'top' ? window.top : window.parent;
|
|
433
|
+
if (!candidateWindow || candidateWindow === window) {
|
|
434
|
+
// Not inside an iframe — 'parent'/'top' is the same as 'self'
|
|
435
|
+
return { targetWindow: window, targetDocument: document, isRemote: false, sourceWindow };
|
|
436
|
+
}
|
|
437
|
+
if (canAccessWindow(candidateWindow)) {
|
|
438
|
+
return {
|
|
439
|
+
targetWindow: candidateWindow,
|
|
440
|
+
targetDocument: candidateWindow.document,
|
|
441
|
+
isRemote: true,
|
|
442
|
+
sourceWindow,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
console.warn(`[JournyMessaging] Cannot access ${target} window (cross-origin). Falling back to rendering inside the current iframe.`);
|
|
446
|
+
return { targetWindow: window, targetDocument: document, isRemote: false, sourceWindow };
|
|
447
|
+
}
|
|
448
|
+
|
|
388
449
|
var defaultStyles = "/* Widget Container */\n.journy-message-widget {\n position: fixed;\n z-index: 10000;\n background: white;\n border-radius: 12px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);\n overflow: hidden;\n user-select: none;\n transform-origin: bottom right;\n}\n\n.journy-message-widget.journy-message-widget-dragging {\n cursor: grabbing;\n transition: none;\n z-index: 10001;\n}\n\n.journy-message-widget.journy-message-widget-expanded {\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n\n.journy-message-widget.journy-message-widget-resizing {\n transition: none;\n}\n\n/* Expand/collapse: height and top are animated via inline transition so bottom-right stays fixed */\n\n/* Widget Header */\n.journy-message-widget-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px;\n background: #f9fafb;\n border-bottom: 1px solid #e5e7eb;\n user-select: none;\n}\n\n.journy-message-widget-drag-handle {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 4px 8px;\n margin-right: 8px;\n cursor: grab;\n color: #9ca3af;\n transition: color 0.2s;\n flex-shrink: 0;\n}\n\n.journy-message-widget-drag-handle:active {\n cursor: grabbing;\n color: #6b7280;\n}\n\n.journy-message-widget-drag-handle:hover {\n color: #6b7280;\n}\n\n.journy-message-widget-drag-handle svg {\n display: block;\n}\n\n.journy-message-widget-header-content {\n display: flex;\n align-items: center;\n gap: 10px;\n flex: 1;\n flex-wrap: wrap;\n cursor: pointer;\n}\n\n.journy-message-widget-badge {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 24px;\n height: 24px;\n padding: 0 8px;\n background: #3b82f6;\n color: white;\n border-radius: 12px;\n font-size: 12px;\n font-weight: 600;\n}\n\n.journy-message-widget-title {\n font-size: 14px;\n font-weight: 600;\n color: #111827;\n}\n\n.journy-message-widget-read-count {\n font-size: 12px;\n color: #6b7280;\n font-weight: 400;\n}\n\n.journy-message-widget-controls {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.journy-message-widget-toggle,\n.journy-message-widget-close {\n background: none;\n border: none;\n font-size: 18px;\n line-height: 1;\n cursor: pointer;\n color: #6b7280;\n padding: 4px 8px;\n border-radius: 4px;\n transition: background-color 0.2s, color 0.2s;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n}\n\n.journy-message-widget-toggle:hover,\n.journy-message-widget-close:hover {\n background-color: #e5e7eb;\n color: #374151;\n}\n\n/* Widget Content */\n.journy-message-widget-content {\n padding: 16px;\n flex: 1;\n overflow-y: auto;\n overflow-x: hidden;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n.journy-message-widget-content--single .journy-message-widget-message {\n flex: 1;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n.journy-message-widget-content--single .journy-message-widget-message .journy-message-content {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n}\n\n/* Resize Handle */\n.journy-message-widget-resize-handle {\n position: absolute;\n bottom: 0;\n right: 0;\n width: 20px;\n height: 20px;\n cursor: nwse-resize;\n display: flex;\n align-items: center;\n justify-content: center;\n background: linear-gradient(135deg, transparent 0%, transparent 40%, #e5e7eb 40%, #e5e7eb 100%);\n z-index: 10;\n transition: background 0.2s;\n}\n\n.journy-message-widget-resize-handle:hover {\n background: linear-gradient(135deg, transparent 0%, transparent 40%, #d1d5db 40%, #d1d5db 100%);\n}\n\n.journy-message-widget-resize-handle svg {\n width: 12px;\n height: 12px;\n opacity: 0.6;\n}\n\n.journy-message-widget-resize-handle:hover svg {\n opacity: 1;\n}\n\n.journy-message-widget-message {\n padding: 0;\n position: relative;\n min-height: 60px;\n}\n\n.journy-message-widget-message-close {\n position: absolute;\n top: 4px;\n right: 4px;\n width: 24px;\n height: 24px;\n padding: 0;\n border: none;\n background: transparent;\n color: #6b7280;\n font-size: 18px;\n line-height: 1;\n cursor: pointer;\n border-radius: 4px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background-color 0.2s, color 0.2s;\n z-index: 1;\n}\n\n.journy-message-widget-message-close:hover {\n background-color: #e5e7eb;\n color: #374151;\n}\n\n.journy-message-widget-message:has(.journy-message-widget-message-close) .journy-message-content {\n padding-right: 28px;\n}\n\n.journy-message-widget-nav {\n background: none;\n border: none;\n font-size: 18px;\n line-height: 1;\n cursor: pointer;\n color: #6b7280;\n padding: 4px 6px;\n border-radius: 4px;\n transition: background-color 0.2s, color 0.2s;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n}\n\n.journy-message-widget-nav:hover:not(:disabled) {\n background-color: #e5e7eb;\n color: #374151;\n}\n\n.journy-message-widget-nav:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n.journy-message-widget-message-separated {\n margin-bottom: 24px;\n padding-bottom: 24px;\n border-bottom: 1px solid #e5e7eb;\n}\n\n.journy-message-widget-message-count {\n font-size: 12px;\n color: #6b7280;\n font-weight: 400;\n margin-left: 8px;\n}\n\n.journy-message-widget-position {\n padding: 4px 12px;\n font-size: 11px;\n color: #9ca3af;\n font-weight: 500;\n text-align: right;\n flex-shrink: 0;\n}\n\n.journy-message-widget-message.journy-message-info {\n border-left: 4px solid #3b82f6;\n padding-left: 12px;\n}\n\n.journy-message-widget-message.journy-message-success {\n border-left: 4px solid #10b981;\n padding-left: 12px;\n}\n\n.journy-message-widget-message.journy-message-warning {\n border-left: 4px solid #f59e0b;\n padding-left: 12px;\n}\n\n.journy-message-widget-message.journy-message-error {\n border-left: 4px solid #ef4444;\n padding-left: 12px;\n}\n\n.journy-message-widget-message.journy-message-viewed {\n border-left: 4px solid #6b7280;\n padding-left: 12px;\n}\n\n/* Message modal (80vw x 60vh) */\n.journy-message-modal-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background-color: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10002;\n padding: 20px;\n}\n\n.journy-message-modal {\n width: 80vw;\n max-width: 80vw;\n height: 60vh;\n max-height: 60vh;\n background: white;\n border-radius: 12px;\n box-shadow: 0 4px 24px rgba(0, 0, 0, 0.2);\n overflow: hidden;\n display: flex;\n flex-direction: column;\n position: relative;\n}\n\n.journy-message-modal-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 24px;\n background: #f9fafb;\n border-bottom: 1px solid #e5e7eb;\n flex-shrink: 0;\n}\n\n.journy-message-modal-title {\n font-size: 18px;\n font-weight: 600;\n color: #111827;\n}\n\n.journy-message-modal-close {\n background: none;\n border: none;\n font-size: 24px;\n line-height: 1;\n cursor: pointer;\n color: #6b7280;\n padding: 4px 8px;\n border-radius: 4px;\n transition: background-color 0.2s, color 0.2s;\n}\n\n.journy-message-modal-close:hover {\n background-color: #e5e7eb;\n color: #374151;\n}\n\n.journy-message-modal .journy-message-widget-message {\n padding: 24px 24px 24px 24px;\n overflow-y: auto;\n flex: 1;\n min-height: 0;\n}\n\n/* Timestamp */\n.journy-message-timestamp {\n font-size: 12px;\n color: #6b7280;\n margin-top: 4px;\n line-height: 1.4;\n}\n\n.journy-message-content-clickable {\n cursor: pointer;\n}\n\n.journy-message-content-clickable:hover {\n opacity: 0.95;\n}\n\n/* Legacy Modal Overlay Styles (kept for backward compatibility) */\n.journy-message-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background-color: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10000;\n opacity: 0;\n transition: opacity 0.3s ease-in-out;\n pointer-events: none;\n}\n\n.journy-message-overlay.journy-message-visible {\n opacity: 1;\n pointer-events: all;\n}\n\n.journy-message-popup {\n background: white;\n border-radius: 8px;\n padding: 24px;\n max-width: 500px;\n width: 90%;\n max-height: 80vh;\n overflow-y: auto;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);\n position: relative;\n transform: scale(0.9);\n transition: transform 0.3s ease-in-out;\n}\n\n.journy-message-overlay.journy-message-visible .journy-message-popup {\n transform: scale(1);\n}\n\n.journy-message-popup.journy-message-info {\n border-top: 4px solid #3b82f6;\n}\n\n.journy-message-popup.journy-message-success {\n border-top: 4px solid #10b981;\n}\n\n.journy-message-popup.journy-message-warning {\n border-top: 4px solid #f59e0b;\n}\n\n.journy-message-popup.journy-message-error {\n border-top: 4px solid #ef4444;\n}\n\n.journy-message-popup.journy-message-viewed {\n border-top: 4px solid #6b7280;\n}\n\n.journy-message-close {\n position: absolute;\n top: 12px;\n right: 12px;\n background: none;\n border: none;\n font-size: 24px;\n line-height: 1;\n cursor: pointer;\n color: #6b7280;\n padding: 4px 8px;\n border-radius: 4px;\n transition: background-color 0.2s, color 0.2s;\n}\n\n.journy-message-close:hover {\n background-color: #f3f4f6;\n color: #374151;\n}\n\n.journy-message-title {\n font-size: 20px;\n font-weight: 600;\n margin-bottom: 12px;\n color: #111827;\n}\n\n.journy-message-content {\n font-size: 16px;\n line-height: 1.6;\n color: #374151;\n margin-bottom: 16px;\n}\n\n.journy-message-content a {\n color: #3b82f6;\n text-decoration: underline;\n transition: color 0.2s;\n}\n\n.journy-message-content a:hover {\n color: #2563eb;\n}\n\n.journy-message-content p {\n margin: 0 0 12px 0;\n}\n\n.journy-message-content p:last-child {\n margin-bottom: 0;\n}\n\n.journy-message-content ul,\n.journy-message-content ol {\n margin: 12px 0;\n padding-left: 24px;\n}\n\n.journy-message-content li {\n margin-bottom: 8px;\n}\n\n/* Headings */\n.journy-message-content h1,\n.journy-message-content h2,\n.journy-message-content h3,\n.journy-message-content h4,\n.journy-message-content h5,\n.journy-message-content h6 {\n margin: 16px 0 12px 0;\n font-weight: 600;\n line-height: 1.3;\n color: #111827;\n}\n\n.journy-message-content h1 {\n font-size: 24px;\n}\n\n.journy-message-content h2 {\n font-size: 20px;\n}\n\n.journy-message-content h3 {\n font-size: 18px;\n}\n\n.journy-message-content h4 {\n font-size: 16px;\n}\n\n.journy-message-content h5,\n.journy-message-content h6 {\n font-size: 14px;\n}\n\n/* Code blocks */\n.journy-message-content code {\n background-color: #f3f4f6;\n color: #ef4444;\n padding: 2px 6px;\n border-radius: 4px;\n font-size: 0.9em;\n font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;\n}\n\n.journy-message-content pre {\n background-color: #1f2937;\n color: #f9fafb;\n padding: 16px;\n border-radius: 8px;\n overflow-x: auto;\n margin: 12px 0;\n font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;\n font-size: 14px;\n line-height: 1.5;\n}\n\n.journy-message-content pre code {\n background-color: transparent;\n color: inherit;\n padding: 0;\n border-radius: 0;\n font-size: inherit;\n}\n\n/* Text formatting */\n.journy-message-content u {\n text-decoration: underline;\n}\n\n.journy-message-content s,\n.journy-message-content strike,\n.journy-message-content del {\n text-decoration: line-through;\n}\n\n.journy-message-content strong {\n font-weight: 600;\n}\n\n.journy-message-content em {\n font-style: italic;\n}\n\n/* Blockquotes */\n.journy-message-content blockquote {\n border-left: 4px solid #e5e7eb;\n padding-left: 16px;\n margin: 12px 0;\n color: #6b7280;\n font-style: italic;\n}\n\n/* Tables */\n.journy-message-content table {\n width: 100%;\n border-collapse: collapse;\n margin: 12px 0;\n}\n\n.journy-message-content th,\n.journy-message-content td {\n border: 1px solid #e5e7eb;\n padding: 8px 12px;\n text-align: left;\n}\n\n.journy-message-content th {\n background-color: #f9fafb;\n font-weight: 600;\n}\n\n/* Horizontal rule */\n.journy-message-content hr {\n border: none;\n border-top: 1px solid #e5e7eb;\n margin: 16px 0;\n}\n\n.journy-message-actions {\n display: flex;\n gap: 12px;\n margin-top: 20px;\n flex-wrap: wrap;\n}\n\n.journy-message-action {\n padding: 10px 20px;\n border-radius: 6px;\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n border: none;\n transition: all 0.2s;\n}\n\n.journy-message-action-primary {\n background-color: #3b82f6;\n color: white;\n}\n\n.journy-message-action-primary:hover {\n background-color: #2563eb;\n}\n\n.journy-message-action-secondary {\n background-color: #e5e7eb;\n color: #374151;\n}\n\n.journy-message-action-secondary:hover {\n background-color: #d1d5db;\n}\n\n.journy-message-action-link {\n background: none;\n color: #3b82f6;\n text-decoration: underline;\n padding: 10px 0;\n}\n\n.journy-message-action-link:hover {\n color: #2563eb;\n}\n\n/* Settings Panel */\n.journy-settings-panel {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n width: 100%;\n background: white;\n z-index: 10;\n display: flex;\n flex-direction: column;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n}\n\n.journy-settings-panel-open {\n transform: translateX(0);\n}\n\n.journy-settings-panel-closed {\n transform: translateX(100%);\n}\n\n.journy-settings-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px;\n background: #f9fafb;\n border-bottom: 1px solid #e5e7eb;\n flex-shrink: 0;\n}\n\n.journy-settings-title {\n font-size: 14px;\n font-weight: 600;\n color: #111827;\n}\n\n.journy-settings-close {\n background: none;\n border: none;\n font-size: 18px;\n line-height: 1;\n cursor: pointer;\n color: #6b7280;\n padding: 4px 8px;\n border-radius: 4px;\n transition: background-color 0.2s, color 0.2s;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n}\n\n.journy-settings-close:hover {\n background-color: #e5e7eb;\n color: #374151;\n}\n\n.journy-settings-body {\n padding: 16px;\n overflow-y: auto;\n flex: 1;\n}\n\n.journy-settings-item {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 0;\n border-bottom: 1px solid #f3f4f6;\n}\n\n.journy-settings-item:last-child {\n border-bottom: none;\n}\n\n.journy-settings-label {\n font-size: 13px;\n font-weight: 500;\n color: #374151;\n}\n\n.journy-settings-select {\n padding: 6px 10px;\n border: 1px solid #d1d5db;\n border-radius: 6px;\n font-size: 13px;\n color: #374151;\n background: white;\n cursor: pointer;\n outline: none;\n transition: border-color 0.2s;\n}\n\n.journy-settings-select:focus {\n border-color: #3b82f6;\n}\n\n.journy-settings-toggle {\n position: relative;\n width: 40px;\n height: 22px;\n border-radius: 11px;\n border: none;\n background: #d1d5db;\n cursor: pointer;\n padding: 0;\n transition: background-color 0.2s;\n flex-shrink: 0;\n}\n\n.journy-settings-toggle-on {\n background: #3b82f6;\n}\n\n.journy-settings-toggle-knob {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: white;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);\n transition: transform 0.2s;\n}\n\n.journy-settings-toggle-on .journy-settings-toggle-knob {\n transform: translateX(18px);\n}\n\n.journy-settings-item-vertical {\n flex-direction: column;\n align-items: flex-start;\n gap: 6px;\n}\n\n.journy-settings-input {\n width: 100%;\n padding: 6px 10px;\n border: 1px solid #d1d5db;\n border-radius: 6px;\n font-size: 13px;\n color: #374151;\n background: white;\n outline: none;\n transition: border-color 0.2s;\n box-sizing: border-box;\n}\n\n.journy-settings-input:focus {\n border-color: #3b82f6;\n}\n\n.journy-settings-input::placeholder {\n color: #9ca3af;\n}\n\n.journy-settings-value {\n font-size: 13px;\n color: #6b7280;\n font-weight: 400;\n max-width: 60%;\n text-align: right;\n word-break: break-all;\n}\n\n.journy-message-widget-empty {\n display: flex;\n align-items: center;\n justify-content: center;\n color: #9ca3af;\n font-size: 14px;\n flex: 1;\n min-height: 80px;\n}\n\n.journy-settings-advanced-btn {\n background: none;\n border: none;\n font-size: 13px;\n font-weight: 500;\n color: #6b7280;\n cursor: pointer;\n padding: 0;\n transition: color 0.2s;\n}\n\n.journy-settings-advanced-btn:hover {\n color: #374151;\n}\n\n.journy-message-widget-settings-btn {\n background: none;\n border: none;\n font-size: 16px;\n line-height: 1;\n cursor: pointer;\n color: #6b7280;\n padding: 4px 8px;\n border-radius: 4px;\n transition: background-color 0.2s, color 0.2s;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n}\n\n.journy-message-widget-settings-btn:hover {\n background-color: #e5e7eb;\n color: #374151;\n}\n\n/* Responsive design */\n@media (max-width: 640px) {\n .journy-message-widget {\n min-width: calc(100vw - 32px);\n max-width: calc(100vw - 32px);\n left: 16px !important;\n right: 16px !important;\n }\n\n .journy-message-popup {\n width: 95%;\n padding: 20px;\n }\n\n .journy-message-title {\n font-size: 18px;\n }\n\n .journy-message-content {\n font-size: 14px;\n }\n\n .journy-message-actions {\n flex-direction: column;\n }\n\n .journy-message-action {\n width: 100%;\n }\n}\n";
|
|
389
450
|
|
|
390
451
|
function useMessagingStore(store) {
|
|
@@ -397,7 +458,7 @@ function useMessagingStore(store) {
|
|
|
397
458
|
return state;
|
|
398
459
|
}
|
|
399
460
|
|
|
400
|
-
/*! @license DOMPurify 3.3.
|
|
461
|
+
/*! @license DOMPurify 3.3.3 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.3.3/LICENSE */
|
|
401
462
|
|
|
402
463
|
const {
|
|
403
464
|
entries,
|
|
@@ -695,7 +756,7 @@ const _createHooksMap = function _createHooksMap() {
|
|
|
695
756
|
function createDOMPurify() {
|
|
696
757
|
let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
|
|
697
758
|
const DOMPurify = root => createDOMPurify(root);
|
|
698
|
-
DOMPurify.version = '3.3.
|
|
759
|
+
DOMPurify.version = '3.3.3';
|
|
699
760
|
DOMPurify.removed = [];
|
|
700
761
|
if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
|
|
701
762
|
// Not running in a browser, provide a factory function
|
|
@@ -990,7 +1051,7 @@ function createDOMPurify() {
|
|
|
990
1051
|
/* Parse profile info */
|
|
991
1052
|
if (USE_PROFILES) {
|
|
992
1053
|
ALLOWED_TAGS = addToSet({}, text);
|
|
993
|
-
ALLOWED_ATTR =
|
|
1054
|
+
ALLOWED_ATTR = create(null);
|
|
994
1055
|
if (USE_PROFILES.html === true) {
|
|
995
1056
|
addToSet(ALLOWED_TAGS, html$1);
|
|
996
1057
|
addToSet(ALLOWED_ATTR, html);
|
|
@@ -1011,6 +1072,13 @@ function createDOMPurify() {
|
|
|
1011
1072
|
addToSet(ALLOWED_ATTR, xml);
|
|
1012
1073
|
}
|
|
1013
1074
|
}
|
|
1075
|
+
/* Prevent function-based ADD_ATTR / ADD_TAGS from leaking across calls */
|
|
1076
|
+
if (!objectHasOwnProperty(cfg, 'ADD_TAGS')) {
|
|
1077
|
+
EXTRA_ELEMENT_HANDLING.tagCheck = null;
|
|
1078
|
+
}
|
|
1079
|
+
if (!objectHasOwnProperty(cfg, 'ADD_ATTR')) {
|
|
1080
|
+
EXTRA_ELEMENT_HANDLING.attributeCheck = null;
|
|
1081
|
+
}
|
|
1014
1082
|
/* Merge configuration parameters */
|
|
1015
1083
|
if (cfg.ADD_TAGS) {
|
|
1016
1084
|
if (typeof cfg.ADD_TAGS === 'function') {
|
|
@@ -1408,6 +1476,10 @@ function createDOMPurify() {
|
|
|
1408
1476
|
*/
|
|
1409
1477
|
// eslint-disable-next-line complexity
|
|
1410
1478
|
const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
|
|
1479
|
+
/* FORBID_ATTR must always win, even if ADD_ATTR predicate would allow it */
|
|
1480
|
+
if (FORBID_ATTR[lcName]) {
|
|
1481
|
+
return false;
|
|
1482
|
+
}
|
|
1411
1483
|
/* Make sure attribute cannot clobber */
|
|
1412
1484
|
if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
|
|
1413
1485
|
return false;
|
|
@@ -1500,7 +1572,7 @@ function createDOMPurify() {
|
|
|
1500
1572
|
value = SANITIZE_NAMED_PROPS_PREFIX + value;
|
|
1501
1573
|
}
|
|
1502
1574
|
/* Work around a security issue with comments inside attributes */
|
|
1503
|
-
if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|title|textarea)/i, value)) {
|
|
1575
|
+
if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, value)) {
|
|
1504
1576
|
_removeAttribute(name, currentNode);
|
|
1505
1577
|
continue;
|
|
1506
1578
|
}
|
|
@@ -1915,7 +1987,7 @@ const SettingsPanel = ({ isOpen, onClose, settings, onSettingsChange, configInfo
|
|
|
1915
1987
|
showAdvanced && (React.createElement(React.Fragment, null,
|
|
1916
1988
|
React.createElement("div", { className: "journy-settings-item journy-settings-item-vertical" },
|
|
1917
1989
|
React.createElement("label", { className: "journy-settings-label" }, "API endpoint"),
|
|
1918
|
-
React.createElement("input", { type: "text", className: "journy-settings-input", value: settings.apiEndpoint, onChange: (e) => update({ apiEndpoint: e.target.value }), placeholder:
|
|
1990
|
+
React.createElement("input", { type: "text", className: "journy-settings-input", value: settings.apiEndpoint, onChange: (e) => update({ apiEndpoint: e.target.value }), placeholder: DEFAULT_API_ENDPOINT })),
|
|
1919
1991
|
configInfo && (React.createElement(React.Fragment, null,
|
|
1920
1992
|
React.createElement("div", { className: "journy-settings-item" },
|
|
1921
1993
|
React.createElement("label", { className: "journy-settings-label" }, "Entity type"),
|
|
@@ -1954,6 +2026,17 @@ const ResizeHandle = ({ onMouseDown, title = 'Resize', className = 'journy-messa
|
|
|
1954
2026
|
React.createElement("svg", { width: ICON_SIZE, height: ICON_SIZE, viewBox: `0 0 ${ICON_SIZE} ${ICON_SIZE}`, fill: "none", xmlns: "http://www.w3.org/2000/svg" },
|
|
1955
2027
|
React.createElement("path", { d: `${PATH_MAIN} ${PATH_INNER}`, stroke: "#9ca3af", strokeWidth: STROKE_WIDTH, strokeLinecap: "round" }))));
|
|
1956
2028
|
|
|
2029
|
+
const defaultContext = {
|
|
2030
|
+
targetWindow: typeof window !== 'undefined' ? window : null,
|
|
2031
|
+
targetDocument: typeof document !== 'undefined' ? document : null,
|
|
2032
|
+
isRemote: false,
|
|
2033
|
+
sourceWindow: typeof window !== 'undefined' ? window : null,
|
|
2034
|
+
};
|
|
2035
|
+
const RenderCtx = React.createContext(defaultContext);
|
|
2036
|
+
function useRenderContext() {
|
|
2037
|
+
return React.useContext(RenderCtx);
|
|
2038
|
+
}
|
|
2039
|
+
|
|
1957
2040
|
const COLLAPSED_WIDTH = 300;
|
|
1958
2041
|
const COLLAPSED_HEIGHT = 80;
|
|
1959
2042
|
const WIDGET_MODE_EXPANDED_WIDTH = 340;
|
|
@@ -1966,12 +2049,13 @@ const MIN_LIST_WIDTH = 300;
|
|
|
1966
2049
|
const MIN_LIST_HEIGHT = 200;
|
|
1967
2050
|
const MIN_POSITION_THRESHOLD = 0;
|
|
1968
2051
|
function useWidgetDragResize({ isCollapsed, isListMode }) {
|
|
2052
|
+
const { targetWindow, targetDocument } = useRenderContext();
|
|
1969
2053
|
const [position, setPosition] = useState(() => {
|
|
1970
2054
|
const saved = getItem('widget_position');
|
|
1971
2055
|
if (saved && typeof saved.left === 'number' && typeof saved.top === 'number') {
|
|
1972
2056
|
return {
|
|
1973
|
-
left: Math.max(0, Math.min(saved.left,
|
|
1974
|
-
top: Math.max(0, Math.min(saved.top,
|
|
2057
|
+
left: Math.max(0, Math.min(saved.left, targetWindow.innerWidth - COLLAPSED_WIDTH)),
|
|
2058
|
+
top: Math.max(0, Math.min(saved.top, targetWindow.innerHeight - COLLAPSED_HEIGHT)),
|
|
1975
2059
|
};
|
|
1976
2060
|
}
|
|
1977
2061
|
if (saved && typeof saved.x === 'number' && typeof saved.y === 'number') {
|
|
@@ -1981,8 +2065,8 @@ function useWidgetDragResize({ isCollapsed, isListMode }) {
|
|
|
1981
2065
|
};
|
|
1982
2066
|
}
|
|
1983
2067
|
return {
|
|
1984
|
-
left:
|
|
1985
|
-
top:
|
|
2068
|
+
left: targetWindow.innerWidth - DEFAULT_EDGE_OFFSET - COLLAPSED_WIDTH,
|
|
2069
|
+
top: targetWindow.innerHeight - DEFAULT_EDGE_OFFSET - COLLAPSED_HEIGHT,
|
|
1986
2070
|
};
|
|
1987
2071
|
});
|
|
1988
2072
|
const [size, setSize] = useState({ width: LIST_MODE_DEFAULT_WIDTH, height: LIST_MODE_DEFAULT_HEIGHT });
|
|
@@ -2008,18 +2092,18 @@ function useWidgetDragResize({ isCollapsed, isListMode }) {
|
|
|
2008
2092
|
const savedSize = getItem('widget_size');
|
|
2009
2093
|
if (savedSize) {
|
|
2010
2094
|
setSize({
|
|
2011
|
-
width: Math.max(MIN_LIST_WIDTH, Math.min(savedSize.width,
|
|
2012
|
-
height: Math.max(MIN_LIST_HEIGHT, Math.min(savedSize.height,
|
|
2095
|
+
width: Math.max(MIN_LIST_WIDTH, Math.min(savedSize.width, targetWindow.innerWidth - VIEWPORT_PADDING)),
|
|
2096
|
+
height: Math.max(MIN_LIST_HEIGHT, Math.min(savedSize.height, targetWindow.innerHeight - VIEWPORT_PADDING)),
|
|
2013
2097
|
});
|
|
2014
2098
|
}
|
|
2015
2099
|
}, []);
|
|
2016
2100
|
// Clamp position so widget stays on screen
|
|
2017
2101
|
useEffect(() => {
|
|
2018
2102
|
setPosition(prev => ({
|
|
2019
|
-
left: Math.max(0, Math.min(prev.left,
|
|
2020
|
-
top: Math.max(0, Math.min(prev.top,
|
|
2103
|
+
left: Math.max(0, Math.min(prev.left, targetWindow.innerWidth - currentWidth)),
|
|
2104
|
+
top: Math.max(0, Math.min(prev.top, targetWindow.innerHeight - currentHeight)),
|
|
2021
2105
|
}));
|
|
2022
|
-
}, [currentWidth, currentHeight]);
|
|
2106
|
+
}, [currentWidth, currentHeight, targetWindow]);
|
|
2023
2107
|
// Save position when it changes
|
|
2024
2108
|
useEffect(() => {
|
|
2025
2109
|
if (position.left > MIN_POSITION_THRESHOLD || position.top > MIN_POSITION_THRESHOLD) {
|
|
@@ -2036,13 +2120,13 @@ function useWidgetDragResize({ isCollapsed, isListMode }) {
|
|
|
2036
2120
|
useEffect(() => {
|
|
2037
2121
|
const handleResize = () => {
|
|
2038
2122
|
setPosition(prev => ({
|
|
2039
|
-
left: Math.max(0, Math.min(prev.left,
|
|
2040
|
-
top: Math.max(0, Math.min(prev.top,
|
|
2123
|
+
left: Math.max(0, Math.min(prev.left, targetWindow.innerWidth - currentWidth)),
|
|
2124
|
+
top: Math.max(0, Math.min(prev.top, targetWindow.innerHeight - currentHeight)),
|
|
2041
2125
|
}));
|
|
2042
2126
|
};
|
|
2043
|
-
|
|
2044
|
-
return () =>
|
|
2045
|
-
}, [currentWidth, currentHeight]);
|
|
2127
|
+
targetWindow.addEventListener('resize', handleResize);
|
|
2128
|
+
return () => targetWindow.removeEventListener('resize', handleResize);
|
|
2129
|
+
}, [currentWidth, currentHeight, targetWindow]);
|
|
2046
2130
|
const handleMouseDown = (e) => {
|
|
2047
2131
|
const target = e.target;
|
|
2048
2132
|
if (!target.closest('.journy-message-widget-drag-handle'))
|
|
@@ -2067,15 +2151,15 @@ function useWidgetDragResize({ isCollapsed, isListMode }) {
|
|
|
2067
2151
|
const newLeft = e.clientX - dragOffset.x;
|
|
2068
2152
|
const newTop = e.clientY - dragOffset.y;
|
|
2069
2153
|
setPosition({
|
|
2070
|
-
left: Math.max(0, Math.min(newLeft,
|
|
2071
|
-
top: Math.max(0, Math.min(newTop,
|
|
2154
|
+
left: Math.max(0, Math.min(newLeft, targetWindow.innerWidth - currentWidth)),
|
|
2155
|
+
top: Math.max(0, Math.min(newTop, targetWindow.innerHeight - currentHeight)),
|
|
2072
2156
|
});
|
|
2073
2157
|
}
|
|
2074
2158
|
else if (isResizing && resizeStart) {
|
|
2075
2159
|
const deltaX = e.clientX - resizeStart.x;
|
|
2076
2160
|
const deltaY = e.clientY - resizeStart.y;
|
|
2077
|
-
const maxWidth =
|
|
2078
|
-
const maxHeight =
|
|
2161
|
+
const maxWidth = targetWindow.innerWidth - position.left;
|
|
2162
|
+
const maxHeight = targetWindow.innerHeight - position.top;
|
|
2079
2163
|
const newWidth = Math.max(MIN_LIST_WIDTH, Math.min(resizeStart.width + deltaX, maxWidth));
|
|
2080
2164
|
const newHeight = Math.max(MIN_LIST_HEIGHT, Math.min(resizeStart.height + deltaY, maxHeight));
|
|
2081
2165
|
setSize({ width: newWidth, height: newHeight });
|
|
@@ -2092,13 +2176,13 @@ function useWidgetDragResize({ isCollapsed, isListMode }) {
|
|
|
2092
2176
|
setIsResizing(false);
|
|
2093
2177
|
setResizeStart(null);
|
|
2094
2178
|
};
|
|
2095
|
-
|
|
2096
|
-
|
|
2179
|
+
targetDocument.addEventListener('mousemove', handleMouseMove);
|
|
2180
|
+
targetDocument.addEventListener('mouseup', handleMouseUp);
|
|
2097
2181
|
return () => {
|
|
2098
|
-
|
|
2099
|
-
|
|
2182
|
+
targetDocument.removeEventListener('mousemove', handleMouseMove);
|
|
2183
|
+
targetDocument.removeEventListener('mouseup', handleMouseUp);
|
|
2100
2184
|
};
|
|
2101
|
-
}, [isDragging, isResizing, dragOffset, resizeStart, position, currentWidth, currentHeight]);
|
|
2185
|
+
}, [isDragging, isResizing, dragOffset, resizeStart, position, currentWidth, currentHeight, targetDocument]);
|
|
2102
2186
|
const handleResizeStart = (e) => {
|
|
2103
2187
|
e.stopPropagation();
|
|
2104
2188
|
if (!widgetRef.current)
|
|
@@ -2128,8 +2212,14 @@ function useWidgetDragResize({ isCollapsed, isListMode }) {
|
|
|
2128
2212
|
const TRANSITION_DURATION_S = 0.25;
|
|
2129
2213
|
|
|
2130
2214
|
const SETTINGS_STORAGE_KEY = 'widget_settings';
|
|
2131
|
-
/**
|
|
2215
|
+
/**
|
|
2216
|
+
* Mark message as received when its element is visible in the viewport.
|
|
2217
|
+
* When running inside an iframe without remote rendering (renderTarget: 'self'),
|
|
2218
|
+
* the IntersectionObserver would use the tiny iframe viewport and fire immediately,
|
|
2219
|
+
* so we skip it and rely on explicit user interactions instead.
|
|
2220
|
+
*/
|
|
2132
2221
|
function useMessageVisibility(ref, messageId, received, onMessageReceived) {
|
|
2222
|
+
const { isRemote } = useRenderContext();
|
|
2133
2223
|
const hasFiredRef = useRef(false);
|
|
2134
2224
|
useEffect(() => {
|
|
2135
2225
|
if (received) {
|
|
@@ -2140,6 +2230,17 @@ function useMessageVisibility(ref, messageId, received, onMessageReceived) {
|
|
|
2140
2230
|
const el = ref.current;
|
|
2141
2231
|
if (!el || !onMessageReceived)
|
|
2142
2232
|
return;
|
|
2233
|
+
// Skip IntersectionObserver when inside an iframe but rendering to self —
|
|
2234
|
+
// the iframe viewport is likely too small, causing false positives.
|
|
2235
|
+
let isInIframe;
|
|
2236
|
+
try {
|
|
2237
|
+
isInIframe = window !== window.top;
|
|
2238
|
+
}
|
|
2239
|
+
catch {
|
|
2240
|
+
isInIframe = true;
|
|
2241
|
+
}
|
|
2242
|
+
if (isInIframe && !isRemote)
|
|
2243
|
+
return;
|
|
2143
2244
|
const observer = new IntersectionObserver((entries) => {
|
|
2144
2245
|
const [entry] = entries;
|
|
2145
2246
|
if (entry?.isIntersecting && !hasFiredRef.current) {
|
|
@@ -2150,7 +2251,7 @@ function useMessageVisibility(ref, messageId, received, onMessageReceived) {
|
|
|
2150
2251
|
}, { threshold: 0.1, root: null });
|
|
2151
2252
|
observer.observe(el);
|
|
2152
2253
|
return () => observer.disconnect();
|
|
2153
|
-
}, [messageId, received, onMessageReceived]);
|
|
2254
|
+
}, [messageId, received, onMessageReceived, isRemote]);
|
|
2154
2255
|
}
|
|
2155
2256
|
/** Wraps a message row with a ref and viewport visibility observer to mark as received when visible. */
|
|
2156
2257
|
const MessageRow = ({ message, isSeparated, onMessageReceived, onDismissMessage, children }) => {
|
|
@@ -2170,7 +2271,6 @@ function useMessageCounts(messages) {
|
|
|
2170
2271
|
const unreadCount = messages.filter((m) => !m.received).length;
|
|
2171
2272
|
const readCount = messages.filter((m) => m.received).length;
|
|
2172
2273
|
return {
|
|
2173
|
-
totalCount: messages.length,
|
|
2174
2274
|
unreadCount,
|
|
2175
2275
|
readCount,
|
|
2176
2276
|
};
|
|
@@ -2178,7 +2278,7 @@ function useMessageCounts(messages) {
|
|
|
2178
2278
|
}
|
|
2179
2279
|
const MessageWidget = ({ store, onClose, onCloseWidget, onLinkClick, onToggleExpand, onMessageReceived, onDismissMessage, onNextMessage, onPrevMessage, onSettingsChange, configInfo, }) => {
|
|
2180
2280
|
const { messages, currentMessage, isCollapsed, displayMode, widgetVisible } = useMessagingStore(store);
|
|
2181
|
-
const {
|
|
2281
|
+
const { unreadCount, readCount } = useMessageCounts(messages);
|
|
2182
2282
|
const [modalMessage, setModalMessage] = useState(null);
|
|
2183
2283
|
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
|
2184
2284
|
const [settings, setSettings] = useState(() => {
|
|
@@ -2307,7 +2407,7 @@ const MessageWidget = ({ store, onClose, onCloseWidget, onLinkClick, onToggleExp
|
|
|
2307
2407
|
allMessagesToShow.length,
|
|
2308
2408
|
" messages)"))))),
|
|
2309
2409
|
React.createElement("div", { className: "journy-message-widget-controls" },
|
|
2310
|
-
!isCollapsed && (React.createElement("button", { className: "journy-message-widget-settings-btn", onClick: (e) => {
|
|
2410
|
+
isDebugSettings() && !isCollapsed && (React.createElement("button", { className: "journy-message-widget-settings-btn", onClick: (e) => {
|
|
2311
2411
|
e.stopPropagation();
|
|
2312
2412
|
e.preventDefault();
|
|
2313
2413
|
setIsSettingsOpen(true);
|
|
@@ -2328,7 +2428,7 @@ const MessageWidget = ({ store, onClose, onCloseWidget, onLinkClick, onToggleExp
|
|
|
2328
2428
|
}, onMouseDown: (e) => {
|
|
2329
2429
|
e.stopPropagation();
|
|
2330
2430
|
}, title: "Close widget", "aria-label": "Close" }, "\u00D7")),
|
|
2331
|
-
!isCollapsed && isListMode
|
|
2431
|
+
!isCollapsed && !isListMode && allMessagesToShow.length > 1 && (React.createElement(React.Fragment, null,
|
|
2332
2432
|
React.createElement("button", { className: "journy-message-widget-nav", onClick: (e) => {
|
|
2333
2433
|
e.stopPropagation();
|
|
2334
2434
|
e.preventDefault();
|
|
@@ -2354,7 +2454,7 @@ const MessageWidget = ({ store, onClose, onCloseWidget, onLinkClick, onToggleExp
|
|
|
2354
2454
|
React.createElement("div", { className: "journy-message-widget-content journy-message-widget-empty" },
|
|
2355
2455
|
React.createElement("p", null, "No unread messages")),
|
|
2356
2456
|
isListMode && React.createElement(ResizeHandle, { onMouseDown: handleResizeStart }))),
|
|
2357
|
-
!isCollapsed && (React.createElement(SettingsPanel, { isOpen: isSettingsOpen, onClose: () => setIsSettingsOpen(false), settings: settings, onSettingsChange: handleSettingsChange, configInfo: configInfo })),
|
|
2457
|
+
isDebugSettings() && !isCollapsed && (React.createElement(SettingsPanel, { isOpen: isSettingsOpen, onClose: () => setIsSettingsOpen(false), settings: settings, onSettingsChange: handleSettingsChange, configInfo: configInfo })),
|
|
2358
2458
|
modalMessage && (React.createElement(MessagePopup, { message: modalMessage, onClose: () => setModalMessage(null), onLinkClick: onLinkClick }))));
|
|
2359
2459
|
};
|
|
2360
2460
|
|
|
@@ -2393,7 +2493,7 @@ const SEND_ANALYTICS_PATH = "/frontend/v1/b";
|
|
|
2393
2493
|
class AnalyticsClient {
|
|
2394
2494
|
constructor(config) {
|
|
2395
2495
|
this.config = config;
|
|
2396
|
-
this.analyticsHost = config.apiEndpoint ||
|
|
2496
|
+
this.analyticsHost = config.apiEndpoint || DEFAULT_API_ENDPOINT;
|
|
2397
2497
|
}
|
|
2398
2498
|
get trackIdentity() {
|
|
2399
2499
|
return {
|
|
@@ -2426,19 +2526,6 @@ class AnalyticsClient {
|
|
|
2426
2526
|
}
|
|
2427
2527
|
}
|
|
2428
2528
|
|
|
2429
|
-
const DEFAULT_API_ENDPOINT = 'https://jtm.journy.io';
|
|
2430
|
-
const DEFAULT_POLLING_INTERVAL = 30000;
|
|
2431
|
-
const ROOT_ELEMENT_ID = 'journy-messages-root';
|
|
2432
|
-
const DEFAULT_STYLE_ID = 'journy-messages-default';
|
|
2433
|
-
const REACT_CHECK_INTERVAL = 100;
|
|
2434
|
-
const REACT_WAIT_TIMEOUT = 10000;
|
|
2435
|
-
const MESSAGE_CLOSE_DELAY = 300;
|
|
2436
|
-
const STORAGE_KEYS = {
|
|
2437
|
-
WIDGET_SETTINGS: 'widget_settings',
|
|
2438
|
-
WIDGET_VISIBLE: 'widget_visible',
|
|
2439
|
-
WIDGET_COLLAPSED: 'widget_collapsed',
|
|
2440
|
-
CURRENT_MESSAGE_ID: 'current_message_id',
|
|
2441
|
-
};
|
|
2442
2529
|
class JournyMessaging {
|
|
2443
2530
|
constructor(config) {
|
|
2444
2531
|
this.initialized = false;
|
|
@@ -2447,6 +2534,7 @@ class JournyMessaging {
|
|
|
2447
2534
|
this.reactRootContainer = null;
|
|
2448
2535
|
this.rootElementId = ROOT_ELEMENT_ID;
|
|
2449
2536
|
this.unsubscribePersistence = null;
|
|
2537
|
+
this.beforeUnloadHandler = null;
|
|
2450
2538
|
this.config = {
|
|
2451
2539
|
apiEndpoint: DEFAULT_API_ENDPOINT,
|
|
2452
2540
|
pollingInterval: DEFAULT_POLLING_INTERVAL,
|
|
@@ -2458,22 +2546,32 @@ class JournyMessaging {
|
|
|
2458
2546
|
if (!this.config.entityType) {
|
|
2459
2547
|
throw new Error('entityType is required');
|
|
2460
2548
|
}
|
|
2549
|
+
this.renderCtx = resolveRenderContext(config.renderTarget);
|
|
2550
|
+
// Clean up parent DOM when iframe unloads
|
|
2551
|
+
if (this.renderCtx.isRemote) {
|
|
2552
|
+
this.beforeUnloadHandler = () => this.destroy();
|
|
2553
|
+
window.addEventListener('beforeunload', this.beforeUnloadHandler);
|
|
2554
|
+
}
|
|
2461
2555
|
this.apiClient = new ApiClient(this.config);
|
|
2462
2556
|
this.messageQueue = new MessageQueue();
|
|
2463
2557
|
this.analyticsClient = new AnalyticsClient(this.config);
|
|
2464
2558
|
this.eventTracker = new EventTracker(this.analyticsClient);
|
|
2465
|
-
// Build default settings from config, then overlay any saved settings
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2559
|
+
// Build default settings from config, then overlay any saved settings.
|
|
2560
|
+
// Explicit config values (provided by the host) always take precedence over
|
|
2561
|
+
// saved localStorage settings so that changing config.js is never silently
|
|
2562
|
+
// ignored.
|
|
2563
|
+
const configOverrides = {
|
|
2564
|
+
...(config.pollingInterval != null && { pollingInterval: config.pollingInterval }),
|
|
2565
|
+
...(config.displayMode != null && { displayMode: config.displayMode }),
|
|
2566
|
+
...(config.apiEndpoint != null && { apiEndpoint: config.apiEndpoint }),
|
|
2567
|
+
...(config.styles != null && { styles: config.styles }),
|
|
2472
2568
|
};
|
|
2473
2569
|
const savedSettings = getItem(STORAGE_KEYS.WIDGET_SETTINGS);
|
|
2474
|
-
this.widgetSettings =
|
|
2475
|
-
|
|
2476
|
-
|
|
2570
|
+
this.widgetSettings = {
|
|
2571
|
+
...DEFAULT_WIDGET_SETTINGS,
|
|
2572
|
+
...(savedSettings || {}),
|
|
2573
|
+
...configOverrides,
|
|
2574
|
+
};
|
|
2477
2575
|
// Apply settings back to config so they take effect
|
|
2478
2576
|
this.config.pollingInterval = this.widgetSettings.pollingInterval;
|
|
2479
2577
|
this.config.apiEndpoint = this.widgetSettings.apiEndpoint;
|
|
@@ -2508,12 +2606,48 @@ class JournyMessaging {
|
|
|
2508
2606
|
return;
|
|
2509
2607
|
// Load existing messages
|
|
2510
2608
|
await this.loadMessages();
|
|
2609
|
+
// In debug mode, inject mock messages if none were loaded so the widget shows instantly
|
|
2610
|
+
if (isDebugMockMessages() && this.messageQueue.getActiveCount() === 0) {
|
|
2611
|
+
this.injectDebugMessages();
|
|
2612
|
+
}
|
|
2511
2613
|
// Set up polling or WebSocket connection
|
|
2512
2614
|
this.startPolling();
|
|
2513
2615
|
// Initialize UI
|
|
2514
2616
|
this.initializeUI();
|
|
2515
2617
|
this.initialized = true;
|
|
2516
2618
|
}
|
|
2619
|
+
injectDebugMessages() {
|
|
2620
|
+
const now = new Date().toISOString();
|
|
2621
|
+
const mockMessages = [
|
|
2622
|
+
{
|
|
2623
|
+
id: 'debug-msg-1',
|
|
2624
|
+
appId: 'debug',
|
|
2625
|
+
status: 'sent',
|
|
2626
|
+
scope: this.config.entityType,
|
|
2627
|
+
message: '<h3>Welcome!</h3><p>This is a <strong>debug preview</strong> message. It only appears when <code>window.__JOURNY_DEBUG__</code> is enabled.</p>',
|
|
2628
|
+
received: false,
|
|
2629
|
+
expired: false,
|
|
2630
|
+
createdAt: now,
|
|
2631
|
+
},
|
|
2632
|
+
{
|
|
2633
|
+
id: 'debug-msg-2',
|
|
2634
|
+
appId: 'debug',
|
|
2635
|
+
status: 'sent',
|
|
2636
|
+
scope: this.config.entityType,
|
|
2637
|
+
message: '<h3>Second Message</h3><p>Use this to test <a href="https://example.com">links</a>, navigation, and layout.</p>',
|
|
2638
|
+
received: true,
|
|
2639
|
+
expired: false,
|
|
2640
|
+
createdAt: now,
|
|
2641
|
+
},
|
|
2642
|
+
];
|
|
2643
|
+
this.messageQueue.addMessages(mockMessages);
|
|
2644
|
+
this.store.setState({
|
|
2645
|
+
messages: this.messageQueue.getAllMessages(),
|
|
2646
|
+
widgetVisible: true,
|
|
2647
|
+
isCollapsed: false,
|
|
2648
|
+
});
|
|
2649
|
+
this.displayNextMessage();
|
|
2650
|
+
}
|
|
2517
2651
|
async loadMessages() {
|
|
2518
2652
|
try {
|
|
2519
2653
|
const messages = await this.apiClient.getUnreadMessages();
|
|
@@ -2559,23 +2693,24 @@ class JournyMessaging {
|
|
|
2559
2693
|
}, this.config.pollingInterval || DEFAULT_POLLING_INTERVAL);
|
|
2560
2694
|
}
|
|
2561
2695
|
applyStyles(stylesConfig) {
|
|
2562
|
-
|
|
2696
|
+
const doc = this.renderCtx.targetDocument;
|
|
2697
|
+
removeInjectedStyles(doc);
|
|
2563
2698
|
if (stylesConfig === 'none') ;
|
|
2564
2699
|
else if (stylesConfig && stylesConfig !== 'default') {
|
|
2565
2700
|
if ('url' in stylesConfig && stylesConfig.url) {
|
|
2566
|
-
injectStyleLink(stylesConfig.url);
|
|
2701
|
+
injectStyleLink(stylesConfig.url, doc);
|
|
2567
2702
|
}
|
|
2568
2703
|
else if ('css' in stylesConfig && stylesConfig.css) {
|
|
2569
|
-
injectStyleTag(stylesConfig.css);
|
|
2704
|
+
injectStyleTag(stylesConfig.css, undefined, doc);
|
|
2570
2705
|
}
|
|
2571
2706
|
}
|
|
2572
2707
|
else {
|
|
2573
|
-
injectStyleTag(defaultStyles, DEFAULT_STYLE_ID);
|
|
2708
|
+
injectStyleTag(defaultStyles, DEFAULT_STYLE_ID, doc);
|
|
2574
2709
|
}
|
|
2575
2710
|
}
|
|
2576
2711
|
initializeUI() {
|
|
2577
|
-
// Create root element for React
|
|
2578
|
-
const rootElement = createRootElement(this.rootElementId);
|
|
2712
|
+
// Create root element for React in the target document
|
|
2713
|
+
const rootElement = createRootElement(this.rootElementId, this.renderCtx.targetDocument);
|
|
2579
2714
|
this.applyStyles(this.widgetSettings.styles);
|
|
2580
2715
|
// Always render UI (even if no messages) so widget can show/hide itself
|
|
2581
2716
|
// Dynamically import React and render component
|
|
@@ -2633,7 +2768,8 @@ class JournyMessaging {
|
|
|
2633
2768
|
accountId: this.config.accountId,
|
|
2634
2769
|
},
|
|
2635
2770
|
};
|
|
2636
|
-
const
|
|
2771
|
+
const widgetElement = React.createElement(MessageWidget$1, props);
|
|
2772
|
+
const element = React.createElement(RenderCtx.Provider, { value: this.renderCtx }, widgetElement);
|
|
2637
2773
|
// Use React 18 createRoot if available, otherwise fall back to render
|
|
2638
2774
|
if (ReactDOM.createRoot) {
|
|
2639
2775
|
if (!this.reactRoot) {
|
|
@@ -2819,8 +2955,12 @@ class JournyMessaging {
|
|
|
2819
2955
|
this.reactRoot = null;
|
|
2820
2956
|
this.reactRootContainer = null;
|
|
2821
2957
|
}
|
|
2822
|
-
|
|
2823
|
-
|
|
2958
|
+
if (this.beforeUnloadHandler) {
|
|
2959
|
+
window.removeEventListener('beforeunload', this.beforeUnloadHandler);
|
|
2960
|
+
this.beforeUnloadHandler = null;
|
|
2961
|
+
}
|
|
2962
|
+
removeRootElement(this.rootElementId, this.renderCtx.targetDocument);
|
|
2963
|
+
removeInjectedStyles(this.renderCtx.targetDocument);
|
|
2824
2964
|
}
|
|
2825
2965
|
}
|
|
2826
2966
|
|