@bootdesk/chat-widget-bridge 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +152 -0
- package/dist/embed-chat.cjs +195 -0
- package/dist/embed-chat.cjs.map +1 -0
- package/dist/embed-chat.js +193 -0
- package/dist/embed-chat.js.map +1 -0
- package/dist/index.cjs +70 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +22 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# @bootdesk/chat-widget-bridge
|
|
2
|
+
|
|
3
|
+
Iframe bridge for BootDesk Chat SDK — enables embedding the chat widget in an iframe with cross-frame communication via `postMessage`.
|
|
4
|
+
|
|
5
|
+
Includes:
|
|
6
|
+
- **`useIframeBridge`** — React hook for iframe communication
|
|
7
|
+
- **`embed-chat`** — Vanilla JS embed script that creates a floating chat button and iframe dynamically
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @bootdesk/chat-widget-bridge
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Peer dependency: `react` (only needed for `useIframeBridge`).
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
### In the iframe (child)
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { useIframeBridge } from "@bootdesk/chat-widget-bridge";
|
|
23
|
+
|
|
24
|
+
function Chat() {
|
|
25
|
+
const { config, isInIframe, notifyMessage, notifyViewportConfig, onNotificationClicked } =
|
|
26
|
+
useIframeBridge();
|
|
27
|
+
|
|
28
|
+
// config.title, config.locale, config.placeholder, config.theme.mode
|
|
29
|
+
// are set by the parent page via postMessage.
|
|
30
|
+
// notifyViewportConfig tells the parent to add/remove
|
|
31
|
+
// interactive-widget=resizes-content on the viewport meta (Android only).
|
|
32
|
+
// iOS doesn't support this — it uses dvh units instead.
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### In the parent page
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
const iframe = document.getElementById("chat-iframe");
|
|
40
|
+
|
|
41
|
+
// Send config to iframe
|
|
42
|
+
iframe.contentWindow.postMessage(
|
|
43
|
+
{
|
|
44
|
+
type: "chat-config",
|
|
45
|
+
title: "Support Chat",
|
|
46
|
+
locale: "pt-BR",
|
|
47
|
+
theme: { mode: "auto" },
|
|
48
|
+
},
|
|
49
|
+
"*",
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// Listen for messages from iframe
|
|
53
|
+
window.addEventListener("message", (event) => {
|
|
54
|
+
if (event.data?.type === "chat-message") {
|
|
55
|
+
console.log("User sent:", event.data.text);
|
|
56
|
+
}
|
|
57
|
+
if (event.data?.type === "chat-viewport-config") {
|
|
58
|
+
const meta = document.querySelector('meta[name="viewport"]');
|
|
59
|
+
if (!meta) return;
|
|
60
|
+
const current = meta.getAttribute("content") || "";
|
|
61
|
+
if (event.data.content) {
|
|
62
|
+
if (!current.includes(event.data.content)) {
|
|
63
|
+
meta.setAttribute("content", current + (current ? ", " : "") + event.data.content);
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
meta.setAttribute(
|
|
67
|
+
"content",
|
|
68
|
+
current.replace(/,?\s*interactive-widget=[^,]*/g, "").replace(/^,\s*/, ""),
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Trigger notification click in iframe
|
|
75
|
+
iframe.contentWindow.postMessage(
|
|
76
|
+
{ type: "chat-notification-clicked" },
|
|
77
|
+
"*",
|
|
78
|
+
);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## API
|
|
82
|
+
|
|
83
|
+
| Return value | Description |
|
|
84
|
+
|---|---|
|
|
85
|
+
| `config` | `BridgeConfig \| null` — config from parent (title, locale, placeholder, theme) |
|
|
86
|
+
| `isInIframe` | `boolean` — `true` when window !== window.parent |
|
|
87
|
+
| `notifyMessage(text)` | Sends `{ type: "chat-message", text }` to parent |
|
|
88
|
+
| `notifyViewportConfig(content)` | Sends `{ type: "chat-viewport-config", content }` to parent (Android keyboard support via `interactive-widget=resizes-content`; iOS handles this via `dvh` units) |
|
|
89
|
+
| `onNotificationClicked(cb)` | Registers callback for `chat-notification-clicked` from parent |
|
|
90
|
+
|
|
91
|
+
## Message Protocol
|
|
92
|
+
|
|
93
|
+
| Direction | type | Payload |
|
|
94
|
+
|---|---|---|
|
|
95
|
+
| Parent → Child | `chat-config` | `{ title?, locale?, placeholder?, theme?: { mode? } }` |
|
|
96
|
+
| Parent → Child | `chat-notification-clicked` | `{}` |
|
|
97
|
+
| Child → Parent | `chat-message` | `{ text: string }` |
|
|
98
|
+
| Child → Parent | `chat-close` | `{}` — requests parent to close/hide the iframe |
|
|
99
|
+
| Child → Parent | `chat-viewport-config` | `{ content: string }` — asks parent to add `interactive-widget=resizes-content` on viewport meta (Android only; iOS uses `dvh` units) |
|
|
100
|
+
|
|
101
|
+
## Embed Script (`embed-chat`)
|
|
102
|
+
|
|
103
|
+
Self-contained vanilla JS script that creates a floating chat button, overlay, and an iframe dynamically on any page.
|
|
104
|
+
|
|
105
|
+
### Usage
|
|
106
|
+
|
|
107
|
+
As a module import (Vite/webpack):
|
|
108
|
+
```js
|
|
109
|
+
import "@bootdesk/chat-widget-bridge/embed-chat";
|
|
110
|
+
|
|
111
|
+
ChatSDK.initialize();
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Via a `<script>` tag:
|
|
115
|
+
```html
|
|
116
|
+
<script src="https://cdn.example.com/embed-chat.js"></script>
|
|
117
|
+
<script>
|
|
118
|
+
ChatSDK.initialize({
|
|
119
|
+
iframeSrc: "/my-chat-page",
|
|
120
|
+
title: "Support Chat",
|
|
121
|
+
placeholder: "How can we help?",
|
|
122
|
+
buttonInnerHtml: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>',
|
|
123
|
+
buttonStyle: { background: "#ff4433" },
|
|
124
|
+
overlayStyle: { background: "rgba(0,0,0,0.5)" },
|
|
125
|
+
});
|
|
126
|
+
</script>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Behavior
|
|
130
|
+
|
|
131
|
+
- Exposes `window.ChatSDK.initialize()` to create a floating chat button, overlay, and iframe
|
|
132
|
+
- On click, opens a panel with the iframe (slide + fade animation)
|
|
133
|
+
- On small screens (<800px) the iframe goes fullscreen and the overlay is hidden
|
|
134
|
+
- Reads `localStorage` key `chat-theme` and passes it to the iframe via `chat-config`
|
|
135
|
+
- Listens for `chat-close` message from the iframe and closes the panel
|
|
136
|
+
- Logs `chat-message` events from the iframe to the console
|
|
137
|
+
- Handles `chat-viewport-config` to update the parent page's viewport meta for Android keyboard (`interactive-widget=resizes-content`; iOS uses `dvh` units)
|
|
138
|
+
|
|
139
|
+
### Configuration
|
|
140
|
+
|
|
141
|
+
| Option | Type | Default | Description |
|
|
142
|
+
|---|---|---|---|
|
|
143
|
+
| `iframeSrc` | `string` | `"/chat-iframe"` | URL for the iframe src |
|
|
144
|
+
| `title` | `string` | `"Chat"` | Title sent to the iframe via `chat-config` |
|
|
145
|
+
| `placeholder` | `string` | `"Type a message..."` | Placeholder sent to the iframe via `chat-config` |
|
|
146
|
+
| `buttonInnerHtml` | `string` | Chat bubble SVG | Inner HTML of the floating button |
|
|
147
|
+
| `buttonStyle` | `object` | Default button styles | CSS overrides for the button |
|
|
148
|
+
| `overlayStyle` | `object` | Default overlay styles | CSS overrides for the overlay |
|
|
149
|
+
|
|
150
|
+
## License
|
|
151
|
+
|
|
152
|
+
MIT
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// src/embed-chat.js
|
|
4
|
+
(function() {
|
|
5
|
+
"use strict";
|
|
6
|
+
var DEFAULTS = {
|
|
7
|
+
iframeSrc: "/chat-iframe",
|
|
8
|
+
title: "Chat",
|
|
9
|
+
placeholder: "Type a message...",
|
|
10
|
+
buttonInnerHtml: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>',
|
|
11
|
+
buttonStyle: {
|
|
12
|
+
position: "fixed",
|
|
13
|
+
bottom: "24px",
|
|
14
|
+
right: "24px",
|
|
15
|
+
width: "56px",
|
|
16
|
+
height: "56px",
|
|
17
|
+
borderRadius: "50%",
|
|
18
|
+
border: "none",
|
|
19
|
+
background: "var(--chat-primary, #6366f1)",
|
|
20
|
+
color: "#fff",
|
|
21
|
+
cursor: "pointer",
|
|
22
|
+
display: "flex",
|
|
23
|
+
alignItems: "center",
|
|
24
|
+
justifyContent: "center",
|
|
25
|
+
boxShadow: "0 4px 16px rgba(0,0,0,0.2)",
|
|
26
|
+
zIndex: "2147483646",
|
|
27
|
+
transition: "transform 0.2s, opacity 0.2s"
|
|
28
|
+
},
|
|
29
|
+
overlayStyle: {
|
|
30
|
+
position: "fixed",
|
|
31
|
+
inset: "0",
|
|
32
|
+
background: "rgba(0,0,0,0.3)",
|
|
33
|
+
zIndex: "2147483646",
|
|
34
|
+
opacity: "0",
|
|
35
|
+
transition: "opacity 0.2s",
|
|
36
|
+
pointerEvents: "none"
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var state = {
|
|
40
|
+
opts: null,
|
|
41
|
+
button: null,
|
|
42
|
+
iframe: null,
|
|
43
|
+
overlay: null,
|
|
44
|
+
isOpen: false,
|
|
45
|
+
originalViewport: void 0
|
|
46
|
+
};
|
|
47
|
+
function mergeStyles(base, overrides) {
|
|
48
|
+
var result = {};
|
|
49
|
+
for (var key in base) result[key] = base[key];
|
|
50
|
+
if (overrides) {
|
|
51
|
+
for (var key in overrides) result[key] = overrides[key];
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
function createButton() {
|
|
56
|
+
var opts = state.opts;
|
|
57
|
+
state.button = document.createElement("button");
|
|
58
|
+
state.button.setAttribute("data-embed-chat-btn", "");
|
|
59
|
+
state.button.setAttribute("aria-label", "Open chat");
|
|
60
|
+
state.button.setAttribute("aria-expanded", "false");
|
|
61
|
+
state.button.innerHTML = opts.buttonInnerHtml;
|
|
62
|
+
Object.assign(state.button.style, mergeStyles(DEFAULTS.buttonStyle, opts.buttonStyle));
|
|
63
|
+
state.button.addEventListener("mouseenter", function() {
|
|
64
|
+
state.button.style.transform = "scale(1.05)";
|
|
65
|
+
});
|
|
66
|
+
state.button.addEventListener("mouseleave", function() {
|
|
67
|
+
state.button.style.transform = "";
|
|
68
|
+
});
|
|
69
|
+
state.button.addEventListener("click", toggle);
|
|
70
|
+
state.button.addEventListener("keydown", function(e) {
|
|
71
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
72
|
+
e.preventDefault();
|
|
73
|
+
toggle();
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
document.body.appendChild(state.button);
|
|
77
|
+
}
|
|
78
|
+
function createOverlay() {
|
|
79
|
+
var opts = state.opts;
|
|
80
|
+
state.overlay = document.createElement("div");
|
|
81
|
+
state.overlay.setAttribute("data-embed-chat-overlay", "");
|
|
82
|
+
Object.assign(state.overlay.style, mergeStyles(DEFAULTS.overlayStyle, opts.overlayStyle));
|
|
83
|
+
state.overlay.addEventListener("click", toggle);
|
|
84
|
+
document.body.appendChild(state.overlay);
|
|
85
|
+
}
|
|
86
|
+
function createIframe() {
|
|
87
|
+
var opts = state.opts;
|
|
88
|
+
state.iframe = document.createElement("iframe");
|
|
89
|
+
state.iframe.setAttribute("data-embed-chat-iframe", "");
|
|
90
|
+
state.iframe.setAttribute("title", "Chat Widget");
|
|
91
|
+
state.iframe.setAttribute("role", "dialog");
|
|
92
|
+
state.iframe.setAttribute("aria-modal", "true");
|
|
93
|
+
state.iframe.setAttribute("allow", "clipboard-write; microphone");
|
|
94
|
+
state.iframe.src = opts.iframeSrc;
|
|
95
|
+
Object.assign(state.iframe.style, {
|
|
96
|
+
position: "fixed",
|
|
97
|
+
bottom: "96px",
|
|
98
|
+
right: "24px",
|
|
99
|
+
width: "420px",
|
|
100
|
+
height: "600px",
|
|
101
|
+
maxWidth: "calc(100dvw - 48px)",
|
|
102
|
+
maxHeight: "calc(100dvh - 120px)",
|
|
103
|
+
border: "none",
|
|
104
|
+
borderRadius: "16px",
|
|
105
|
+
boxShadow: "0 8px 32px rgba(0,0,0,0.15)",
|
|
106
|
+
zIndex: "2147483647",
|
|
107
|
+
opacity: "0",
|
|
108
|
+
transform: "translateY(16px) scale(0.96)",
|
|
109
|
+
transition: "opacity 0.2s, transform 0.25s",
|
|
110
|
+
pointerEvents: "none",
|
|
111
|
+
background: "#fff"
|
|
112
|
+
});
|
|
113
|
+
document.body.appendChild(state.iframe);
|
|
114
|
+
state.iframe.addEventListener("load", function() {
|
|
115
|
+
var savedTheme = "auto";
|
|
116
|
+
try {
|
|
117
|
+
var stored = localStorage.getItem("chat-theme");
|
|
118
|
+
if (stored === "light" || stored === "dark" || stored === "auto") savedTheme = stored;
|
|
119
|
+
} catch {
|
|
120
|
+
}
|
|
121
|
+
state.iframe.contentWindow.postMessage(
|
|
122
|
+
{ type: "chat-config", title: opts.title, placeholder: opts.placeholder, theme: { mode: savedTheme } },
|
|
123
|
+
"*"
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function close() {
|
|
128
|
+
if (!state.isOpen) return;
|
|
129
|
+
toggle();
|
|
130
|
+
}
|
|
131
|
+
function toggle() {
|
|
132
|
+
state.isOpen = !state.isOpen;
|
|
133
|
+
var open = state.isOpen;
|
|
134
|
+
state.button.setAttribute("aria-expanded", String(open));
|
|
135
|
+
state.iframe.style.opacity = open ? "1" : "0";
|
|
136
|
+
state.iframe.style.transform = open ? "translateY(0) scale(1)" : "translateY(16px) scale(0.96)";
|
|
137
|
+
state.iframe.style.pointerEvents = open ? "auto" : "none";
|
|
138
|
+
state.overlay.style.opacity = open ? "1" : "0";
|
|
139
|
+
state.overlay.style.pointerEvents = open ? "auto" : "none";
|
|
140
|
+
state.button.style.transform = open ? "scale(0)" : "";
|
|
141
|
+
state.button.style.opacity = open ? "0" : "1";
|
|
142
|
+
state.button.style.pointerEvents = open ? "none" : "auto";
|
|
143
|
+
document.body.style.overflow = open ? "hidden" : "";
|
|
144
|
+
if (open) state.iframe.focus();
|
|
145
|
+
}
|
|
146
|
+
function handleMessage(event) {
|
|
147
|
+
if (!state.iframe || event.source !== state.iframe.contentWindow) return;
|
|
148
|
+
var data = event.data || {};
|
|
149
|
+
if (data.type === "chat-message") {
|
|
150
|
+
console.log("[Embed Chat] Message:", data.text);
|
|
151
|
+
}
|
|
152
|
+
if (data.type === "chat-close") {
|
|
153
|
+
close();
|
|
154
|
+
}
|
|
155
|
+
if (data.type === "chat-viewport-config") {
|
|
156
|
+
var meta = document.querySelector('meta[name="viewport"]');
|
|
157
|
+
if (!meta) return;
|
|
158
|
+
var current = meta.getAttribute("content") || "";
|
|
159
|
+
if (data.content) {
|
|
160
|
+
if (state.originalViewport === void 0) state.originalViewport = current;
|
|
161
|
+
if (!current.includes(data.content)) {
|
|
162
|
+
meta.setAttribute("content", current + (current ? ", " : "") + data.content);
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
meta.setAttribute("content", state.originalViewport ?? current);
|
|
166
|
+
state.originalViewport = void 0;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function initialize(opts) {
|
|
171
|
+
if (document.querySelector("[data-embed-chat-btn]")) return;
|
|
172
|
+
state.opts = {};
|
|
173
|
+
for (var key in DEFAULTS) {
|
|
174
|
+
state.opts[key] = DEFAULTS[key];
|
|
175
|
+
}
|
|
176
|
+
if (opts) {
|
|
177
|
+
for (var key in opts) {
|
|
178
|
+
if (key === "buttonStyle" || key === "overlayStyle") {
|
|
179
|
+
state.opts[key] = mergeStyles(state.opts[key] || {}, opts[key]);
|
|
180
|
+
} else if (opts[key] !== void 0) {
|
|
181
|
+
state.opts[key] = opts[key];
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
var style = document.createElement("style");
|
|
186
|
+
style.textContent = "@media (max-width: 799px) { [data-embed-chat-iframe] { width: 100dvw !important; height: 100dvh !important; bottom: 0 !important; right: 0 !important; max-width: none !important; max-height: none !important; border-radius: 0 !important; } [data-embed-chat-overlay] { display: none !important; } }";
|
|
187
|
+
document.head.appendChild(style);
|
|
188
|
+
createOverlay();
|
|
189
|
+
createIframe();
|
|
190
|
+
createButton();
|
|
191
|
+
window.addEventListener("message", handleMessage);
|
|
192
|
+
}
|
|
193
|
+
window.ChatSDK = { initialize };
|
|
194
|
+
})();
|
|
195
|
+
//# sourceMappingURL=embed-chat.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/embed-chat.js"],"sourcesContent":["(function () {\n \"use strict\";\n\n var DEFAULTS = {\n iframeSrc: \"/chat-iframe\",\n title: \"Chat\",\n placeholder: \"Type a message...\",\n buttonInnerHtml:\n '<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/></svg>',\n buttonStyle: {\n position: \"fixed\",\n bottom: \"24px\",\n right: \"24px\",\n width: \"56px\",\n height: \"56px\",\n borderRadius: \"50%\",\n border: \"none\",\n background: \"var(--chat-primary, #6366f1)\",\n color: \"#fff\",\n cursor: \"pointer\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n boxShadow: \"0 4px 16px rgba(0,0,0,0.2)\",\n zIndex: \"2147483646\",\n transition: \"transform 0.2s, opacity 0.2s\",\n },\n overlayStyle: {\n position: \"fixed\",\n inset: \"0\",\n background: \"rgba(0,0,0,0.3)\",\n zIndex: \"2147483646\",\n opacity: \"0\",\n transition: \"opacity 0.2s\",\n pointerEvents: \"none\",\n },\n };\n\n var state = {\n opts: null,\n button: null,\n iframe: null,\n overlay: null,\n isOpen: false,\n originalViewport: undefined,\n };\n\n function mergeStyles(base, overrides) {\n var result = {};\n for (var key in base) result[key] = base[key];\n if (overrides) {\n for (var key in overrides) result[key] = overrides[key];\n }\n return result;\n }\n\n function createButton() {\n var opts = state.opts;\n state.button = document.createElement(\"button\");\n state.button.setAttribute(\"data-embed-chat-btn\", \"\");\n state.button.setAttribute(\"aria-label\", \"Open chat\");\n state.button.setAttribute(\"aria-expanded\", \"false\");\n state.button.innerHTML = opts.buttonInnerHtml;\n Object.assign(state.button.style, mergeStyles(DEFAULTS.buttonStyle, opts.buttonStyle));\n state.button.addEventListener(\"mouseenter\", function () {\n state.button.style.transform = \"scale(1.05)\";\n });\n state.button.addEventListener(\"mouseleave\", function () {\n state.button.style.transform = \"\";\n });\n state.button.addEventListener(\"click\", toggle);\n state.button.addEventListener(\"keydown\", function (e) {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n toggle();\n }\n });\n document.body.appendChild(state.button);\n }\n\n function createOverlay() {\n var opts = state.opts;\n state.overlay = document.createElement(\"div\");\n state.overlay.setAttribute(\"data-embed-chat-overlay\", \"\");\n Object.assign(state.overlay.style, mergeStyles(DEFAULTS.overlayStyle, opts.overlayStyle));\n state.overlay.addEventListener(\"click\", toggle);\n document.body.appendChild(state.overlay);\n }\n\n function createIframe() {\n var opts = state.opts;\n state.iframe = document.createElement(\"iframe\");\n state.iframe.setAttribute(\"data-embed-chat-iframe\", \"\");\n state.iframe.setAttribute(\"title\", \"Chat Widget\");\n state.iframe.setAttribute(\"role\", \"dialog\");\n state.iframe.setAttribute(\"aria-modal\", \"true\");\n state.iframe.setAttribute(\"allow\", \"clipboard-write; microphone\");\n state.iframe.src = opts.iframeSrc;\n Object.assign(state.iframe.style, {\n position: \"fixed\",\n bottom: \"96px\",\n right: \"24px\",\n width: \"420px\",\n height: \"600px\",\n maxWidth: \"calc(100dvw - 48px)\",\n maxHeight: \"calc(100dvh - 120px)\",\n border: \"none\",\n borderRadius: \"16px\",\n boxShadow: \"0 8px 32px rgba(0,0,0,0.15)\",\n zIndex: \"2147483647\",\n opacity: \"0\",\n transform: \"translateY(16px) scale(0.96)\",\n transition: \"opacity 0.2s, transform 0.25s\",\n pointerEvents: \"none\",\n background: \"#fff\",\n });\n document.body.appendChild(state.iframe);\n\n state.iframe.addEventListener(\"load\", function () {\n var savedTheme = \"auto\";\n try {\n var stored = localStorage.getItem(\"chat-theme\");\n if (stored === \"light\" || stored === \"dark\" || stored === \"auto\") savedTheme = stored;\n } catch { /* unavailable */ }\n state.iframe.contentWindow.postMessage(\n { type: \"chat-config\", title: opts.title, placeholder: opts.placeholder, theme: { mode: savedTheme } },\n \"*\",\n );\n });\n }\n\n function close() {\n if (!state.isOpen) return;\n toggle();\n }\n\n function toggle() {\n state.isOpen = !state.isOpen;\n var open = state.isOpen;\n\n state.button.setAttribute(\"aria-expanded\", String(open));\n state.iframe.style.opacity = open ? \"1\" : \"0\";\n state.iframe.style.transform = open ? \"translateY(0) scale(1)\" : \"translateY(16px) scale(0.96)\";\n state.iframe.style.pointerEvents = open ? \"auto\" : \"none\";\n\n state.overlay.style.opacity = open ? \"1\" : \"0\";\n state.overlay.style.pointerEvents = open ? \"auto\" : \"none\";\n\n state.button.style.transform = open ? \"scale(0)\" : \"\";\n state.button.style.opacity = open ? \"0\" : \"1\";\n state.button.style.pointerEvents = open ? \"none\" : \"auto\";\n\n document.body.style.overflow = open ? \"hidden\" : \"\";\n\n if (open) state.iframe.focus();\n }\n\n function handleMessage(event) {\n if (!state.iframe || event.source !== state.iframe.contentWindow) return;\n var data = event.data || {};\n if (data.type === \"chat-message\") {\n console.log(\"[Embed Chat] Message:\", data.text);\n }\n if (data.type === \"chat-close\") {\n close();\n }\n if (data.type === \"chat-viewport-config\") {\n var meta = document.querySelector('meta[name=\"viewport\"]');\n if (!meta) return;\n var current = meta.getAttribute(\"content\") || \"\";\n if (data.content) {\n if (state.originalViewport === undefined) state.originalViewport = current;\n if (!current.includes(data.content)) {\n meta.setAttribute(\"content\", current + (current ? \", \" : \"\") + data.content);\n }\n } else {\n meta.setAttribute(\"content\", state.originalViewport ?? current);\n state.originalViewport = undefined;\n }\n }\n }\n\n function initialize(opts) {\n if (document.querySelector(\"[data-embed-chat-btn]\")) return;\n\n state.opts = {};\n for (var key in DEFAULTS) {\n state.opts[key] = DEFAULTS[key];\n }\n if (opts) {\n for (var key in opts) {\n if (key === \"buttonStyle\" || key === \"overlayStyle\") {\n state.opts[key] = mergeStyles(state.opts[key] || {}, opts[key]);\n } else if (opts[key] !== undefined) {\n state.opts[key] = opts[key];\n }\n }\n }\n\n var style = document.createElement(\"style\");\n style.textContent =\n '@media (max-width: 799px) { [data-embed-chat-iframe] { width: 100dvw !important; height: 100dvh !important; bottom: 0 !important; right: 0 !important; max-width: none !important; max-height: none !important; border-radius: 0 !important; } [data-embed-chat-overlay] { display: none !important; } }';\n document.head.appendChild(style);\n\n createOverlay();\n createIframe();\n createButton();\n window.addEventListener(\"message\", handleMessage);\n }\n\n window.ChatSDK = { initialize: initialize };\n})();\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// src/embed-chat.js
|
|
2
|
+
(function() {
|
|
3
|
+
"use strict";
|
|
4
|
+
var DEFAULTS = {
|
|
5
|
+
iframeSrc: "/chat-iframe",
|
|
6
|
+
title: "Chat",
|
|
7
|
+
placeholder: "Type a message...",
|
|
8
|
+
buttonInnerHtml: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>',
|
|
9
|
+
buttonStyle: {
|
|
10
|
+
position: "fixed",
|
|
11
|
+
bottom: "24px",
|
|
12
|
+
right: "24px",
|
|
13
|
+
width: "56px",
|
|
14
|
+
height: "56px",
|
|
15
|
+
borderRadius: "50%",
|
|
16
|
+
border: "none",
|
|
17
|
+
background: "var(--chat-primary, #6366f1)",
|
|
18
|
+
color: "#fff",
|
|
19
|
+
cursor: "pointer",
|
|
20
|
+
display: "flex",
|
|
21
|
+
alignItems: "center",
|
|
22
|
+
justifyContent: "center",
|
|
23
|
+
boxShadow: "0 4px 16px rgba(0,0,0,0.2)",
|
|
24
|
+
zIndex: "2147483646",
|
|
25
|
+
transition: "transform 0.2s, opacity 0.2s"
|
|
26
|
+
},
|
|
27
|
+
overlayStyle: {
|
|
28
|
+
position: "fixed",
|
|
29
|
+
inset: "0",
|
|
30
|
+
background: "rgba(0,0,0,0.3)",
|
|
31
|
+
zIndex: "2147483646",
|
|
32
|
+
opacity: "0",
|
|
33
|
+
transition: "opacity 0.2s",
|
|
34
|
+
pointerEvents: "none"
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var state = {
|
|
38
|
+
opts: null,
|
|
39
|
+
button: null,
|
|
40
|
+
iframe: null,
|
|
41
|
+
overlay: null,
|
|
42
|
+
isOpen: false,
|
|
43
|
+
originalViewport: void 0
|
|
44
|
+
};
|
|
45
|
+
function mergeStyles(base, overrides) {
|
|
46
|
+
var result = {};
|
|
47
|
+
for (var key in base) result[key] = base[key];
|
|
48
|
+
if (overrides) {
|
|
49
|
+
for (var key in overrides) result[key] = overrides[key];
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
function createButton() {
|
|
54
|
+
var opts = state.opts;
|
|
55
|
+
state.button = document.createElement("button");
|
|
56
|
+
state.button.setAttribute("data-embed-chat-btn", "");
|
|
57
|
+
state.button.setAttribute("aria-label", "Open chat");
|
|
58
|
+
state.button.setAttribute("aria-expanded", "false");
|
|
59
|
+
state.button.innerHTML = opts.buttonInnerHtml;
|
|
60
|
+
Object.assign(state.button.style, mergeStyles(DEFAULTS.buttonStyle, opts.buttonStyle));
|
|
61
|
+
state.button.addEventListener("mouseenter", function() {
|
|
62
|
+
state.button.style.transform = "scale(1.05)";
|
|
63
|
+
});
|
|
64
|
+
state.button.addEventListener("mouseleave", function() {
|
|
65
|
+
state.button.style.transform = "";
|
|
66
|
+
});
|
|
67
|
+
state.button.addEventListener("click", toggle);
|
|
68
|
+
state.button.addEventListener("keydown", function(e) {
|
|
69
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
70
|
+
e.preventDefault();
|
|
71
|
+
toggle();
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
document.body.appendChild(state.button);
|
|
75
|
+
}
|
|
76
|
+
function createOverlay() {
|
|
77
|
+
var opts = state.opts;
|
|
78
|
+
state.overlay = document.createElement("div");
|
|
79
|
+
state.overlay.setAttribute("data-embed-chat-overlay", "");
|
|
80
|
+
Object.assign(state.overlay.style, mergeStyles(DEFAULTS.overlayStyle, opts.overlayStyle));
|
|
81
|
+
state.overlay.addEventListener("click", toggle);
|
|
82
|
+
document.body.appendChild(state.overlay);
|
|
83
|
+
}
|
|
84
|
+
function createIframe() {
|
|
85
|
+
var opts = state.opts;
|
|
86
|
+
state.iframe = document.createElement("iframe");
|
|
87
|
+
state.iframe.setAttribute("data-embed-chat-iframe", "");
|
|
88
|
+
state.iframe.setAttribute("title", "Chat Widget");
|
|
89
|
+
state.iframe.setAttribute("role", "dialog");
|
|
90
|
+
state.iframe.setAttribute("aria-modal", "true");
|
|
91
|
+
state.iframe.setAttribute("allow", "clipboard-write; microphone");
|
|
92
|
+
state.iframe.src = opts.iframeSrc;
|
|
93
|
+
Object.assign(state.iframe.style, {
|
|
94
|
+
position: "fixed",
|
|
95
|
+
bottom: "96px",
|
|
96
|
+
right: "24px",
|
|
97
|
+
width: "420px",
|
|
98
|
+
height: "600px",
|
|
99
|
+
maxWidth: "calc(100dvw - 48px)",
|
|
100
|
+
maxHeight: "calc(100dvh - 120px)",
|
|
101
|
+
border: "none",
|
|
102
|
+
borderRadius: "16px",
|
|
103
|
+
boxShadow: "0 8px 32px rgba(0,0,0,0.15)",
|
|
104
|
+
zIndex: "2147483647",
|
|
105
|
+
opacity: "0",
|
|
106
|
+
transform: "translateY(16px) scale(0.96)",
|
|
107
|
+
transition: "opacity 0.2s, transform 0.25s",
|
|
108
|
+
pointerEvents: "none",
|
|
109
|
+
background: "#fff"
|
|
110
|
+
});
|
|
111
|
+
document.body.appendChild(state.iframe);
|
|
112
|
+
state.iframe.addEventListener("load", function() {
|
|
113
|
+
var savedTheme = "auto";
|
|
114
|
+
try {
|
|
115
|
+
var stored = localStorage.getItem("chat-theme");
|
|
116
|
+
if (stored === "light" || stored === "dark" || stored === "auto") savedTheme = stored;
|
|
117
|
+
} catch {
|
|
118
|
+
}
|
|
119
|
+
state.iframe.contentWindow.postMessage(
|
|
120
|
+
{ type: "chat-config", title: opts.title, placeholder: opts.placeholder, theme: { mode: savedTheme } },
|
|
121
|
+
"*"
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
function close() {
|
|
126
|
+
if (!state.isOpen) return;
|
|
127
|
+
toggle();
|
|
128
|
+
}
|
|
129
|
+
function toggle() {
|
|
130
|
+
state.isOpen = !state.isOpen;
|
|
131
|
+
var open = state.isOpen;
|
|
132
|
+
state.button.setAttribute("aria-expanded", String(open));
|
|
133
|
+
state.iframe.style.opacity = open ? "1" : "0";
|
|
134
|
+
state.iframe.style.transform = open ? "translateY(0) scale(1)" : "translateY(16px) scale(0.96)";
|
|
135
|
+
state.iframe.style.pointerEvents = open ? "auto" : "none";
|
|
136
|
+
state.overlay.style.opacity = open ? "1" : "0";
|
|
137
|
+
state.overlay.style.pointerEvents = open ? "auto" : "none";
|
|
138
|
+
state.button.style.transform = open ? "scale(0)" : "";
|
|
139
|
+
state.button.style.opacity = open ? "0" : "1";
|
|
140
|
+
state.button.style.pointerEvents = open ? "none" : "auto";
|
|
141
|
+
document.body.style.overflow = open ? "hidden" : "";
|
|
142
|
+
if (open) state.iframe.focus();
|
|
143
|
+
}
|
|
144
|
+
function handleMessage(event) {
|
|
145
|
+
if (!state.iframe || event.source !== state.iframe.contentWindow) return;
|
|
146
|
+
var data = event.data || {};
|
|
147
|
+
if (data.type === "chat-message") {
|
|
148
|
+
console.log("[Embed Chat] Message:", data.text);
|
|
149
|
+
}
|
|
150
|
+
if (data.type === "chat-close") {
|
|
151
|
+
close();
|
|
152
|
+
}
|
|
153
|
+
if (data.type === "chat-viewport-config") {
|
|
154
|
+
var meta = document.querySelector('meta[name="viewport"]');
|
|
155
|
+
if (!meta) return;
|
|
156
|
+
var current = meta.getAttribute("content") || "";
|
|
157
|
+
if (data.content) {
|
|
158
|
+
if (state.originalViewport === void 0) state.originalViewport = current;
|
|
159
|
+
if (!current.includes(data.content)) {
|
|
160
|
+
meta.setAttribute("content", current + (current ? ", " : "") + data.content);
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
meta.setAttribute("content", state.originalViewport ?? current);
|
|
164
|
+
state.originalViewport = void 0;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function initialize(opts) {
|
|
169
|
+
if (document.querySelector("[data-embed-chat-btn]")) return;
|
|
170
|
+
state.opts = {};
|
|
171
|
+
for (var key in DEFAULTS) {
|
|
172
|
+
state.opts[key] = DEFAULTS[key];
|
|
173
|
+
}
|
|
174
|
+
if (opts) {
|
|
175
|
+
for (var key in opts) {
|
|
176
|
+
if (key === "buttonStyle" || key === "overlayStyle") {
|
|
177
|
+
state.opts[key] = mergeStyles(state.opts[key] || {}, opts[key]);
|
|
178
|
+
} else if (opts[key] !== void 0) {
|
|
179
|
+
state.opts[key] = opts[key];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
var style = document.createElement("style");
|
|
184
|
+
style.textContent = "@media (max-width: 799px) { [data-embed-chat-iframe] { width: 100dvw !important; height: 100dvh !important; bottom: 0 !important; right: 0 !important; max-width: none !important; max-height: none !important; border-radius: 0 !important; } [data-embed-chat-overlay] { display: none !important; } }";
|
|
185
|
+
document.head.appendChild(style);
|
|
186
|
+
createOverlay();
|
|
187
|
+
createIframe();
|
|
188
|
+
createButton();
|
|
189
|
+
window.addEventListener("message", handleMessage);
|
|
190
|
+
}
|
|
191
|
+
window.ChatSDK = { initialize };
|
|
192
|
+
})();
|
|
193
|
+
//# sourceMappingURL=embed-chat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/embed-chat.js"],"sourcesContent":["(function () {\n \"use strict\";\n\n var DEFAULTS = {\n iframeSrc: \"/chat-iframe\",\n title: \"Chat\",\n placeholder: \"Type a message...\",\n buttonInnerHtml:\n '<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/></svg>',\n buttonStyle: {\n position: \"fixed\",\n bottom: \"24px\",\n right: \"24px\",\n width: \"56px\",\n height: \"56px\",\n borderRadius: \"50%\",\n border: \"none\",\n background: \"var(--chat-primary, #6366f1)\",\n color: \"#fff\",\n cursor: \"pointer\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n boxShadow: \"0 4px 16px rgba(0,0,0,0.2)\",\n zIndex: \"2147483646\",\n transition: \"transform 0.2s, opacity 0.2s\",\n },\n overlayStyle: {\n position: \"fixed\",\n inset: \"0\",\n background: \"rgba(0,0,0,0.3)\",\n zIndex: \"2147483646\",\n opacity: \"0\",\n transition: \"opacity 0.2s\",\n pointerEvents: \"none\",\n },\n };\n\n var state = {\n opts: null,\n button: null,\n iframe: null,\n overlay: null,\n isOpen: false,\n originalViewport: undefined,\n };\n\n function mergeStyles(base, overrides) {\n var result = {};\n for (var key in base) result[key] = base[key];\n if (overrides) {\n for (var key in overrides) result[key] = overrides[key];\n }\n return result;\n }\n\n function createButton() {\n var opts = state.opts;\n state.button = document.createElement(\"button\");\n state.button.setAttribute(\"data-embed-chat-btn\", \"\");\n state.button.setAttribute(\"aria-label\", \"Open chat\");\n state.button.setAttribute(\"aria-expanded\", \"false\");\n state.button.innerHTML = opts.buttonInnerHtml;\n Object.assign(state.button.style, mergeStyles(DEFAULTS.buttonStyle, opts.buttonStyle));\n state.button.addEventListener(\"mouseenter\", function () {\n state.button.style.transform = \"scale(1.05)\";\n });\n state.button.addEventListener(\"mouseleave\", function () {\n state.button.style.transform = \"\";\n });\n state.button.addEventListener(\"click\", toggle);\n state.button.addEventListener(\"keydown\", function (e) {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n toggle();\n }\n });\n document.body.appendChild(state.button);\n }\n\n function createOverlay() {\n var opts = state.opts;\n state.overlay = document.createElement(\"div\");\n state.overlay.setAttribute(\"data-embed-chat-overlay\", \"\");\n Object.assign(state.overlay.style, mergeStyles(DEFAULTS.overlayStyle, opts.overlayStyle));\n state.overlay.addEventListener(\"click\", toggle);\n document.body.appendChild(state.overlay);\n }\n\n function createIframe() {\n var opts = state.opts;\n state.iframe = document.createElement(\"iframe\");\n state.iframe.setAttribute(\"data-embed-chat-iframe\", \"\");\n state.iframe.setAttribute(\"title\", \"Chat Widget\");\n state.iframe.setAttribute(\"role\", \"dialog\");\n state.iframe.setAttribute(\"aria-modal\", \"true\");\n state.iframe.setAttribute(\"allow\", \"clipboard-write; microphone\");\n state.iframe.src = opts.iframeSrc;\n Object.assign(state.iframe.style, {\n position: \"fixed\",\n bottom: \"96px\",\n right: \"24px\",\n width: \"420px\",\n height: \"600px\",\n maxWidth: \"calc(100dvw - 48px)\",\n maxHeight: \"calc(100dvh - 120px)\",\n border: \"none\",\n borderRadius: \"16px\",\n boxShadow: \"0 8px 32px rgba(0,0,0,0.15)\",\n zIndex: \"2147483647\",\n opacity: \"0\",\n transform: \"translateY(16px) scale(0.96)\",\n transition: \"opacity 0.2s, transform 0.25s\",\n pointerEvents: \"none\",\n background: \"#fff\",\n });\n document.body.appendChild(state.iframe);\n\n state.iframe.addEventListener(\"load\", function () {\n var savedTheme = \"auto\";\n try {\n var stored = localStorage.getItem(\"chat-theme\");\n if (stored === \"light\" || stored === \"dark\" || stored === \"auto\") savedTheme = stored;\n } catch { /* unavailable */ }\n state.iframe.contentWindow.postMessage(\n { type: \"chat-config\", title: opts.title, placeholder: opts.placeholder, theme: { mode: savedTheme } },\n \"*\",\n );\n });\n }\n\n function close() {\n if (!state.isOpen) return;\n toggle();\n }\n\n function toggle() {\n state.isOpen = !state.isOpen;\n var open = state.isOpen;\n\n state.button.setAttribute(\"aria-expanded\", String(open));\n state.iframe.style.opacity = open ? \"1\" : \"0\";\n state.iframe.style.transform = open ? \"translateY(0) scale(1)\" : \"translateY(16px) scale(0.96)\";\n state.iframe.style.pointerEvents = open ? \"auto\" : \"none\";\n\n state.overlay.style.opacity = open ? \"1\" : \"0\";\n state.overlay.style.pointerEvents = open ? \"auto\" : \"none\";\n\n state.button.style.transform = open ? \"scale(0)\" : \"\";\n state.button.style.opacity = open ? \"0\" : \"1\";\n state.button.style.pointerEvents = open ? \"none\" : \"auto\";\n\n document.body.style.overflow = open ? \"hidden\" : \"\";\n\n if (open) state.iframe.focus();\n }\n\n function handleMessage(event) {\n if (!state.iframe || event.source !== state.iframe.contentWindow) return;\n var data = event.data || {};\n if (data.type === \"chat-message\") {\n console.log(\"[Embed Chat] Message:\", data.text);\n }\n if (data.type === \"chat-close\") {\n close();\n }\n if (data.type === \"chat-viewport-config\") {\n var meta = document.querySelector('meta[name=\"viewport\"]');\n if (!meta) return;\n var current = meta.getAttribute(\"content\") || \"\";\n if (data.content) {\n if (state.originalViewport === undefined) state.originalViewport = current;\n if (!current.includes(data.content)) {\n meta.setAttribute(\"content\", current + (current ? \", \" : \"\") + data.content);\n }\n } else {\n meta.setAttribute(\"content\", state.originalViewport ?? current);\n state.originalViewport = undefined;\n }\n }\n }\n\n function initialize(opts) {\n if (document.querySelector(\"[data-embed-chat-btn]\")) return;\n\n state.opts = {};\n for (var key in DEFAULTS) {\n state.opts[key] = DEFAULTS[key];\n }\n if (opts) {\n for (var key in opts) {\n if (key === \"buttonStyle\" || key === \"overlayStyle\") {\n state.opts[key] = mergeStyles(state.opts[key] || {}, opts[key]);\n } else if (opts[key] !== undefined) {\n state.opts[key] = opts[key];\n }\n }\n }\n\n var style = document.createElement(\"style\");\n style.textContent =\n '@media (max-width: 799px) { [data-embed-chat-iframe] { width: 100dvw !important; height: 100dvh !important; bottom: 0 !important; right: 0 !important; max-width: none !important; max-height: none !important; border-radius: 0 !important; } [data-embed-chat-overlay] { display: none !important; } }';\n document.head.appendChild(style);\n\n createOverlay();\n createIframe();\n createButton();\n window.addEventListener(\"message\", handleMessage);\n }\n\n window.ChatSDK = { initialize: initialize };\n})();\n"],"mappings":"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","names":[]}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
useIframeBridge: () => useIframeBridge
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
|
|
27
|
+
// src/useIframeBridge.ts
|
|
28
|
+
var import_react = require("react");
|
|
29
|
+
function useIframeBridge() {
|
|
30
|
+
const [config, setConfig] = (0, import_react.useState)(null);
|
|
31
|
+
const notificationCbRef = (0, import_react.useRef)(null);
|
|
32
|
+
const isInIframe = typeof window !== "undefined" && window !== window.parent;
|
|
33
|
+
const notifyMessage = (0, import_react.useCallback)(
|
|
34
|
+
(text) => {
|
|
35
|
+
if (!isInIframe) return;
|
|
36
|
+
const msg = { type: "chat-message", text };
|
|
37
|
+
window.parent.postMessage(msg, "*");
|
|
38
|
+
},
|
|
39
|
+
[isInIframe]
|
|
40
|
+
);
|
|
41
|
+
const onNotificationClicked = (0, import_react.useCallback)((cb) => {
|
|
42
|
+
notificationCbRef.current = cb;
|
|
43
|
+
}, []);
|
|
44
|
+
(0, import_react.useEffect)(() => {
|
|
45
|
+
if (!isInIframe) return;
|
|
46
|
+
function handleMessage(event) {
|
|
47
|
+
const data = event.data;
|
|
48
|
+
if (!data || typeof data !== "object" || !data.type) return;
|
|
49
|
+
switch (data.type) {
|
|
50
|
+
case "chat-config": {
|
|
51
|
+
const configData = { ...data };
|
|
52
|
+
delete configData.type;
|
|
53
|
+
setConfig(configData);
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
case "chat-notification-clicked":
|
|
57
|
+
notificationCbRef.current?.();
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
window.addEventListener("message", handleMessage);
|
|
62
|
+
return () => window.removeEventListener("message", handleMessage);
|
|
63
|
+
}, [isInIframe]);
|
|
64
|
+
return { config, isInIframe, notifyMessage, onNotificationClicked };
|
|
65
|
+
}
|
|
66
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
67
|
+
0 && (module.exports = {
|
|
68
|
+
useIframeBridge
|
|
69
|
+
});
|
|
70
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/useIframeBridge.ts"],"sourcesContent":["export { useIframeBridge } from \"./useIframeBridge\";\nexport type { BridgeConfig, IframeBridgeHook, BridgeMessage } from \"./types\";\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { BridgeConfig, BridgeMessage, IframeBridgeHook } from \"./types\";\n\nexport function useIframeBridge(): IframeBridgeHook {\n const [config, setConfig] = useState<BridgeConfig | null>(null);\n const notificationCbRef = useRef<(() => void) | null>(null);\n\n const isInIframe = typeof window !== \"undefined\" && window !== window.parent;\n\n const notifyMessage = useCallback(\n (text: string) => {\n if (!isInIframe) return;\n const msg: BridgeMessage = { type: \"chat-message\", text };\n window.parent.postMessage(msg, \"*\");\n },\n [isInIframe],\n );\n\n const onNotificationClicked = useCallback((cb: () => void) => {\n notificationCbRef.current = cb;\n }, []);\n\n useEffect(() => {\n if (!isInIframe) return;\n\n function handleMessage(event: MessageEvent) {\n const data = event.data as Record<string, unknown>;\n if (!data || typeof data !== \"object\" || !data.type) return;\n\n switch (data.type) {\n case \"chat-config\": {\n const configData = { ...data };\n delete configData.type;\n setConfig(configData as BridgeConfig);\n break;\n }\n case \"chat-notification-clicked\":\n notificationCbRef.current?.();\n break;\n }\n }\n\n window.addEventListener(\"message\", handleMessage);\n return () => window.removeEventListener(\"message\", handleMessage);\n }, [isInIframe]);\n\n return { config, isInIframe, notifyMessage, onNotificationClicked };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAAyD;AAGlD,SAAS,kBAAoC;AAClD,QAAM,CAAC,QAAQ,SAAS,QAAI,uBAA8B,IAAI;AAC9D,QAAM,wBAAoB,qBAA4B,IAAI;AAE1D,QAAM,aAAa,OAAO,WAAW,eAAe,WAAW,OAAO;AAEtE,QAAM,oBAAgB;AAAA,IACpB,CAAC,SAAiB;AAChB,UAAI,CAAC,WAAY;AACjB,YAAM,MAAqB,EAAE,MAAM,gBAAgB,KAAK;AACxD,aAAO,OAAO,YAAY,KAAK,GAAG;AAAA,IACpC;AAAA,IACA,CAAC,UAAU;AAAA,EACb;AAEA,QAAM,4BAAwB,0BAAY,CAAC,OAAmB;AAC5D,sBAAkB,UAAU;AAAA,EAC9B,GAAG,CAAC,CAAC;AAEL,8BAAU,MAAM;AACd,QAAI,CAAC,WAAY;AAEjB,aAAS,cAAc,OAAqB;AAC1C,YAAM,OAAO,MAAM;AACnB,UAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,CAAC,KAAK,KAAM;AAErD,cAAQ,KAAK,MAAM;AAAA,QACjB,KAAK,eAAe;AAClB,gBAAM,aAAa,EAAE,GAAG,KAAK;AAC7B,iBAAO,WAAW;AAClB,oBAAU,UAA0B;AACpC;AAAA,QACF;AAAA,QACA,KAAK;AACH,4BAAkB,UAAU;AAC5B;AAAA,MACJ;AAAA,IACF;AAEA,WAAO,iBAAiB,WAAW,aAAa;AAChD,WAAO,MAAM,OAAO,oBAAoB,WAAW,aAAa;AAAA,EAClE,GAAG,CAAC,UAAU,CAAC;AAEf,SAAO,EAAE,QAAQ,YAAY,eAAe,sBAAsB;AACpE;","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
interface BridgeConfig {
|
|
2
|
+
locale?: string;
|
|
3
|
+
title?: string;
|
|
4
|
+
placeholder?: string;
|
|
5
|
+
theme?: {
|
|
6
|
+
mode?: "auto" | "light" | "dark";
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
interface IframeBridgeHook {
|
|
10
|
+
config: BridgeConfig | null;
|
|
11
|
+
isInIframe: boolean;
|
|
12
|
+
notifyMessage: (text: string) => void;
|
|
13
|
+
onNotificationClicked: (cb: () => void) => void;
|
|
14
|
+
}
|
|
15
|
+
interface BridgeMessage {
|
|
16
|
+
type: "chat-config" | "chat-message" | "chat-notification-clicked" | "chat-close";
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
declare function useIframeBridge(): IframeBridgeHook;
|
|
21
|
+
|
|
22
|
+
export { type BridgeConfig, type BridgeMessage, type IframeBridgeHook, useIframeBridge };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
interface BridgeConfig {
|
|
2
|
+
locale?: string;
|
|
3
|
+
title?: string;
|
|
4
|
+
placeholder?: string;
|
|
5
|
+
theme?: {
|
|
6
|
+
mode?: "auto" | "light" | "dark";
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
interface IframeBridgeHook {
|
|
10
|
+
config: BridgeConfig | null;
|
|
11
|
+
isInIframe: boolean;
|
|
12
|
+
notifyMessage: (text: string) => void;
|
|
13
|
+
onNotificationClicked: (cb: () => void) => void;
|
|
14
|
+
}
|
|
15
|
+
interface BridgeMessage {
|
|
16
|
+
type: "chat-config" | "chat-message" | "chat-notification-clicked" | "chat-close";
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
declare function useIframeBridge(): IframeBridgeHook;
|
|
21
|
+
|
|
22
|
+
export { type BridgeConfig, type BridgeMessage, type IframeBridgeHook, useIframeBridge };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// src/useIframeBridge.ts
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
function useIframeBridge() {
|
|
4
|
+
const [config, setConfig] = useState(null);
|
|
5
|
+
const notificationCbRef = useRef(null);
|
|
6
|
+
const isInIframe = typeof window !== "undefined" && window !== window.parent;
|
|
7
|
+
const notifyMessage = useCallback(
|
|
8
|
+
(text) => {
|
|
9
|
+
if (!isInIframe) return;
|
|
10
|
+
const msg = { type: "chat-message", text };
|
|
11
|
+
window.parent.postMessage(msg, "*");
|
|
12
|
+
},
|
|
13
|
+
[isInIframe]
|
|
14
|
+
);
|
|
15
|
+
const onNotificationClicked = useCallback((cb) => {
|
|
16
|
+
notificationCbRef.current = cb;
|
|
17
|
+
}, []);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (!isInIframe) return;
|
|
20
|
+
function handleMessage(event) {
|
|
21
|
+
const data = event.data;
|
|
22
|
+
if (!data || typeof data !== "object" || !data.type) return;
|
|
23
|
+
switch (data.type) {
|
|
24
|
+
case "chat-config": {
|
|
25
|
+
const configData = { ...data };
|
|
26
|
+
delete configData.type;
|
|
27
|
+
setConfig(configData);
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
case "chat-notification-clicked":
|
|
31
|
+
notificationCbRef.current?.();
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
window.addEventListener("message", handleMessage);
|
|
36
|
+
return () => window.removeEventListener("message", handleMessage);
|
|
37
|
+
}, [isInIframe]);
|
|
38
|
+
return { config, isInIframe, notifyMessage, onNotificationClicked };
|
|
39
|
+
}
|
|
40
|
+
export {
|
|
41
|
+
useIframeBridge
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/useIframeBridge.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { BridgeConfig, BridgeMessage, IframeBridgeHook } from \"./types\";\n\nexport function useIframeBridge(): IframeBridgeHook {\n const [config, setConfig] = useState<BridgeConfig | null>(null);\n const notificationCbRef = useRef<(() => void) | null>(null);\n\n const isInIframe = typeof window !== \"undefined\" && window !== window.parent;\n\n const notifyMessage = useCallback(\n (text: string) => {\n if (!isInIframe) return;\n const msg: BridgeMessage = { type: \"chat-message\", text };\n window.parent.postMessage(msg, \"*\");\n },\n [isInIframe],\n );\n\n const onNotificationClicked = useCallback((cb: () => void) => {\n notificationCbRef.current = cb;\n }, []);\n\n useEffect(() => {\n if (!isInIframe) return;\n\n function handleMessage(event: MessageEvent) {\n const data = event.data as Record<string, unknown>;\n if (!data || typeof data !== \"object\" || !data.type) return;\n\n switch (data.type) {\n case \"chat-config\": {\n const configData = { ...data };\n delete configData.type;\n setConfig(configData as BridgeConfig);\n break;\n }\n case \"chat-notification-clicked\":\n notificationCbRef.current?.();\n break;\n }\n }\n\n window.addEventListener(\"message\", handleMessage);\n return () => window.removeEventListener(\"message\", handleMessage);\n }, [isInIframe]);\n\n return { config, isInIframe, notifyMessage, onNotificationClicked };\n}\n"],"mappings":";AAAA,SAAS,aAAa,WAAW,QAAQ,gBAAgB;AAGlD,SAAS,kBAAoC;AAClD,QAAM,CAAC,QAAQ,SAAS,IAAI,SAA8B,IAAI;AAC9D,QAAM,oBAAoB,OAA4B,IAAI;AAE1D,QAAM,aAAa,OAAO,WAAW,eAAe,WAAW,OAAO;AAEtE,QAAM,gBAAgB;AAAA,IACpB,CAAC,SAAiB;AAChB,UAAI,CAAC,WAAY;AACjB,YAAM,MAAqB,EAAE,MAAM,gBAAgB,KAAK;AACxD,aAAO,OAAO,YAAY,KAAK,GAAG;AAAA,IACpC;AAAA,IACA,CAAC,UAAU;AAAA,EACb;AAEA,QAAM,wBAAwB,YAAY,CAAC,OAAmB;AAC5D,sBAAkB,UAAU;AAAA,EAC9B,GAAG,CAAC,CAAC;AAEL,YAAU,MAAM;AACd,QAAI,CAAC,WAAY;AAEjB,aAAS,cAAc,OAAqB;AAC1C,YAAM,OAAO,MAAM;AACnB,UAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,CAAC,KAAK,KAAM;AAErD,cAAQ,KAAK,MAAM;AAAA,QACjB,KAAK,eAAe;AAClB,gBAAM,aAAa,EAAE,GAAG,KAAK;AAC7B,iBAAO,WAAW;AAClB,oBAAU,UAA0B;AACpC;AAAA,QACF;AAAA,QACA,KAAK;AACH,4BAAkB,UAAU;AAC5B;AAAA,MACJ;AAAA,IACF;AAEA,WAAO,iBAAiB,WAAW,aAAa;AAChD,WAAO,MAAM,OAAO,oBAAoB,WAAW,aAAa;AAAA,EAClE,GAAG,CAAC,UAAU,CAAC;AAEf,SAAO,EAAE,QAAQ,YAAY,eAAe,sBAAsB;AACpE;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bootdesk/chat-widget-bridge",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Iframe bridge for BootDesk Chat SDK - enables embedding the chat widget in an iframe with cross-frame communication via postMessage",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./embed-chat": {
|
|
16
|
+
"import": "./dist/embed-chat.js",
|
|
17
|
+
"require": "./dist/embed-chat.cjs"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": ["dist"],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"lint": "eslint src",
|
|
25
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
26
|
+
"format:check": "prettier --check \"src/**/*.ts\"",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"docs": "typedoc --out ../../docs/_build/js/bridge src"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@eslint/js": "^10.0.1",
|
|
35
|
+
"@testing-library/react": "^16.0.0",
|
|
36
|
+
"@testing-library/jest-dom": "^6.0.0",
|
|
37
|
+
"@types/react": "^18.0.0",
|
|
38
|
+
"eslint": "^10.4.0",
|
|
39
|
+
"eslint-config-prettier": "^10.1.8",
|
|
40
|
+
"jsdom": "^25.0.0",
|
|
41
|
+
"prettier": "^3.8.3",
|
|
42
|
+
"react": "^18.0.0",
|
|
43
|
+
"react-dom": "^18.0.0",
|
|
44
|
+
"tsup": "^8.0.0",
|
|
45
|
+
"typescript": "^5.0.0",
|
|
46
|
+
"typescript-eslint": "^8.59.4",
|
|
47
|
+
"vitest": "^1.0.0"
|
|
48
|
+
}
|
|
49
|
+
}
|