copilot-chat-widget 0.1.16 → 0.1.17

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 CHANGED
@@ -1,80 +1,63 @@
1
- # Copilot Chat Widget
2
-
3
- Embeddable chat widget ready for CDN drop-in or npm import.
4
-
5
- ## Install
6
- ```bash
7
- npm install copilot-chat-widget
8
- # or
9
- yarn add copilot-chat-widget
10
- ```
11
-
12
- ## Quick start (npm / bundler)
13
- ```js
14
- import { loadCopilotChatWidget } from "copilot-chat-widget";
15
-
16
- loadCopilotChatWidget({
17
- token: "YOUR_WIDGET_TOKEN",
18
- // optional when backend is another origin:
19
- // baseUrl: "https://your-backend-domain"
20
- });
21
- ```
22
- `loadCopilotChatWidget` injects the standalone bundle (`chat-widget.min.js`) and boots the widget with the given token.
23
-
24
- ### React/Next.js
25
- ```jsx
26
- import { useEffect } from "react";
27
- import { loadCopilotChatWidget } from "copilot-chat-widget";
28
-
29
- export default function Page() {
30
- useEffect(() => {
31
- loadCopilotChatWidget({
32
- token: process.env.NEXT_PUBLIC_CHAT_WIDGET_TOKEN,
33
- baseUrl: process.env.NEXT_PUBLIC_WIDGET_BASE_URL // optional override
34
- });
35
- }, []);
36
-
37
- return <main>Your page content</main>;
38
- }
39
- ```
40
-
41
- ## Quick start (script tag)
42
- ```html
43
- <script
44
- src="https://unpkg.com/copilot-chat-widget/dist/chat-widget.min.js"
45
- data-token="YOUR_WIDGET_TOKEN"
46
- data-base-url="https://your-backend-domain" <!-- optional if backend is another origin -->
47
- ></script>
48
- ```
49
- - `data-token`: required.
50
- - `data-base-url`: optional override; if omitted, the widget uses the build-time `WIDGET_BASE_URL`, then falls back to the script/page origin.
51
- - `data-autoload="false"`: insert script but call `window.CopilotChat.init()` yourself.
52
-
53
- ## Configuration
54
- - `token` (string, required): widget token.
55
- - `baseUrl` (string): backend host serving `/api/chat-widget/config`. Needed when the embedding page is on a different origin. Fallback order: `baseUrl` option/data attribute `WIDGET_BASE_URL` (build-time) script/page origin.
56
- - `autoload` (boolean): set `false` to defer bootstrap and call `window.CopilotChat.load()` manually.
57
-
58
- Runtime controls:
59
- ```js
60
- window.CopilotChat.open(); // open widget
61
- window.CopilotChat.close(); // close widget
62
- window.CopilotChat.load(); // initialize if autoload=false
63
- ```
64
-
65
- ### Token resolution order
66
- The widget looks for a token in this order:
67
- 1. `token` passed to `loadCopilotChatWidget`
68
- 2. `window.CopilotChatConfig.token` or `data-token` on the script tag
69
- 3. `token` query param on the script URL
70
- 4. `localStorage.copilotChatToken` (if previously provided)
71
- 5. Prompt (browser `window.prompt`) if none found, then store in `localStorage` for reuse
72
-
73
- ## Build & publish (for maintainers)
74
- ```bash
75
- cd webui/widget
76
- npm install
77
- WIDGET_BASE_URL=https://your-backend-domain npm run build # embed default baseUrl into bundle
78
- npm publish --access public
79
- ```
80
- To use a different package name, update `name` in `webui/widget/package.json` before publishing.
1
+ # Copilot Chat Widget
2
+
3
+ Embeddable chat widget that can be added via NPM or a script tag.
4
+
5
+ ## Installation
6
+ ```bash
7
+ npm install copilot-chat-widget
8
+ # or
9
+ yarn add copilot-chat-widget
10
+ ```
11
+
12
+ ## Usage (NPM import)
13
+ ```js
14
+ import { loadCopilotChatWidget } from "copilot-chat-widget";
15
+
16
+ loadCopilotChatWidget({
17
+ token: "YOUR_WIDGET_TOKEN",
18
+ // optional: baseUrl if backend is on a different origin
19
+ // baseUrl: "https://your-backend-domain"
20
+ });
21
+ ```
22
+ `loadCopilotChatWidget` injects the standalone script (`chat-widget.min.js`) and bootstraps the widget using the provided `token`.
23
+
24
+ ### React/Next.js example
25
+ ```jsx
26
+ import { useEffect } from "react";
27
+ import { loadCopilotChatWidget } from "copilot-chat-widget";
28
+
29
+ export default function Page() {
30
+ useEffect(() => {
31
+ loadCopilotChatWidget({
32
+ token: process.env.NEXT_PUBLIC_CHAT_WIDGET_TOKEN,
33
+ baseUrl: process.env.NEXT_PUBLIC_WIDGET_BASE_URL // optional override if backend is another origin
34
+ });
35
+ }, []);
36
+
37
+ return <main>Your page content</main>;
38
+ }
39
+ ```
40
+
41
+ ## Usage (script tag)
42
+ ```html
43
+ <script
44
+ src="https://cdn.example.com/chat-widget.min.js"
45
+ data-token="YOUR_WIDGET_TOKEN"
46
+ data-base-url="https://your-backend-domain" <!-- optional if backend is another origin -->
47
+ ></script>
48
+ ```
49
+ - `data-token`: required.
50
+ - `data-base-url`: optional override; if omitted, the widget uses the build-time default `WIDGET_BASE_URL`, then falls back to the script/page origin.
51
+ - `data-autoload="false"`: if you want to insert the script but call `window.CopilotChat.init()` manually.
52
+
53
+ ## Configuration
54
+ - `token` (string, required): widget token.
55
+ - `baseUrl` (string): backend host serving `/api/chat-widget/config`. Needed when the embedding page is on a different origin. If omitted, the widget uses `WIDGET_BASE_URL` (injected at build) then falls back to the script/page origin.
56
+ - `autoload` (boolean): set `false` to defer bootstrap and call `window.CopilotChat.load()` yourself.
57
+
58
+ Runtime controls (after script loads):
59
+ ```js
60
+ window.CopilotChat.open(); // open widget
61
+ window.CopilotChat.close(); // close widget
62
+ window.CopilotChat.load(); // initialize if autoload=false
63
+ ```
package/dist/index.cjs CHANGED
@@ -1 +1,7 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});var n=typeof document<"u"?document.currentScript:null;const r="http://localhost:3000";function l(d={}){if(typeof window>"u"||typeof document>"u")return null;const{token:a,autoload:i,baseUrl:t=r}=d;window.CopilotChatConfig={...window.CopilotChatConfig,...d,baseUrl:t||window.CopilotChatConfig?.baseUrl||void 0};const o=document.getElementById("copilot-chat-widget-loader");if(o)return a&&(o.dataset.token=a),t&&(o.dataset.baseUrl=t),i===!1&&(o.dataset.autoload="false"),o;const e=document.createElement("script");return e.id="copilot-chat-widget-loader",e.src=new URL("./chat-widget.min.js",typeof document>"u"?require("url").pathToFileURL(__filename).href:n&&n.tagName.toUpperCase()==="SCRIPT"&&n.src||new URL("index.cjs",document.baseURI).href).href,e.async=!0,a&&(e.dataset.token=a),t&&(e.dataset.baseUrl=t),i===!1&&(e.dataset.autoload="false"),document.body.appendChild(e),e}exports.default=l;exports.loadCopilotChatWidget=l;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const k="http://localhost:3000",w=64,C=720,E=({iframeUrl:h,launcherIcon:u})=>{const d=document.querySelector("[data-copilot-widget-root]");d&&d.remove();const g=s=>{const o=document.querySelector("[data-copilot-checkout-toast]");o&&o.remove();const r=document.createElement("div");r.setAttribute("data-copilot-checkout-toast","true"),r.innerText=s,Object.assign(r.style,{position:"fixed",top:"20px",left:"50%",transform:"translateX(-50%)",padding:"12px 16px",background:"#0f172a",color:"white",borderRadius:"12px",boxShadow:"0 8px 24px rgba(0,0,0,0.25)",zIndex:2147483647,fontSize:"14px",fontWeight:"600",maxWidth:"420px",lineHeight:"1.4",textAlign:"center"}),document.body.appendChild(r),setTimeout(()=>{r.remove()},4500)},c=document.createElement("div");c.setAttribute("data-copilot-widget-root","true");const n=c.attachShadow({mode:"open"});document.body.appendChild(c);const t=document.createElement("button");t.type="button",t.setAttribute("aria-label","Open Copilot chat"),t.innerHTML=`
2
+ <img
3
+ src="${u}"
4
+ alt="Copilot chat launcher"
5
+ style="width: 38px; height: 38px; object-fit: contain; border-radius: 50%; pointer-events: none;"
6
+ />
7
+ `,Object.assign(t.style,{position:"fixed",bottom:"24px",right:"24px",width:`${w}px`,height:`${w}px`,borderRadius:"50%",border:"none",background:"linear-gradient(135deg, #0078ff, #00c6ff)",color:"white",cursor:"pointer",zIndex:999998,display:"flex",alignItems:"center",justifyContent:"center",boxShadow:"0 6px 14px rgba(0,0,0,0.25)",transition:"all 0.25s ease"}),t.onmouseover=()=>{t.style.transform="scale(1.12)",t.style.boxShadow="0 10px 25px rgba(0,0,0,0.3)"},t.onmouseout=()=>{t.style.transform="scale(1)",t.style.boxShadow="0 6px 14px rgba(0,0,0,0.25)"};const e=document.createElement("div");e.setAttribute("data-copilot-widget-root","true"),Object.assign(e.style,{display:"none",position:"fixed",bottom:`${w+36}px`,right:"24px",zIndex:"999999",transformOrigin:"bottom right",transform:"scale(0.8) translateY(20px)",opacity:"0",transition:"all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55)"});const l=document.createElement("div"),f=document.createElement("div"),x=document.createElement("div");Object.assign(l.style,{position:"absolute",bottom:"-14px",right:"28px",width:"30px",height:"20px",pointerEvents:"none",display:"none",zIndex:"1"}),Object.assign(f.style,{position:"absolute",bottom:"0",left:"0",right:"0",margin:"0 auto",width:"0",height:"0",borderLeft:"15px solid transparent",borderRight:"15px solid transparent",borderTop:"15px solid rgba(15,23,42,0.1)"}),Object.assign(x.style,{position:"absolute",bottom:"2px",left:"0",right:"0",margin:"0 auto",width:"0",height:"0",borderLeft:"13px solid transparent",borderRight:"13px solid transparent",borderTop:"13px solid white",boxShadow:"0 6px 16px rgba(15,23,42,0.12)",borderRadius:"2px"}),l.appendChild(f),l.appendChild(x);const p=document.createElement("div");Object.assign(p.style,{width:`${C}px`,maxWidth:"calc(100vw - 48px)",height:`${Math.min(C,Math.max(320,window.innerHeight-140))}px`,maxHeight:"calc(100vh - 150px)",borderRadius:"20px",background:"white",border:"1px solid rgba(15,23,42,0.12)",boxShadow:"0 18px 45px rgba(15,23,42,0.16)",overflow:"hidden"}),n.appendChild(p);const i=document.createElement("iframe");i.src=h,i.title="Copilot chat widget",i.allow="clipboard-read; clipboard-write; microphone; camera; display-capture",i.setAttribute("scrolling","no"),Object.assign(i.style,{width:"100%",height:"100%",border:"none",display:"block",background:"transparent",overflow:"hidden"}),n.appendChild(p),p.appendChild(i),e.appendChild(l),e.appendChild(p),document.body.appendChild(t),document.body.appendChild(e);let a=!1;const m=()=>{a&&(a=!1,e.style.opacity="0",e.style.transform="scale(0.8) translateY(20px)",l.style.display="none",setTimeout(()=>{e.style.display="none"},250),t.style.transform="scale(1)")},v=s=>{const{data:o,source:r}=s||{};if(o?.type){if(o.type==="CART_CHECKOUT"&&r===i.contentWindow){console.log("[CopilotChat] Received checkout payload from widget:",o),g("Checkout message received from chat widget. Check console for payload.");return}if(o.type==="WIDGET_READY"&&r===i.contentWindow){i.contentWindow.postMessage({type:"INIT_WIDGET"},"*");return}o.type==="CHAT_CLOSED"&&m()}};window.addEventListener("message",v),t.onclick=s=>{s.stopPropagation(),a=!a,a?(e.style.display="block",l.style.display="block",requestAnimationFrame(()=>{e.style.opacity="1",e.style.transform="scale(1) translateY(0)"})):m()},document.addEventListener("click",s=>{const o=s.target;o&&a&&!e.contains(o)&&o!==t&&m()});const b={close:m,open:()=>{a||t.click()}};return window.CopilotChat=window.CopilotChat||{},window.CopilotChat.close=b.close,window.CopilotChat.open=b.open,window.CopilotChat.controls=b,b};async function y(h={}){if(typeof window>"u"||typeof document>"u")return null;const u=window.CopilotChatConfig||{},d=h.token||u.token||typeof window<"u"&&window.localStorage?.getItem("copilotChatToken")||null;if(!d)return console.error("[CopilotChat] Missing token (provide via loadCopilotChatWidget({ token }) or window.CopilotChatConfig.token)."),null;const g=h.baseUrl||u.baseUrl||k,c=`${g.replace(/\/$/,"")}/api/chat-widget/config?token=${encodeURIComponent(d)}`;try{const n=await fetch(c,{credentials:"omit",mode:"cors"});if(!n.ok)throw new Error(`Server responded with ${n.status}`);const t=await n.json();if(!t?.iframeUrl||!t?.launcherIcon)throw new Error("Received incomplete widget configuration from server.");return window.CopilotChat=window.CopilotChat||{},window.CopilotChat.token=d,window.CopilotChat.baseUrl=g,E({iframeUrl:t.iframeUrl,launcherIcon:t.launcherIcon})}catch(n){return console.error("[CopilotChat] Error during widget bootstrap:",n instanceof Error?n.message:n),null}}exports.default=y;exports.loadCopilotChatWidget=y;
package/dist/index.mjs CHANGED
@@ -1,20 +1,199 @@
1
- const n = "http://localhost:3000";
2
- function l(i = {}) {
1
+ const C = "http://localhost:3000";
2
+ const y = ({ iframeUrl: h, launcherIcon: u }) => {
3
+ const d = document.querySelector("[data-copilot-widget-root]");
4
+ d && d.remove();
5
+ const g = (s) => {
6
+ const o = document.querySelector("[data-copilot-checkout-toast]");
7
+ o && o.remove();
8
+ const r = document.createElement("div");
9
+ r.setAttribute("data-copilot-checkout-toast", "true"), r.innerText = s, Object.assign(r.style, {
10
+ position: "fixed",
11
+ top: "20px",
12
+ left: "50%",
13
+ transform: "translateX(-50%)",
14
+ padding: "12px 16px",
15
+ background: "#0f172a",
16
+ color: "white",
17
+ borderRadius: "12px",
18
+ boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
19
+ zIndex: 2147483647,
20
+ fontSize: "14px",
21
+ fontWeight: "600",
22
+ maxWidth: "420px",
23
+ lineHeight: "1.4",
24
+ textAlign: "center"
25
+ }), document.body.appendChild(r), setTimeout(() => {
26
+ r.remove();
27
+ }, 4500);
28
+ }, c = document.createElement("div");
29
+ c.setAttribute("data-copilot-widget-root", "true");
30
+ const n = c.attachShadow({ mode: "open" });
31
+ document.body.appendChild(c);
32
+ const t = document.createElement("button");
33
+ t.type = "button", t.setAttribute("aria-label", "Open Copilot chat"), t.innerHTML = `
34
+ <img
35
+ src="${u}"
36
+ alt="Copilot chat launcher"
37
+ style="width: 38px; height: 38px; object-fit: contain; border-radius: 50%; pointer-events: none;"
38
+ />
39
+ `, Object.assign(t.style, {
40
+ position: "fixed",
41
+ bottom: "24px",
42
+ right: "24px",
43
+ width: "64px",
44
+ height: "64px",
45
+ borderRadius: "50%",
46
+ border: "none",
47
+ background: "linear-gradient(135deg, #0078ff, #00c6ff)",
48
+ color: "white",
49
+ cursor: "pointer",
50
+ zIndex: 999998,
51
+ display: "flex",
52
+ alignItems: "center",
53
+ justifyContent: "center",
54
+ boxShadow: "0 6px 14px rgba(0,0,0,0.25)",
55
+ transition: "all 0.25s ease"
56
+ }), t.onmouseover = () => {
57
+ t.style.transform = "scale(1.12)", t.style.boxShadow = "0 10px 25px rgba(0,0,0,0.3)";
58
+ }, t.onmouseout = () => {
59
+ t.style.transform = "scale(1)", t.style.boxShadow = "0 6px 14px rgba(0,0,0,0.25)";
60
+ };
61
+ const e = document.createElement("div");
62
+ e.setAttribute("data-copilot-widget-root", "true"), Object.assign(e.style, {
63
+ display: "none",
64
+ position: "fixed",
65
+ bottom: "100px",
66
+ right: "24px",
67
+ zIndex: "999999",
68
+ transformOrigin: "bottom right",
69
+ transform: "scale(0.8) translateY(20px)",
70
+ opacity: "0",
71
+ transition: "all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55)"
72
+ });
73
+ const l = document.createElement("div"), b = document.createElement("div"), f = document.createElement("div");
74
+ Object.assign(l.style, {
75
+ position: "absolute",
76
+ bottom: "-14px",
77
+ right: "28px",
78
+ width: "30px",
79
+ height: "20px",
80
+ pointerEvents: "none",
81
+ display: "none",
82
+ zIndex: "1"
83
+ }), Object.assign(b.style, {
84
+ position: "absolute",
85
+ bottom: "0",
86
+ left: "0",
87
+ right: "0",
88
+ margin: "0 auto",
89
+ width: "0",
90
+ height: "0",
91
+ borderLeft: "15px solid transparent",
92
+ borderRight: "15px solid transparent",
93
+ borderTop: "15px solid rgba(15,23,42,0.1)"
94
+ }), Object.assign(f.style, {
95
+ position: "absolute",
96
+ bottom: "2px",
97
+ left: "0",
98
+ right: "0",
99
+ margin: "0 auto",
100
+ width: "0",
101
+ height: "0",
102
+ borderLeft: "13px solid transparent",
103
+ borderRight: "13px solid transparent",
104
+ borderTop: "13px solid white",
105
+ boxShadow: "0 6px 16px rgba(15,23,42,0.12)",
106
+ borderRadius: "2px"
107
+ }), l.appendChild(b), l.appendChild(f);
108
+ const p = document.createElement("div");
109
+ Object.assign(p.style, {
110
+ width: "720px",
111
+ maxWidth: "calc(100vw - 48px)",
112
+ height: `${Math.min(720, Math.max(320, window.innerHeight - 140))}px`,
113
+ maxHeight: "calc(100vh - 150px)",
114
+ borderRadius: "20px",
115
+ background: "white",
116
+ border: "1px solid rgba(15,23,42,0.12)",
117
+ boxShadow: "0 18px 45px rgba(15,23,42,0.16)",
118
+ overflow: "hidden"
119
+ }), n.appendChild(p);
120
+ const i = document.createElement("iframe");
121
+ i.src = h, i.title = "Copilot chat widget", i.allow = "clipboard-read; clipboard-write; microphone; camera; display-capture", i.setAttribute("scrolling", "no"), Object.assign(i.style, {
122
+ width: "100%",
123
+ height: "100%",
124
+ border: "none",
125
+ display: "block",
126
+ background: "transparent",
127
+ overflow: "hidden"
128
+ }), n.appendChild(p), p.appendChild(i), e.appendChild(l), e.appendChild(p), document.body.appendChild(t), document.body.appendChild(e);
129
+ let a = !1;
130
+ const m = () => {
131
+ a && (a = !1, e.style.opacity = "0", e.style.transform = "scale(0.8) translateY(20px)", l.style.display = "none", setTimeout(() => {
132
+ e.style.display = "none";
133
+ }, 250), t.style.transform = "scale(1)");
134
+ }, x = (s) => {
135
+ const { data: o, source: r } = s || {};
136
+ if (o?.type) {
137
+ if (o.type === "CART_CHECKOUT" && r === i.contentWindow) {
138
+ console.log("[CopilotChat] Received checkout payload from widget:", o), g("Checkout message received from chat widget. Check console for payload.");
139
+ return;
140
+ }
141
+ if (o.type === "WIDGET_READY" && r === i.contentWindow) {
142
+ i.contentWindow.postMessage({ type: "INIT_WIDGET" }, "*");
143
+ return;
144
+ }
145
+ o.type === "CHAT_CLOSED" && m();
146
+ }
147
+ };
148
+ window.addEventListener("message", x), t.onclick = (s) => {
149
+ s.stopPropagation(), a = !a, a ? (e.style.display = "block", l.style.display = "block", requestAnimationFrame(() => {
150
+ e.style.opacity = "1", e.style.transform = "scale(1) translateY(0)";
151
+ })) : m();
152
+ }, document.addEventListener("click", (s) => {
153
+ const o = s.target;
154
+ o && a && !e.contains(o) && o !== t && m();
155
+ });
156
+ const w = {
157
+ close: m,
158
+ open: () => {
159
+ a || t.click();
160
+ }
161
+ };
162
+ return window.CopilotChat = window.CopilotChat || {}, window.CopilotChat.close = w.close, window.CopilotChat.open = w.open, window.CopilotChat.controls = w, w;
163
+ };
164
+ async function E(h = {}) {
3
165
  if (typeof window > "u" || typeof document > "u")
4
166
  return null;
5
- const { token: a, autoload: d, baseUrl: e = n } = i;
6
- window.CopilotChatConfig = {
7
- ...window.CopilotChatConfig,
8
- ...i,
9
- baseUrl: e || window.CopilotChatConfig?.baseUrl || void 0
10
- };
11
- const o = document.getElementById("copilot-chat-widget-loader");
12
- if (o)
13
- return a && (o.dataset.token = a), e && (o.dataset.baseUrl = e), d === !1 && (o.dataset.autoload = "false"), o;
14
- const t = document.createElement("script");
15
- return t.id = "copilot-chat-widget-loader", t.src = new URL("./chat-widget.min.js", import.meta.url).href, t.async = !0, a && (t.dataset.token = a), e && (t.dataset.baseUrl = e), d === !1 && (t.dataset.autoload = "false"), document.body.appendChild(t), t;
167
+ const u = window.CopilotChatConfig || {}, d = h.token || u.token || typeof window < "u" && window.localStorage?.getItem("copilotChatToken") || null;
168
+ if (!d)
169
+ return console.error(
170
+ "[CopilotChat] Missing token (provide via loadCopilotChatWidget({ token }) or window.CopilotChatConfig.token)."
171
+ ), null;
172
+ const g = h.baseUrl || u.baseUrl || C, c = `${g.replace(/\/$/, "")}/api/chat-widget/config?token=${encodeURIComponent(
173
+ d
174
+ )}`;
175
+ try {
176
+ const n = await fetch(c, {
177
+ credentials: "omit",
178
+ mode: "cors"
179
+ });
180
+ if (!n.ok)
181
+ throw new Error(`Server responded with ${n.status}`);
182
+ const t = await n.json();
183
+ if (!t?.iframeUrl || !t?.launcherIcon)
184
+ throw new Error("Received incomplete widget configuration from server.");
185
+ return window.CopilotChat = window.CopilotChat || {}, window.CopilotChat.token = d, window.CopilotChat.baseUrl = g, y({
186
+ iframeUrl: t.iframeUrl,
187
+ launcherIcon: t.launcherIcon
188
+ });
189
+ } catch (n) {
190
+ return console.error(
191
+ "[CopilotChat] Error during widget bootstrap:",
192
+ n instanceof Error ? n.message : n
193
+ ), null;
194
+ }
16
195
  }
17
196
  export {
18
- l as default,
19
- l as loadCopilotChatWidget
197
+ E as default,
198
+ E as loadCopilotChatWidget
20
199
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "copilot-chat-widget",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "Embeddable Copilot chat widget that can be loaded via NPM or a script tag.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
package/src/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- export interface CopilotChatOptions {
2
- token: string;
3
- autoload?: boolean;
4
- baseUrl?: string;
5
- }
6
-
7
- export function loadCopilotChatWidget(options: CopilotChatOptions): HTMLScriptElement | null;
8
- export default loadCopilotChatWidget;
1
+ export interface CopilotChatOptions {
2
+ token: string;
3
+ autoload?: boolean;
4
+ baseUrl?: string;
5
+ }
6
+
7
+ export function loadCopilotChatWidget(options: CopilotChatOptions): unknown;
8
+ export default loadCopilotChatWidget;
package/src/index.js CHANGED
@@ -3,38 +3,336 @@ const BUILD_BASE_URL =
3
3
  ? __WIDGET_BASE_URL__
4
4
  : undefined;
5
5
 
6
- export function loadCopilotChatWidget(options = {}) {
6
+ const BUTTON_SIZE = 64;
7
+ const IFRAME_SIZE = 720;
8
+
9
+ const buildWidget = ({ iframeUrl, launcherIcon }) => {
10
+ const existingRoot = document.querySelector("[data-copilot-widget-root]");
11
+ if (existingRoot) {
12
+ existingRoot.remove();
13
+ }
14
+
15
+ const showCheckoutToast = (message) => {
16
+ const existingToast = document.querySelector("[data-copilot-checkout-toast]");
17
+ if (existingToast) {
18
+ existingToast.remove();
19
+ }
20
+
21
+ const toast = document.createElement("div");
22
+ toast.setAttribute("data-copilot-checkout-toast", "true");
23
+ toast.innerText = message;
24
+
25
+ Object.assign(toast.style, {
26
+ position: "fixed",
27
+ top: "20px",
28
+ left: "50%",
29
+ transform: "translateX(-50%)",
30
+ padding: "12px 16px",
31
+ background: "#0f172a",
32
+ color: "white",
33
+ borderRadius: "12px",
34
+ boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
35
+ zIndex: 2147483647,
36
+ fontSize: "14px",
37
+ fontWeight: "600",
38
+ maxWidth: "420px",
39
+ lineHeight: "1.4",
40
+ textAlign: "center",
41
+ });
42
+
43
+ document.body.appendChild(toast);
44
+
45
+ setTimeout(() => {
46
+ toast.remove();
47
+ }, 4500);
48
+ };
49
+
50
+ // Shadow DOM container
51
+ const containerHost = document.createElement("div");
52
+ containerHost.setAttribute("data-copilot-widget-root", "true");
53
+ const shadow = containerHost.attachShadow({ mode: "open" });
54
+ document.body.appendChild(containerHost);
55
+
56
+ // Button
57
+ const btn = document.createElement("button");
58
+ btn.type = "button";
59
+ btn.setAttribute("aria-label", "Open Copilot chat");
60
+ btn.innerHTML = `
61
+ <img
62
+ src="${launcherIcon}"
63
+ alt="Copilot chat launcher"
64
+ style="width: 38px; height: 38px; object-fit: contain; border-radius: 50%; pointer-events: none;"
65
+ />
66
+ `;
67
+
68
+ Object.assign(btn.style, {
69
+ position: "fixed",
70
+ bottom: "24px",
71
+ right: "24px",
72
+ width: `${BUTTON_SIZE}px`,
73
+ height: `${BUTTON_SIZE}px`,
74
+ borderRadius: "50%",
75
+ border: "none",
76
+ background: "linear-gradient(135deg, #0078ff, #00c6ff)",
77
+ color: "white",
78
+ cursor: "pointer",
79
+ zIndex: 999998,
80
+ display: "flex",
81
+ alignItems: "center",
82
+ justifyContent: "center",
83
+ boxShadow: "0 6px 14px rgba(0,0,0,0.25)",
84
+ transition: "all 0.25s ease",
85
+ });
86
+
87
+ btn.onmouseover = () => {
88
+ btn.style.transform = "scale(1.12)";
89
+ btn.style.boxShadow = "0 10px 25px rgba(0,0,0,0.3)";
90
+ };
91
+ btn.onmouseout = () => {
92
+ btn.style.transform = "scale(1)";
93
+ btn.style.boxShadow = "0 6px 14px rgba(0,0,0,0.25)";
94
+ };
95
+
96
+ const container = document.createElement("div");
97
+ container.setAttribute("data-copilot-widget-root", "true");
98
+ Object.assign(container.style, {
99
+ display: "none",
100
+ position: "fixed",
101
+ bottom: `${BUTTON_SIZE + 36}px`,
102
+ right: "24px",
103
+ zIndex: "999999",
104
+ transformOrigin: "bottom right",
105
+ transform: "scale(0.8) translateY(20px)",
106
+ opacity: "0",
107
+ transition: "all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55)",
108
+ });
109
+
110
+ const arrow = document.createElement("div");
111
+ const arrowBorder = document.createElement("div");
112
+ const arrowFill = document.createElement("div");
113
+
114
+ Object.assign(arrow.style, {
115
+ position: "absolute",
116
+ bottom: "-14px",
117
+ right: "28px",
118
+ width: "30px",
119
+ height: "20px",
120
+ pointerEvents: "none",
121
+ display: "none",
122
+ zIndex: "1",
123
+ });
124
+
125
+ Object.assign(arrowBorder.style, {
126
+ position: "absolute",
127
+ bottom: "0",
128
+ left: "0",
129
+ right: "0",
130
+ margin: "0 auto",
131
+ width: "0",
132
+ height: "0",
133
+ borderLeft: "15px solid transparent",
134
+ borderRight: "15px solid transparent",
135
+ borderTop: "15px solid rgba(15,23,42,0.1)",
136
+ });
137
+
138
+ Object.assign(arrowFill.style, {
139
+ position: "absolute",
140
+ bottom: "2px",
141
+ left: "0",
142
+ right: "0",
143
+ margin: "0 auto",
144
+ width: "0",
145
+ height: "0",
146
+ borderLeft: "13px solid transparent",
147
+ borderRight: "13px solid transparent",
148
+ borderTop: "13px solid white",
149
+ boxShadow: "0 6px 16px rgba(15,23,42,0.12)",
150
+ borderRadius: "2px",
151
+ });
152
+
153
+ arrow.appendChild(arrowBorder);
154
+ arrow.appendChild(arrowFill);
155
+
156
+ const frameWrapper = document.createElement("div");
157
+ Object.assign(frameWrapper.style, {
158
+ width: `${IFRAME_SIZE}px`,
159
+ maxWidth: "calc(100vw - 48px)",
160
+ height: `${Math.min(IFRAME_SIZE, Math.max(320, window.innerHeight - 140))}px`,
161
+ maxHeight: "calc(100vh - 150px)",
162
+ borderRadius: "20px",
163
+ background: "white",
164
+ border: "1px solid rgba(15,23,42,0.12)",
165
+ boxShadow: "0 18px 45px rgba(15,23,42,0.16)",
166
+ overflow: "hidden",
167
+ });
168
+ shadow.appendChild(frameWrapper);
169
+
170
+ const iframe = document.createElement("iframe");
171
+ iframe.src = iframeUrl;
172
+ iframe.title = "Copilot chat widget";
173
+ iframe.allow = "clipboard-read; clipboard-write; microphone; camera; display-capture";
174
+ iframe.setAttribute("scrolling", "no");
175
+ Object.assign(iframe.style, {
176
+ width: "100%",
177
+ height: "100%",
178
+ border: "none",
179
+ display: "block",
180
+ background: "transparent",
181
+ overflow: "hidden",
182
+ });
183
+
184
+ shadow.appendChild(frameWrapper);
185
+ frameWrapper.appendChild(iframe);
186
+
187
+ container.appendChild(arrow);
188
+ container.appendChild(frameWrapper);
189
+ document.body.appendChild(btn);
190
+ document.body.appendChild(container);
191
+
192
+ let isOpen = false;
193
+
194
+ const closeChat = () => {
195
+ if (!isOpen) return;
196
+ isOpen = false;
197
+ container.style.opacity = "0";
198
+ container.style.transform = "scale(0.8) translateY(20px)";
199
+ arrow.style.display = "none";
200
+ setTimeout(() => {
201
+ container.style.display = "none";
202
+ }, 250);
203
+ btn.style.transform = "scale(1)";
204
+ };
205
+
206
+ const handleWidgetMessage = (event) => {
207
+ const { data, source } = event || {};
208
+ if (!data?.type) return;
209
+
210
+ if (data.type === "CART_CHECKOUT" && source === iframe.contentWindow) {
211
+ console.log("[CopilotChat] Received checkout payload from widget:", data);
212
+ showCheckoutToast("Checkout message received from chat widget. Check console for payload.");
213
+ return;
214
+ }
215
+
216
+ if (data.type === "WIDGET_READY" && source === iframe.contentWindow) {
217
+ iframe.contentWindow.postMessage({ type: "INIT_WIDGET" }, "*");
218
+ return;
219
+ }
220
+
221
+ if (data.type === "CHAT_CLOSED") {
222
+ closeChat();
223
+ }
224
+ };
225
+
226
+ window.addEventListener("message", handleWidgetMessage);
227
+
228
+ btn.onclick = (event) => {
229
+ event.stopPropagation();
230
+ isOpen = !isOpen;
231
+ if (isOpen) {
232
+ container.style.display = "block";
233
+ arrow.style.display = "block";
234
+ requestAnimationFrame(() => {
235
+ container.style.opacity = "1";
236
+ container.style.transform = "scale(1) translateY(0)";
237
+ });
238
+ } else {
239
+ closeChat();
240
+ }
241
+ };
242
+
243
+ document.addEventListener("click", (event) => {
244
+ const target = event.target;
245
+ if (!target) return;
246
+ if (isOpen && !container.contains(target) && target !== btn) {
247
+ closeChat();
248
+ }
249
+ });
250
+
251
+ const controls = {
252
+ close: closeChat,
253
+ open: () => {
254
+ if (!isOpen) {
255
+ btn.click();
256
+ }
257
+ },
258
+ };
259
+
260
+ window.CopilotChat = window.CopilotChat || {};
261
+ window.CopilotChat.close = controls.close;
262
+ window.CopilotChat.open = controls.open;
263
+ window.CopilotChat.controls = controls;
264
+
265
+ return controls;
266
+ };
267
+
268
+ export async function loadCopilotChatWidget(options = {}) {
7
269
  if (typeof window === "undefined" || typeof document === "undefined") {
8
270
  return null;
9
271
  }
10
272
 
11
- const { token, autoload, baseUrl = BUILD_BASE_URL } = options;
273
+ const globalConfig = window.CopilotChatConfig || {};
274
+ const token =
275
+ options.token ||
276
+ globalConfig.token ||
277
+ (typeof window !== "undefined"
278
+ ? window.localStorage?.getItem("copilotChatToken") || null
279
+ : null);
12
280
 
13
- // Persist config for the runtime script
14
- window.CopilotChatConfig = {
15
- ...window.CopilotChatConfig,
16
- ...options,
17
- baseUrl: baseUrl || window.CopilotChatConfig?.baseUrl || undefined,
18
- };
281
+ if (!token) {
282
+ console.error(
283
+ "[CopilotChat] Missing token (provide via loadCopilotChatWidget({ token }) or window.CopilotChatConfig.token)."
284
+ );
285
+ return null;
286
+ }
287
+
288
+ const baseUrl =
289
+ options.baseUrl ||
290
+ globalConfig.baseUrl ||
291
+ BUILD_BASE_URL ||
292
+ (typeof window !== "undefined" && window.location?.origin) ||
293
+ null;
19
294
 
20
- const existing = document.getElementById("copilot-chat-widget-loader");
21
- if (existing) {
22
- if (token) existing.dataset.token = token;
23
- if (baseUrl) existing.dataset.baseUrl = baseUrl;
24
- if (autoload === false) existing.dataset.autoload = "false";
25
- return existing;
295
+ if (!baseUrl) {
296
+ console.error(
297
+ "[CopilotChat] Missing baseUrl (set via loadCopilotChatWidget({ baseUrl }), window.CopilotChatConfig.baseUrl, or WIDGET_BASE_URL)."
298
+ );
299
+ return null;
26
300
  }
27
301
 
28
- const script = document.createElement("script");
29
- script.id = "copilot-chat-widget-loader";
30
- script.src = new URL("./chat-widget.min.js", import.meta.url).href;
31
- script.async = true;
32
- if (token) script.dataset.token = token;
33
- if (baseUrl) script.dataset.baseUrl = baseUrl;
34
- if (autoload === false) script.dataset.autoload = "false";
302
+ const url = `${baseUrl.replace(/\/$/, "")}/api/chat-widget/config?token=${encodeURIComponent(
303
+ token
304
+ )}`;
35
305
 
36
- document.body.appendChild(script);
37
- return script;
306
+ try {
307
+ const response = await fetch(url, {
308
+ credentials: "omit",
309
+ mode: "cors",
310
+ });
311
+
312
+ if (!response.ok) {
313
+ throw new Error(`Server responded with ${response.status}`);
314
+ }
315
+
316
+ const remoteConfig = await response.json();
317
+ if (!remoteConfig?.iframeUrl || !remoteConfig?.launcherIcon) {
318
+ throw new Error("Received incomplete widget configuration from server.");
319
+ }
320
+
321
+ window.CopilotChat = window.CopilotChat || {};
322
+ window.CopilotChat.token = token;
323
+ window.CopilotChat.baseUrl = baseUrl;
324
+
325
+ return buildWidget({
326
+ iframeUrl: remoteConfig.iframeUrl,
327
+ launcherIcon: remoteConfig.launcherIcon,
328
+ });
329
+ } catch (error) {
330
+ console.error(
331
+ "[CopilotChat] Error during widget bootstrap:",
332
+ error instanceof Error ? error.message : error
333
+ );
334
+ return null;
335
+ }
38
336
  }
39
337
 
40
338
  export default loadCopilotChatWidget;