brainerce 1.32.0 → 1.34.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 +37 -7
- package/dist/bot/bootstrap.global.js +13 -4
- package/dist/bot/index.d.mts +14 -0
- package/dist/bot/index.d.ts +14 -0
- package/dist/bot/index.js +83 -28
- package/dist/bot/index.mjs +83 -28
- package/dist/index.d.mts +91 -0
- package/dist/index.d.ts +91 -0
- package/dist/index.js +117 -0
- package/dist/index.mjs +117 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -409,6 +409,33 @@ The `create-brainerce-store` scaffold ships ready-made components (`<Announcemen
|
|
|
409
409
|
|
|
410
410
|
Full guide: [Core Integration "Content"](https://brainerce.com/docs/integration/core#content). Advanced patterns (channel scoping, custom fields, translations, admin writes): [Optional Features "Content"](https://brainerce.com/docs/integration/optional). Validation + sanitize rules: [Rules & Reference "Content"](https://brainerce.com/docs/integration/rules).
|
|
411
411
|
|
|
412
|
+
### Blog
|
|
413
|
+
|
|
414
|
+
Merchants publish blog posts in **Content → Blog**. Storefronts choose their own URL scheme — render posts at `/blog/[slug]`, `/articles/[slug]`, or whatever fits the brand.
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
// List published posts (any SDK mode)
|
|
418
|
+
const { data: posts, meta } = await client.blog.getPosts({ page: 1, limit: 10 });
|
|
419
|
+
|
|
420
|
+
// Filter by category or tag
|
|
421
|
+
const { data: news } = await client.blog.getPosts({ category: 'news' });
|
|
422
|
+
const { data: tips } = await client.blog.getPosts({ tag: 'tutorial' });
|
|
423
|
+
|
|
424
|
+
// Fetch one by slug — returns null on 404
|
|
425
|
+
const post = await client.blog.getPost(params.slug);
|
|
426
|
+
if (!post) notFound();
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
**Security**: `post.content` is merchant-authored HTML. Always sanitize before rendering:
|
|
430
|
+
|
|
431
|
+
```tsx
|
|
432
|
+
import DOMPurify from 'isomorphic-dompurify';
|
|
433
|
+
const safeHtml = DOMPurify.sanitize(post.content);
|
|
434
|
+
return <div dangerouslySetInnerHTML={{ __html: safeHtml }} className="prose" />;
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
**Scheduling**: A post is visible once `status === 'PUBLISHED'` and `publishedAt <= now()`. Set a future `publishedAt` when publishing to schedule — no cron needed.
|
|
438
|
+
|
|
412
439
|
---
|
|
413
440
|
|
|
414
441
|
## Common Mistakes to Avoid
|
|
@@ -4867,11 +4894,12 @@ import { BrainerceBot } from 'brainerce/bot';
|
|
|
4867
4894
|
|
|
4868
4895
|
const bot = await BrainerceBot.mount({
|
|
4869
4896
|
connectionId: 'vc_abc123',
|
|
4870
|
-
// Optional: make
|
|
4871
|
-
//
|
|
4872
|
-
//
|
|
4873
|
-
|
|
4874
|
-
|
|
4897
|
+
// Optional: make cart adds (card buttons, the in-card variant picker, and
|
|
4898
|
+
// bot-initiated adds) go through YOUR cart so your header count stays in
|
|
4899
|
+
// sync. variantId is null for simple products. Return false to make the
|
|
4900
|
+
// widget fall back to navigating to the product page.
|
|
4901
|
+
onAddToCart: async ({ productId, variantId, quantity }) => {
|
|
4902
|
+
await client.smartAddToCart({ productId, variantId: variantId ?? undefined, quantity });
|
|
4875
4903
|
return true;
|
|
4876
4904
|
},
|
|
4877
4905
|
});
|
|
@@ -4879,9 +4907,11 @@ const bot = await BrainerceBot.mount({
|
|
|
4879
4907
|
bot?.destroy(); // optional teardown
|
|
4880
4908
|
```
|
|
4881
4909
|
|
|
4882
|
-
The widget persists an anonymous session in `localStorage`, restores conversations on revisit, streams answers, and shows product recommendation cards (image, price, add-to-cart / view buttons).
|
|
4910
|
+
The widget persists an anonymous session in `localStorage`, restores conversations on revisit, streams answers, and shows product recommendation cards (image, price, add-to-cart / view buttons). Multi-variant products get an **in-card variant picker** (attribute chips, live variant price/image) so shoppers can choose options and add without leaving the chat; shoppers can also just ask the assistant to add an item ("add the moka to my cart") and it happens through the same chain. A leave-a-message form lands in the merchant's Inquiries inbox, and zero-result searches feed the merchant's "unmet demand" analytics.
|
|
4911
|
+
|
|
4912
|
+
**Add to cart resolution** (never a dead button): the widget first calls your `onAddToCart` option; without one it dispatches a cancelable `brainerce:bot:add-to-cart` `CustomEvent` on `window` (`detail: { productId, variantId, quantity, connectionId }` — call `preventDefault()` after handling it); if nothing handles either, it navigates to the product page. Products too complex for in-chat picking (3+ attribute dimensions or 25+ variants) always navigate. Aside from your own cart handler, the widget is read-only by design — shoppers can never mutate the store through it.
|
|
4883
4913
|
|
|
4884
|
-
|
|
4914
|
+
Merchant-side display controls (Studio → Storefront Bot): chat size (compact / full screen / shopper's choice), auto-open, position, and whether shoppers may expand the window (`allowExpand`).
|
|
4885
4915
|
|
|
4886
4916
|
## Webhooks
|
|
4887
4917
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
"use strict";(()=>{var
|
|
2
|
-
`),t=null,i=null;for(let o of n){let r=/^\s*[-•*]\s+(.*)$/.exec(o);if(r){i=null,t||(t=document.createElement("ul"),b.appendChild(t));let d=document.createElement("li");
|
|
1
|
+
"use strict";(()=>{var R="https://api.brainerce.com",O=new Set(["he","ar"]),S={en:{online:"Online",placeholder:"Ask anything\u2026",error:"Something went wrong \u2014 please try again.",leaveMessage:"Leave a message for the team",yourEmail:"Your email",yourMessage:"Your message",send:"Send",sent:"Thanks! The team will get back to you by email.",close:"Close",expand:"Expand",collapse:"Collapse",searching:"Searching the store\u2026",addToCart:"Add to cart",added:"Added",view:"View",chooseOptions:"View product",results:"From the store",poweredBy:"Powered by Brainerce",addFailed:"I couldn\u2019t add that to the cart \u2014 try the button on the product card."},he:{online:"\u05DE\u05D7\u05D5\u05D1\u05E8",placeholder:"\u05E9\u05D0\u05DC\u05D5 \u05D0\u05D5\u05EA\u05D9 \u05D4\u05DB\u05DC\u2026",error:"\u05DE\u05E9\u05D4\u05D5 \u05D4\u05E9\u05EA\u05D1\u05E9 \u2014 \u05E0\u05E1\u05D5 \u05E9\u05D5\u05D1.",leaveMessage:"\u05D4\u05E9\u05D0\u05D9\u05E8\u05D5 \u05D4\u05D5\u05D3\u05E2\u05D4 \u05DC\u05E6\u05D5\u05D5\u05EA",yourEmail:"\u05D4\u05D0\u05D9\u05DE\u05D9\u05D9\u05DC \u05E9\u05DC\u05DB\u05DD",yourMessage:"\u05D4\u05D4\u05D5\u05D3\u05E2\u05D4 \u05E9\u05DC\u05DB\u05DD",send:"\u05E9\u05DC\u05D9\u05D7\u05D4",sent:"\u05EA\u05D5\u05D3\u05D4! \u05D4\u05E6\u05D5\u05D5\u05EA \u05D9\u05D7\u05D6\u05D5\u05E8 \u05D0\u05DC\u05D9\u05DB\u05DD \u05D1\u05DE\u05D9\u05D9\u05DC.",close:"\u05E1\u05D2\u05D9\u05E8\u05D4",expand:"\u05D4\u05E8\u05D7\u05D1\u05D4",collapse:"\u05DB\u05D9\u05D5\u05D5\u05E5",searching:"\u05DE\u05D7\u05E4\u05E9 \u05D1\u05D7\u05E0\u05D5\u05EA\u2026",addToCart:"\u05D4\u05D5\u05E1\u05E4\u05D4 \u05DC\u05E1\u05DC",added:"\u05E0\u05D5\u05E1\u05E3",view:"\u05E6\u05E4\u05D9\u05D9\u05D4",chooseOptions:"\u05DC\u05E6\u05E4\u05D5\u05EA \u05D1\u05DE\u05D5\u05E6\u05E8",results:"\u05DE\u05EA\u05D5\u05DA \u05D4\u05D7\u05E0\u05D5\u05EA",poweredBy:"\u05DE\u05D5\u05E4\u05E2\u05DC \u05E2\u05DC \u05D9\u05D3\u05D9 Brainerce",addFailed:"\u05DC\u05D0 \u05D4\u05E6\u05DC\u05D7\u05EA\u05D9 \u05DC\u05D4\u05D5\u05E1\u05D9\u05E3 \u05DC\u05E1\u05DC \u2014 \u05E0\u05E1\u05D5 \u05D3\u05E8\u05DA \u05D4\u05DB\u05E4\u05EA\u05D5\u05E8 \u05D1\u05DB\u05E8\u05D8\u05D9\u05E1 \u05D4\u05DE\u05D5\u05E6\u05E8."}},H={chat:'<svg viewBox="0 0 24 24" fill="none"><path d="M12 3C7.03 3 3 6.58 3 11c0 2.04.86 3.9 2.28 5.32-.15 1.23-.62 2.39-1.1 3.21-.13.23.05.52.31.47 1.56-.27 3.07-.93 4.13-1.62A10.6 10.6 0 0 0 12 19c4.97 0 9-3.58 9-8s-4.03-8-9-8Z" fill="currentColor"/></svg>',close:'<svg viewBox="0 0 24 24" fill="none"><path d="M6 6l12 12M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>',expand:'<svg viewBox="0 0 24 24" fill="none"><path d="M14 4h6v6M10 20H4v-6M20 4l-7 7M4 20l7-7" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',collapse:'<svg viewBox="0 0 24 24" fill="none"><path d="M20 10h-6V4M4 14h6v6M20 4l-6 6M4 20l6-6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',mail:'<svg viewBox="0 0 24 24" fill="none"><rect x="3.5" y="5.5" width="17" height="13" rx="2.5" stroke="currentColor" stroke-width="1.7"/><path d="m4.5 7.5 7.5 5.5 7.5-5.5" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>',send:'<svg viewBox="0 0 24 24" fill="none"><path d="M4.4 11.2 19 4.6c.7-.3 1.4.4 1.1 1.1l-6.6 14.6c-.3.7-1.3.6-1.5-.1l-1.7-5.4a1 1 0 0 0-.6-.6l-5.4-1.7c-.7-.2-.8-1.2-.1-1.5Z" fill="currentColor"/></svg>',cart:'<svg viewBox="0 0 24 24" fill="none"><path d="M3 4h2l2.4 11.2A2 2 0 0 0 9.36 17H17.5a2 2 0 0 0 1.95-1.55L21 8H6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/><circle cx="10" cy="20.5" r="1.4" fill="currentColor"/><circle cx="17" cy="20.5" r="1.4" fill="currentColor"/></svg>',check:'<svg viewBox="0 0 24 24" fill="none"><path d="m5 12.5 4.5 4.5L19 7.5" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>',arrow:'<svg viewBox="0 0 24 24" fill="none"><path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',brand:'<svg viewBox="0 0 24 24"><defs><linearGradient id="bb-brand-g" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#34d399"/><stop offset="1" stop-color="#8b5cf6"/></linearGradient></defs><rect x="1.5" y="1.5" width="21" height="21" rx="6" fill="url(#bb-brand-g)"/><text x="12" y="17" text-anchor="middle" font-family="ui-sans-serif,system-ui,sans-serif" font-size="14" font-weight="700" fill="#fff">B</text></svg>'},z=typeof DOMParser<"u"?new DOMParser:null;function m(b){let e=(H[b]??H.chat).replace("<svg ",'<svg xmlns="http://www.w3.org/2000/svg" '),t=z?.parseFromString(e,"image/svg+xml")?.documentElement;return!t||t.nodeName==="parsererror"?document.createTextNode(""):(t.setAttribute("aria-hidden","true"),t.setAttribute("class","bb-ic"),t)}function P(b){let e=new Uint8Array(16);crypto.getRandomValues(e);let n=btoa(String.fromCharCode(...e)).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"");return`${b}${n}`}function T(b){return/^\/(?!\/)/.test(b)||/^https?:\/\//i.test(b)}var $=/\*\*([^*\n]+)\*\*|\[([^\]\n]+)\]\(([^)\s]+)\)/g;function A(b,e){let n=0;$.lastIndex=0;for(let t=$.exec(e);t;t=$.exec(e)){if(t.index>n&&b.appendChild(document.createTextNode(e.slice(n,t.index))),t[1]!==void 0){let i=document.createElement("strong");i.textContent=t[1],b.appendChild(i)}else if(T(t[3])){let i=document.createElement("a");i.href=t[3],i.target="_blank",i.rel="noopener noreferrer",i.textContent=t[2],b.appendChild(i)}else b.appendChild(document.createTextNode(t[2]));n=t.index+t[0].length}n<e.length&&b.appendChild(document.createTextNode(e.slice(n)))}function U(b,e){b.replaceChildren();let n=e.split(`
|
|
2
|
+
`),t=null,i=null;for(let o of n){let r=/^\s*[-•*]\s+(.*)$/.exec(o);if(r){i=null,t||(t=document.createElement("ul"),b.appendChild(t));let d=document.createElement("li");A(d,r[1]),t.appendChild(d);continue}if(t=null,!o.trim()){i=null;continue}i?i.appendChild(document.createElement("br")):(i=document.createElement("p"),b.appendChild(i)),A(i,o)}}var M=class b{constructor(e){this.settings={enabled:!1};this.locale="en";this.sessionId=null;this.conversationId=null;this.busy=!1;this.opened=!1;this.expanded=!1;this.destroyed=!1;this.pendingText="";this.cardsRow=null;this.cardIds=new Set;this.prevBodyOverflow=null;this.connectionId=e.connectionId,this.baseUrl=(e.baseUrl||R).replace(/\/$/,""),this.storageKey=`brainerce-bot:${this.connectionId}`,this.onAddToCart=e.onAddToCart}static async mount(e){if(!e?.connectionId)return console.warn("[BrainerceBot] connectionId is required"),null;let n=new b(e);return await n.boot(e.target??document.body)?n:null}destroy(){this.destroyed=!0,this.prevBodyOverflow!==null&&(document.body.style.overflow=this.prevBodyOverflow,this.prevBodyOverflow=null),this.host?.remove()}async boot(e){try{let n=await fetch(`${this.baseUrl}/api/storefront-bot/${encodeURIComponent(this.connectionId)}/settings`);if(!n.ok)return!1;this.settings=await n.json()}catch{return!1}return this.settings.enabled?(this.locale=this.settings.languages?.[0]??"en",this.restoreIds(),this.render(e),this.settings.displayMode==="auto_open"&&setTimeout(()=>!this.destroyed&&this.open(),3e3),!0):!1}t(e){return(S[this.locale]??S.en)[e]??S.en[e]??e}restoreIds(){try{let e=localStorage.getItem(this.storageKey);if(e){let n=JSON.parse(e);this.sessionId=n.sessionId??null,this.conversationId=n.conversationId??null}}catch{}}persistIds(){try{localStorage.setItem(this.storageKey,JSON.stringify({sessionId:this.sessionId,conversationId:this.conversationId}))}catch{}}css(e,n,t){let i=this.settings.bubbleShape==="square",o=i?"14px":"22px",r=i?"8px":"15px",d="5px",a=this.settings.displayMode??"floating",l=a==="side_rail",s=a==="full_screen";return`
|
|
3
3
|
:host { all: initial; }
|
|
4
4
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0;
|
|
5
5
|
font-family: -apple-system, "SF Pro Text", "Segoe UI Variable Text", "Segoe UI", system-ui, "Helvetica Neue", sans-serif;
|
|
@@ -237,6 +237,15 @@
|
|
|
237
237
|
.bb-send .bb-ic { width: 17px; height: 17px; ${n==="rtl"?"transform: scaleX(-1);":""} }
|
|
238
238
|
.bb-send:hover { filter: brightness(1.08); transform: scale(1.06); }
|
|
239
239
|
.bb-send:disabled { opacity: .4; pointer-events: none; }
|
|
240
|
-
`}render(e){let n=this.settings.accentColor||"#6366F1",t=R.has(this.locale)?"rtl":"ltr",i=this.settings.position==="start"?"left":"right",o=t==="rtl"?i==="left"?"right":"left":i,r=this.settings.displayName||"Assistant";this.host=document.createElement("div"),this.host.setAttribute("data-brainerce-bot",this.connectionId),this.root=this.host.attachShadow({mode:"open"});let d=document.createElement("style");d.textContent=this.css(n,t,o),this.root.appendChild(d);let a=document.createElement("div");a.className="bb",this.root.appendChild(a);let l=document.createElement("div");l.className="bb-scrim",l.addEventListener("click",()=>{this.expanded?this.toggleExpand():this.close()}),a.appendChild(l),this.windowEl=document.createElement("div"),this.windowEl.className="bb-window",a.appendChild(this.windowEl);let s=document.createElement("div");s.className="bb-header";let h=document.createElement("span");if(h.className="bb-avatar",this.settings.avatarUrl&&T(this.settings.avatarUrl)){let p=document.createElement("img");p.src=this.settings.avatarUrl,p.alt="",h.appendChild(p)}else h.appendChild(document.createTextNode(r.charAt(0).toUpperCase()));let u=document.createElement("span");u.className="bb-head-main";let g=document.createElement("span");g.className="bb-name",g.textContent=r;let v=document.createElement("span");v.className="bb-status",v.textContent=this.t("online"),u.appendChild(g),u.appendChild(v);let x=document.createElement("span");x.className="bb-actions",(this.settings.displayMode??"floating")!=="full_screen"&&this.settings.allowExpand!==!1&&(this.expandBtn=this.iconButton("expand",this.t("expand"),()=>this.toggleExpand()),x.appendChild(this.expandBtn)),x.appendChild(this.iconButton("mail",this.t("leaveMessage"),()=>this.toggleEscalation())),x.appendChild(this.iconButton("close",this.t("close"),()=>this.close())),s.appendChild(h),s.appendChild(u),s.appendChild(x),this.windowEl.appendChild(s),this.messagesEl=document.createElement("div"),this.messagesEl.className="bb-msgs",this.windowEl.appendChild(this.messagesEl),this.chipsEl=document.createElement("div"),this.chipsEl.className="bb-chips";for(let p of this.settings.starterQuestions??[]){let I=document.createElement("button");I.className="bb-chip",I.textContent=p,I.addEventListener("click",()=>this.send(p)),this.chipsEl.appendChild(I)}this.windowEl.appendChild(this.chipsEl);let c=document.createElement("div");c.className="bb-esc";let E=document.createElement("span");E.className="bb-esc-title",E.textContent=this.t("leaveMessage");let y=document.createElement("input");y.type="email",y.name="email",y.placeholder=this.t("yourEmail");let C=document.createElement("textarea");C.name="message",C.rows=2,C.placeholder=this.t("yourMessage");let k=document.createElement("button");k.type="button",k.className="bb-esc-send",k.textContent=this.t("send"),k.addEventListener("click",()=>this.submitEscalation(c)),c.appendChild(E),c.appendChild(y),c.appendChild(C),c.appendChild(k),this.windowEl.appendChild(c);let m=document.createElement("div");if(m.className="bb-composer",this.inputEl=document.createElement("textarea"),this.inputEl.className="bb-input",this.inputEl.rows=1,this.inputEl.placeholder=this.t("placeholder"),this.inputEl.addEventListener("keydown",p=>{p.key==="Enter"&&!p.shiftKey&&(p.preventDefault(),this.send(this.inputEl?.value??""))}),this.inputEl.addEventListener("input",()=>this.syncSendState()),this.sendBtn=document.createElement("button"),this.sendBtn.className="bb-send",this.sendBtn.disabled=!0,this.sendBtn.setAttribute("aria-label",this.t("send")),this.sendBtn.appendChild(f("send")),this.sendBtn.addEventListener("click",()=>this.send(this.inputEl?.value??"")),m.appendChild(this.inputEl),m.appendChild(this.sendBtn),this.windowEl.appendChild(m),this.launcherEl=document.createElement("button"),this.launcherEl.className="bb-launcher",this.launcherEl.setAttribute("aria-label",r),this.settings.avatarUrl&&T(this.settings.avatarUrl)){let p=document.createElement("img");p.src=this.settings.avatarUrl,p.alt="",this.launcherEl.appendChild(p)}else{let p=f("chat");p.classList.add("bb-l-chat"),this.launcherEl.appendChild(p)}let L=f("close");L.classList.add("bb-l-close"),this.launcherEl.appendChild(L),this.launcherEl.addEventListener("click",()=>this.opened?this.close():this.open()),a.appendChild(this.launcherEl),e.appendChild(this.host)}iconButton(e,n,t){let i=document.createElement("button");return i.className="bb-iconbtn",i.title=n,i.setAttribute("aria-label",n),i.appendChild(f(e)),i.addEventListener("click",t),i}syncSendState(){this.sendBtn&&(this.sendBtn.disabled=!(this.inputEl?.value??"").trim()||this.busy)}open(){if(!this.windowEl||this.opened)return;this.opened=!0;let e=this.root?.querySelector(".bb");e?.classList.add("open");let n=(this.settings.displayMode??"floating")==="full_screen"||window.innerWidth<=520;e?.classList.toggle("big",n||this.expanded),this.messagesEl&&this.messagesEl.childElementCount===0&&this.primeThread(),this.inputEl?.focus()}close(){this.opened=!1;let e=this.root?.querySelector(".bb");e?.classList.remove("open"),this.expanded||e?.classList.remove("big")}toggleExpand(){if(this.expanded=!this.expanded,this.root?.querySelector(".bb")?.classList.toggle("expanded",this.expanded),this.root?.querySelector(".bb")?.classList.toggle("big",this.expanded),this.expandBtn){this.expandBtn.replaceChildren(f(this.expanded?"collapse":"expand"));let e=this.t(this.expanded?"collapse":"expand");this.expandBtn.title=e,this.expandBtn.setAttribute("aria-label",e)}}async primeThread(){if(this.conversationId&&this.sessionId)try{let e=await fetch(`${this.baseUrl}/api/storefront-bot/${encodeURIComponent(this.connectionId)}/conversations/${encodeURIComponent(this.conversationId)}?limit=50`,{headers:{"X-Bot-Session":this.sessionId}});if(e.ok){let n=await e.json();for(let t of n.data){let i=this.appendMessage(t.role==="assistant"?"bot":"user","");B(i,t.content)}if(n.data.length>0){this.chipsEl?.remove();return}}else this.conversationId=null,this.sessionId=null,this.persistIds()}catch{}if(this.settings.greeting){let e=this.appendMessage("bot","");B(e,this.settings.greeting)}}async send(e){let n=e.trim();if(!n||this.busy)return;this.busy=!0,this.inputEl&&(this.inputEl.value=""),this.syncSendState(),this.chipsEl?.remove(),this.appendMessage("user",n);let t=this.appendTyping();this.pendingText="",this.cardsRow=null,this.cardIds=new Set;let i=null;try{let o=await fetch(`${this.baseUrl}/api/storefront-bot/${encodeURIComponent(this.connectionId)}/chat`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:n,turnId:O("trn_"),...this.conversationId?{conversationId:this.conversationId}:{},...this.sessionId?{anonymousSessionId:this.sessionId}:{},locale:this.locale})});if(!o.ok||!o.body)throw new Error(`chat failed (${o.status})`);let r=o.body.getReader(),d=new TextDecoder,a="";for(;;){let{value:l,done:s}=await r.read();if(s)break;a+=d.decode(l,{stream:!0});let h;for(;(h=a.indexOf(`
|
|
241
240
|
|
|
242
|
-
|
|
241
|
+
/* Powered-by footer */
|
|
242
|
+
.bb-foot { flex-shrink: 0; display: flex; justify-content: center; align-items: center;
|
|
243
|
+
padding: 5px 12px 7px; background: #fff; }
|
|
244
|
+
.bb-foot a { display: inline-flex; align-items: center; gap: 5px; font-size: 10.5px;
|
|
245
|
+
font-weight: 500; color: #aab0bd; text-decoration: none; letter-spacing: .005em;
|
|
246
|
+
transition: color .12s ease; }
|
|
247
|
+
.bb-foot a:hover { color: #6b7280; }
|
|
248
|
+
.bb-foot .bb-ic { width: 13px; height: 13px; border-radius: 4px; }
|
|
249
|
+
`}render(e){let n=this.settings.accentColor||"#6366F1",t=O.has(this.locale)?"rtl":"ltr",i=this.settings.position==="start"?"left":"right",o=t==="rtl"?i==="left"?"right":"left":i,r=this.settings.displayName||"Assistant";this.host=document.createElement("div"),this.host.setAttribute("data-brainerce-bot",this.connectionId),this.root=this.host.attachShadow({mode:"open"});let d=document.createElement("style");d.textContent=this.css(n,t,o),this.root.appendChild(d);let a=document.createElement("div");a.className="bb",this.root.appendChild(a);let l=document.createElement("div");l.className="bb-scrim",l.addEventListener("click",()=>{this.expanded?this.toggleExpand():this.close()}),a.appendChild(l),this.windowEl=document.createElement("div"),this.windowEl.className="bb-window",a.appendChild(this.windowEl);let s=document.createElement("div");s.className="bb-header";let h=document.createElement("span");if(h.className="bb-avatar",this.settings.avatarUrl&&T(this.settings.avatarUrl)){let p=document.createElement("img");p.src=this.settings.avatarUrl,p.alt="",h.appendChild(p)}else h.appendChild(document.createTextNode(r.charAt(0).toUpperCase()));let u=document.createElement("span");u.className="bb-head-main";let g=document.createElement("span");g.className="bb-name",g.textContent=r;let w=document.createElement("span");w.className="bb-status",w.textContent=this.t("online"),u.appendChild(g),u.appendChild(w);let v=document.createElement("span");v.className="bb-actions",(this.settings.displayMode??"floating")!=="full_screen"&&this.settings.allowExpand!==!1&&(this.expandBtn=this.iconButton("expand",this.t("expand"),()=>this.toggleExpand()),v.appendChild(this.expandBtn)),v.appendChild(this.iconButton("mail",this.t("leaveMessage"),()=>this.toggleEscalation())),v.appendChild(this.iconButton("close",this.t("close"),()=>this.close())),s.appendChild(h),s.appendChild(u),s.appendChild(v),this.windowEl.appendChild(s),this.messagesEl=document.createElement("div"),this.messagesEl.className="bb-msgs",this.windowEl.appendChild(this.messagesEl),this.chipsEl=document.createElement("div"),this.chipsEl.className="bb-chips";for(let p of this.settings.starterQuestions??[]){let N=document.createElement("button");N.className="bb-chip",N.textContent=p,N.addEventListener("click",()=>this.send(p)),this.chipsEl.appendChild(N)}this.windowEl.appendChild(this.chipsEl);let c=document.createElement("div");c.className="bb-esc";let C=document.createElement("span");C.className="bb-esc-title",C.textContent=this.t("leaveMessage");let E=document.createElement("input");E.type="email",E.name="email",E.placeholder=this.t("yourEmail");let k=document.createElement("textarea");k.name="message",k.rows=2,k.placeholder=this.t("yourMessage");let I=document.createElement("button");I.type="button",I.className="bb-esc-send",I.textContent=this.t("send"),I.addEventListener("click",()=>this.submitEscalation(c)),c.appendChild(C),c.appendChild(E),c.appendChild(k),c.appendChild(I),this.windowEl.appendChild(c);let f=document.createElement("div");f.className="bb-composer",this.inputEl=document.createElement("textarea"),this.inputEl.className="bb-input",this.inputEl.rows=1,this.inputEl.placeholder=this.t("placeholder"),this.inputEl.addEventListener("keydown",p=>{p.key==="Enter"&&!p.shiftKey&&(p.preventDefault(),this.send(this.inputEl?.value??""))}),this.inputEl.addEventListener("input",()=>this.syncSendState()),this.sendBtn=document.createElement("button"),this.sendBtn.className="bb-send",this.sendBtn.disabled=!0,this.sendBtn.setAttribute("aria-label",this.t("send")),this.sendBtn.appendChild(m("send")),this.sendBtn.addEventListener("click",()=>this.send(this.inputEl?.value??"")),f.appendChild(this.inputEl),f.appendChild(this.sendBtn),this.windowEl.appendChild(f);let B=document.createElement("div");B.className="bb-foot";let x=document.createElement("a");if(x.href="https://brainerce.com",x.target="_blank",x.rel="noopener noreferrer",x.appendChild(m("brand")),x.appendChild(document.createTextNode(this.t("poweredBy"))),B.appendChild(x),this.windowEl.appendChild(B),this.launcherEl=document.createElement("button"),this.launcherEl.className="bb-launcher",this.launcherEl.setAttribute("aria-label",r),this.settings.avatarUrl&&T(this.settings.avatarUrl)){let p=document.createElement("img");p.src=this.settings.avatarUrl,p.alt="",this.launcherEl.appendChild(p)}else{let p=m("chat");p.classList.add("bb-l-chat"),this.launcherEl.appendChild(p)}let L=m("close");L.classList.add("bb-l-close"),this.launcherEl.appendChild(L),this.launcherEl.addEventListener("click",()=>this.opened?this.close():this.open()),a.appendChild(this.launcherEl),e.appendChild(this.host)}iconButton(e,n,t){let i=document.createElement("button");return i.className="bb-iconbtn",i.title=n,i.setAttribute("aria-label",n),i.appendChild(m(e)),i.addEventListener("click",t),i}syncSendState(){this.sendBtn&&(this.sendBtn.disabled=!(this.inputEl?.value??"").trim()||this.busy)}open(){if(!this.windowEl||this.opened)return;this.opened=!0;let e=this.root?.querySelector(".bb");e?.classList.add("open");let n=(this.settings.displayMode??"floating")==="full_screen"||window.innerWidth<=520;e?.classList.toggle("big",n||this.expanded),this.syncBodyScroll(),this.messagesEl&&this.messagesEl.childElementCount===0&&this.primeThread(),this.inputEl?.focus()}close(){this.opened=!1;let e=this.root?.querySelector(".bb");e?.classList.remove("open"),this.expanded||e?.classList.remove("big"),this.syncBodyScroll()}syncBodyScroll(){let e=this.opened&&!!this.root?.querySelector(".bb")?.classList.contains("big");e&&this.prevBodyOverflow===null?(this.prevBodyOverflow=document.body.style.overflow||"",document.body.style.overflow="hidden"):!e&&this.prevBodyOverflow!==null&&(document.body.style.overflow=this.prevBodyOverflow,this.prevBodyOverflow=null)}toggleExpand(){if(this.expanded=!this.expanded,this.root?.querySelector(".bb")?.classList.toggle("expanded",this.expanded),this.root?.querySelector(".bb")?.classList.toggle("big",this.expanded),this.syncBodyScroll(),this.expandBtn){this.expandBtn.replaceChildren(m(this.expanded?"collapse":"expand"));let e=this.t(this.expanded?"collapse":"expand");this.expandBtn.title=e,this.expandBtn.setAttribute("aria-label",e)}}async primeThread(){if(this.conversationId&&this.sessionId)try{let e=await fetch(`${this.baseUrl}/api/storefront-bot/${encodeURIComponent(this.connectionId)}/conversations/${encodeURIComponent(this.conversationId)}?limit=50`,{headers:{"X-Bot-Session":this.sessionId}});if(e.ok){let n=await e.json();for(let t of n.data){let i=this.appendMessage(t.role==="assistant"?"bot":"user","");U(i,t.content)}if(n.data.length>0){this.chipsEl?.remove();return}}else this.conversationId=null,this.sessionId=null,this.persistIds()}catch{}if(this.settings.greeting){let e=this.appendMessage("bot","");U(e,this.settings.greeting)}}async send(e){let n=e.trim();if(!n||this.busy)return;this.busy=!0,this.inputEl&&(this.inputEl.value=""),this.syncSendState(),this.chipsEl?.remove(),this.appendMessage("user",n);let t=this.appendTyping();this.pendingText="",this.cardsRow=null,this.cardIds=new Set;let i=null;try{let o=await fetch(`${this.baseUrl}/api/storefront-bot/${encodeURIComponent(this.connectionId)}/chat`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:n,turnId:P("trn_"),...this.conversationId?{conversationId:this.conversationId}:{},...this.sessionId?{anonymousSessionId:this.sessionId}:{},locale:this.locale})});if(!o.ok||!o.body)throw new Error(`chat failed (${o.status})`);let r=o.body.getReader(),d=new TextDecoder,a="";for(;;){let{value:l,done:s}=await r.read();if(s)break;a+=d.decode(l,{stream:!0});let h;for(;(h=a.indexOf(`
|
|
250
|
+
|
|
251
|
+
`))>=0;){let u=a.slice(0,h);if(a=a.slice(h+2),!u.startsWith("data: "))continue;let g;try{g=JSON.parse(u.slice(6))}catch{continue}i=this.handleFrame(g,t,i)}}i&&this.pendingText&&U(i,this.pendingText)}catch{this.appendMessage("err",this.t("error"))}finally{t.remove(),this.busy=!1,this.syncSendState()}}handleFrame(e,n,t){switch(e.type){case"connected":return this.conversationId=e.conversationId||this.conversationId,this.sessionId=e.anonymousSessionId||this.sessionId,this.persistIds(),t;case"token":return t||(n.remove(),t=this.appendMessage("bot","")),this.pendingText+=e.text,t.textContent=this.pendingText,this.scrollDown(),t;case"tool":return n.classList.toggle("searching",e.status==="running"),t;case"card":return this.appendCard(e.card),t;case"action":return this.handleAction(e),t;case"error":return this.appendMessage("err",e.message||this.t("error")),t;case"done":default:return t}}appendCard(e){if(!this.messagesEl||this.cardIds.has(e.productId))return;if(this.cardIds.add(e.productId),!this.cardsRow){let s=document.createElement("div");s.className="bb-shelf";let h=document.createElement("div");h.className="bb-shelf-cap",h.textContent=this.t("results"),this.cardsRow=document.createElement("div"),this.cardsRow.className="bb-cards",s.appendChild(h),s.appendChild(this.cardsRow),this.messagesEl.appendChild(s)}let n=T(e.url)?e.url:null,t=document.createElement("div");t.className="bb-card";let i=()=>{this.beacon(e.botRef),n&&(window.location.href=n)},o=document.createElement("span");if(o.className="bb-card-img",e.imageUrl&&T(e.imageUrl)){let s=document.createElement("img");s.src=e.imageUrl,s.alt="",s.loading="lazy",o.appendChild(s)}else o.appendChild(m("cart"));o.addEventListener("click",i),t.appendChild(o);let r=document.createElement("span");r.className="bb-card-body";let d=document.createElement("span");d.className="bb-card-title",d.textContent=e.title,d.addEventListener("click",i);let a=document.createElement("span");a.className="bb-card-price",a.textContent=e.price.formatted,r.appendChild(d),r.appendChild(a);let l=document.createElement("span");if(l.className="bb-card-cta",e.requiresOptions){let s=document.createElement("button");s.className="bb-btn bb-btn-add",s.setAttribute("aria-label",this.t("addToCart")),s.appendChild(m("cart")),e.variants?.length?s.addEventListener("click",()=>this.togglePicker(r,e,o)):s.addEventListener("click",i),l.appendChild(s)}else{let s=document.createElement("button");s.className="bb-btn bb-btn-add",s.appendChild(m("cart")),s.appendChild(document.createTextNode(this.t("addToCart"))),s.addEventListener("click",()=>void this.addToCart(e,s));let h=document.createElement("button");h.className="bb-btn bb-btn-ghost",h.appendChild(document.createTextNode(this.t("view"))),h.addEventListener("click",i),l.appendChild(s),l.appendChild(h)}r.appendChild(l),t.appendChild(r),this.cardsRow.appendChild(t),this.scrollDown()}togglePicker(e,n,t){let i=e.parentElement,o=e.querySelector(".bb-pick");if(o){o.remove(),i?.classList.remove("picking");return}i?.classList.add("picking");let r=n.variants??[],d=[];for(let u of r)for(let g of Object.keys(u.attributes))d.includes(g)||d.push(g);let a={},l=document.createElement("span");l.className="bb-pick";let s=()=>r.find(u=>d.every(g=>a[g]&&u.attributes[g]===a[g]))??null,h=()=>{l.replaceChildren();for(let c of d){let C=document.createElement("span");C.className="bb-pick-key",C.textContent=c;let E=document.createElement("span");E.className="bb-pick-vals";let k=new Set;for(let I of r){let f=I.attributes[c];if(!f||k.has(f))continue;k.add(f);let B=r.some(L=>L.attributes[c]===f&&d.every(p=>p===c||!a[p]||L.attributes[p]===a[p])),x=document.createElement("button");x.className=`bb-chipv${a[c]===f?" sel":""}${B?"":" off"}`,x.textContent=f,x.addEventListener("click",()=>{a[c]===f?delete a[c]:a[c]=f,h()}),E.appendChild(x)}l.appendChild(C),l.appendChild(E)}let u=document.createElement("button");u.className="bb-pick-close",u.setAttribute("aria-label",this.t("close")),u.appendChild(m("close")),u.addEventListener("click",()=>{l.remove(),i?.classList.remove("picking")}),l.appendChild(u);let g=s(),w=document.createElement("span");w.className="bb-pick-foot";let v=document.createElement("span");v.className="bb-pick-price",v.textContent=g?g.price.formatted:"";let y=document.createElement("button");if(y.className="bb-btn bb-btn-add",y.appendChild(m("cart")),y.appendChild(document.createTextNode(this.t("addToCart"))),g||(y.disabled=!0),y.addEventListener("click",()=>{let c=s();c&&this.addToCart(n,y,c.id)}),w.appendChild(v),w.appendChild(y),l.appendChild(w),g?.imageUrl&&T(g.imageUrl)){let c=t.querySelector("img");c&&(c.src=g.imageUrl)}this.scrollDown()};h(),e.appendChild(l),this.scrollDown()}async addToCart(e,n,t){this.beacon(e.botRef),n.dataset.state="busy",await this.dispatchAdd(e.productId,t??null)?(n.dataset.state="done",n.replaceChildren(m("check"),document.createTextNode(this.t("added"))),setTimeout(()=>{!this.destroyed&&n.isConnected&&(delete n.dataset.state,n.replaceChildren(m("cart"),document.createTextNode(this.t("addToCart"))))},2200)):(delete n.dataset.state,T(e.url)&&(window.location.href=e.url))}async dispatchAdd(e,n){try{if(this.onAddToCart)return await this.onAddToCart({productId:e,variantId:n,quantity:1})!==!1;let t=new CustomEvent("brainerce:bot:add-to-cart",{detail:{productId:e,variantId:n,quantity:1,connectionId:this.connectionId},cancelable:!0,bubbles:!0,composed:!0});return!window.dispatchEvent(t)}catch{return!1}}async handleAction(e){if(e.action!=="add_to_cart")return;e.botRef&&this.beacon(e.botRef),await this.dispatchAdd(e.productId,e.variantId)||this.appendMessage("err",this.t("addFailed"))}beacon(e){try{navigator.sendBeacon?.(`${this.baseUrl}/api/storefront-bot/attribution/click`,new Blob([JSON.stringify({botRef:e})],{type:"application/json"}))}catch{}}toggleEscalation(){this.root?.querySelector(".bb-esc")?.classList.toggle("open")}async submitEscalation(e){let n=e.querySelector('input[name="email"]')?.value.trim(),t=e.querySelector('textarea[name="message"]')?.value.trim();if(!(!n||!t||!this.conversationId||!this.sessionId))try{if((await fetch(`${this.baseUrl}/api/storefront-bot/${encodeURIComponent(this.connectionId)}/escalate`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:n,message:t,conversationId:this.conversationId,anonymousSessionId:this.sessionId,locale:this.locale})})).ok){let o=document.createElement("span");o.className="bb-esc-note",o.appendChild(m("check")),o.appendChild(document.createTextNode(this.t("sent"))),e.replaceChildren(o)}}catch{}}appendMessage(e,n){let t=document.createElement("div");return t.className=`bb-msg ${e}`,t.setAttribute("dir","auto"),t.textContent=n,this.messagesEl?.appendChild(t),this.scrollDown(),t}appendTyping(){let e=document.createElement("div");e.className="bb-typing";let n=document.createElement("span");n.className="bb-dots";for(let i=0;i<3;i++)n.appendChild(document.createElement("i"));let t=document.createElement("span");return t.className="bb-tool",t.textContent=this.t("searching"),e.appendChild(n),e.appendChild(t),this.messagesEl?.appendChild(e),this.scrollDown(),e}scrollDown(){this.messagesEl&&(this.messagesEl.scrollTop=this.messagesEl.scrollHeight)}};(()=>{let b=document.currentScript,e=b?.dataset.connectionId;if(!e){console.warn("[BrainerceBot] missing data-connection-id on the bot.js script tag");return}let n=b?.dataset.apiBase||void 0,t=()=>void M.mount({connectionId:e,baseUrl:n});document.readyState==="loading"?document.addEventListener("DOMContentLoaded",t,{once:!0}):t()})();})();
|
package/dist/bot/index.d.mts
CHANGED
|
@@ -74,6 +74,12 @@ declare class BrainerceBot {
|
|
|
74
74
|
private syncSendState;
|
|
75
75
|
private open;
|
|
76
76
|
private close;
|
|
77
|
+
/**
|
|
78
|
+
* The large dialog owns the screen — the page behind must not scroll.
|
|
79
|
+
* Locks <body> while a big surface is open; restores on close/collapse.
|
|
80
|
+
*/
|
|
81
|
+
private prevBodyOverflow;
|
|
82
|
+
private syncBodyScroll;
|
|
77
83
|
private toggleExpand;
|
|
78
84
|
/** First open: restore the server thread, or show the greeting. */
|
|
79
85
|
private primeThread;
|
|
@@ -93,6 +99,14 @@ declare class BrainerceBot {
|
|
|
93
99
|
* 3. fallback: navigate to the product page
|
|
94
100
|
*/
|
|
95
101
|
private addToCart;
|
|
102
|
+
/**
|
|
103
|
+
* The host-cart chain shared by card buttons and bot-initiated actions:
|
|
104
|
+
* onAddToCart option -> cancelable CustomEvent. Returns whether a host
|
|
105
|
+
* took the add.
|
|
106
|
+
*/
|
|
107
|
+
private dispatchAdd;
|
|
108
|
+
/** Bot-initiated widget actions (the model called the addToCart tool). */
|
|
109
|
+
private handleAction;
|
|
96
110
|
/** The durable conversion signal — fire-and-forget, never blocks. */
|
|
97
111
|
private beacon;
|
|
98
112
|
private toggleEscalation;
|
package/dist/bot/index.d.ts
CHANGED
|
@@ -74,6 +74,12 @@ declare class BrainerceBot {
|
|
|
74
74
|
private syncSendState;
|
|
75
75
|
private open;
|
|
76
76
|
private close;
|
|
77
|
+
/**
|
|
78
|
+
* The large dialog owns the screen — the page behind must not scroll.
|
|
79
|
+
* Locks <body> while a big surface is open; restores on close/collapse.
|
|
80
|
+
*/
|
|
81
|
+
private prevBodyOverflow;
|
|
82
|
+
private syncBodyScroll;
|
|
77
83
|
private toggleExpand;
|
|
78
84
|
/** First open: restore the server thread, or show the greeting. */
|
|
79
85
|
private primeThread;
|
|
@@ -93,6 +99,14 @@ declare class BrainerceBot {
|
|
|
93
99
|
* 3. fallback: navigate to the product page
|
|
94
100
|
*/
|
|
95
101
|
private addToCart;
|
|
102
|
+
/**
|
|
103
|
+
* The host-cart chain shared by card buttons and bot-initiated actions:
|
|
104
|
+
* onAddToCart option -> cancelable CustomEvent. Returns whether a host
|
|
105
|
+
* took the add.
|
|
106
|
+
*/
|
|
107
|
+
private dispatchAdd;
|
|
108
|
+
/** Bot-initiated widget actions (the model called the addToCart tool). */
|
|
109
|
+
private handleAction;
|
|
96
110
|
/** The durable conversion signal — fire-and-forget, never blocks. */
|
|
97
111
|
private beacon;
|
|
98
112
|
private toggleEscalation;
|
package/dist/bot/index.js
CHANGED
|
@@ -45,7 +45,9 @@ var CHROME = {
|
|
|
45
45
|
added: "Added",
|
|
46
46
|
view: "View",
|
|
47
47
|
chooseOptions: "View product",
|
|
48
|
-
results: "From the store"
|
|
48
|
+
results: "From the store",
|
|
49
|
+
poweredBy: "Powered by Brainerce",
|
|
50
|
+
addFailed: "I couldn\u2019t add that to the cart \u2014 try the button on the product card."
|
|
49
51
|
},
|
|
50
52
|
he: {
|
|
51
53
|
online: "\u05DE\u05D7\u05D5\u05D1\u05E8",
|
|
@@ -64,7 +66,9 @@ var CHROME = {
|
|
|
64
66
|
added: "\u05E0\u05D5\u05E1\u05E3",
|
|
65
67
|
view: "\u05E6\u05E4\u05D9\u05D9\u05D4",
|
|
66
68
|
chooseOptions: "\u05DC\u05E6\u05E4\u05D5\u05EA \u05D1\u05DE\u05D5\u05E6\u05E8",
|
|
67
|
-
results: "\u05DE\u05EA\u05D5\u05DA \u05D4\u05D7\u05E0\u05D5\u05EA"
|
|
69
|
+
results: "\u05DE\u05EA\u05D5\u05DA \u05D4\u05D7\u05E0\u05D5\u05EA",
|
|
70
|
+
poweredBy: "\u05DE\u05D5\u05E4\u05E2\u05DC \u05E2\u05DC \u05D9\u05D3\u05D9 Brainerce",
|
|
71
|
+
addFailed: "\u05DC\u05D0 \u05D4\u05E6\u05DC\u05D7\u05EA\u05D9 \u05DC\u05D4\u05D5\u05E1\u05D9\u05E3 \u05DC\u05E1\u05DC \u2014 \u05E0\u05E1\u05D5 \u05D3\u05E8\u05DA \u05D4\u05DB\u05E4\u05EA\u05D5\u05E8 \u05D1\u05DB\u05E8\u05D8\u05D9\u05E1 \u05D4\u05DE\u05D5\u05E6\u05E8."
|
|
68
72
|
}
|
|
69
73
|
};
|
|
70
74
|
var ICONS = {
|
|
@@ -76,7 +80,10 @@ var ICONS = {
|
|
|
76
80
|
send: '<svg viewBox="0 0 24 24" fill="none"><path d="M4.4 11.2 19 4.6c.7-.3 1.4.4 1.1 1.1l-6.6 14.6c-.3.7-1.3.6-1.5-.1l-1.7-5.4a1 1 0 0 0-.6-.6l-5.4-1.7c-.7-.2-.8-1.2-.1-1.5Z" fill="currentColor"/></svg>',
|
|
77
81
|
cart: '<svg viewBox="0 0 24 24" fill="none"><path d="M3 4h2l2.4 11.2A2 2 0 0 0 9.36 17H17.5a2 2 0 0 0 1.95-1.55L21 8H6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/><circle cx="10" cy="20.5" r="1.4" fill="currentColor"/><circle cx="17" cy="20.5" r="1.4" fill="currentColor"/></svg>',
|
|
78
82
|
check: '<svg viewBox="0 0 24 24" fill="none"><path d="m5 12.5 4.5 4.5L19 7.5" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
79
|
-
arrow: '<svg viewBox="0 0 24 24" fill="none"><path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>'
|
|
83
|
+
arrow: '<svg viewBox="0 0 24 24" fill="none"><path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
84
|
+
// Brainerce brand mark for the Powered-by footer: the gradient "B"
|
|
85
|
+
// (emerald→violet), matching the dashboard BrandedLoader identity.
|
|
86
|
+
brand: '<svg viewBox="0 0 24 24"><defs><linearGradient id="bb-brand-g" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#34d399"/><stop offset="1" stop-color="#8b5cf6"/></linearGradient></defs><rect x="1.5" y="1.5" width="21" height="21" rx="6" fill="url(#bb-brand-g)"/><text x="12" y="17" text-anchor="middle" font-family="ui-sans-serif,system-ui,sans-serif" font-size="14" font-weight="700" fill="#fff">B</text></svg>'
|
|
80
87
|
};
|
|
81
88
|
var svgParser = typeof DOMParser !== "undefined" ? new DOMParser() : null;
|
|
82
89
|
function icon(name) {
|
|
@@ -170,6 +177,11 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
170
177
|
this.pendingText = "";
|
|
171
178
|
this.cardsRow = null;
|
|
172
179
|
this.cardIds = /* @__PURE__ */ new Set();
|
|
180
|
+
/**
|
|
181
|
+
* The large dialog owns the screen — the page behind must not scroll.
|
|
182
|
+
* Locks <body> while a big surface is open; restores on close/collapse.
|
|
183
|
+
*/
|
|
184
|
+
this.prevBodyOverflow = null;
|
|
173
185
|
this.connectionId = options.connectionId;
|
|
174
186
|
this.baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
175
187
|
this.storageKey = `brainerce-bot:${this.connectionId}`;
|
|
@@ -187,6 +199,10 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
187
199
|
}
|
|
188
200
|
destroy() {
|
|
189
201
|
this.destroyed = true;
|
|
202
|
+
if (this.prevBodyOverflow !== null) {
|
|
203
|
+
document.body.style.overflow = this.prevBodyOverflow;
|
|
204
|
+
this.prevBodyOverflow = null;
|
|
205
|
+
}
|
|
190
206
|
this.host?.remove();
|
|
191
207
|
}
|
|
192
208
|
// ---------------------------------------------------------------------------
|
|
@@ -481,6 +497,15 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
481
497
|
.bb-send .bb-ic { width: 17px; height: 17px; ${dir === "rtl" ? "transform: scaleX(-1);" : ""} }
|
|
482
498
|
.bb-send:hover { filter: brightness(1.08); transform: scale(1.06); }
|
|
483
499
|
.bb-send:disabled { opacity: .4; pointer-events: none; }
|
|
500
|
+
|
|
501
|
+
/* Powered-by footer */
|
|
502
|
+
.bb-foot { flex-shrink: 0; display: flex; justify-content: center; align-items: center;
|
|
503
|
+
padding: 5px 12px 7px; background: #fff; }
|
|
504
|
+
.bb-foot a { display: inline-flex; align-items: center; gap: 5px; font-size: 10.5px;
|
|
505
|
+
font-weight: 500; color: #aab0bd; text-decoration: none; letter-spacing: .005em;
|
|
506
|
+
transition: color .12s ease; }
|
|
507
|
+
.bb-foot a:hover { color: #6b7280; }
|
|
508
|
+
.bb-foot .bb-ic { width: 13px; height: 13px; border-radius: 4px; }
|
|
484
509
|
`;
|
|
485
510
|
}
|
|
486
511
|
render(target) {
|
|
@@ -601,6 +626,16 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
601
626
|
composer.appendChild(this.inputEl);
|
|
602
627
|
composer.appendChild(this.sendBtn);
|
|
603
628
|
this.windowEl.appendChild(composer);
|
|
629
|
+
const foot = document.createElement("div");
|
|
630
|
+
foot.className = "bb-foot";
|
|
631
|
+
const credit = document.createElement("a");
|
|
632
|
+
credit.href = "https://brainerce.com";
|
|
633
|
+
credit.target = "_blank";
|
|
634
|
+
credit.rel = "noopener noreferrer";
|
|
635
|
+
credit.appendChild(icon("brand"));
|
|
636
|
+
credit.appendChild(document.createTextNode(this.t("poweredBy")));
|
|
637
|
+
foot.appendChild(credit);
|
|
638
|
+
this.windowEl.appendChild(foot);
|
|
604
639
|
this.launcherEl = document.createElement("button");
|
|
605
640
|
this.launcherEl.className = "bb-launcher";
|
|
606
641
|
this.launcherEl.setAttribute("aria-label", name);
|
|
@@ -640,6 +675,7 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
640
675
|
el?.classList.add("open");
|
|
641
676
|
const takeover = (this.settings.displayMode ?? "floating") === "full_screen" || window.innerWidth <= 520;
|
|
642
677
|
el?.classList.toggle("big", takeover || this.expanded);
|
|
678
|
+
this.syncBodyScroll();
|
|
643
679
|
if (this.messagesEl && this.messagesEl.childElementCount === 0) {
|
|
644
680
|
void this.primeThread();
|
|
645
681
|
}
|
|
@@ -650,11 +686,23 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
650
686
|
const el = this.root?.querySelector(".bb");
|
|
651
687
|
el?.classList.remove("open");
|
|
652
688
|
if (!this.expanded) el?.classList.remove("big");
|
|
689
|
+
this.syncBodyScroll();
|
|
690
|
+
}
|
|
691
|
+
syncBodyScroll() {
|
|
692
|
+
const big = this.opened && !!this.root?.querySelector(".bb")?.classList.contains("big");
|
|
693
|
+
if (big && this.prevBodyOverflow === null) {
|
|
694
|
+
this.prevBodyOverflow = document.body.style.overflow || "";
|
|
695
|
+
document.body.style.overflow = "hidden";
|
|
696
|
+
} else if (!big && this.prevBodyOverflow !== null) {
|
|
697
|
+
document.body.style.overflow = this.prevBodyOverflow;
|
|
698
|
+
this.prevBodyOverflow = null;
|
|
699
|
+
}
|
|
653
700
|
}
|
|
654
701
|
toggleExpand() {
|
|
655
702
|
this.expanded = !this.expanded;
|
|
656
703
|
this.root?.querySelector(".bb")?.classList.toggle("expanded", this.expanded);
|
|
657
704
|
this.root?.querySelector(".bb")?.classList.toggle("big", this.expanded);
|
|
705
|
+
this.syncBodyScroll();
|
|
658
706
|
if (this.expandBtn) {
|
|
659
707
|
this.expandBtn.replaceChildren(icon(this.expanded ? "collapse" : "expand"));
|
|
660
708
|
const label = this.t(this.expanded ? "collapse" : "expand");
|
|
@@ -780,6 +828,9 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
780
828
|
case "card":
|
|
781
829
|
this.appendCard(frame.card);
|
|
782
830
|
return botBubble;
|
|
831
|
+
case "action":
|
|
832
|
+
void this.handleAction(frame);
|
|
833
|
+
return botBubble;
|
|
783
834
|
case "error":
|
|
784
835
|
this.appendMessage("err", frame.message || this.t("error"));
|
|
785
836
|
return botBubble;
|
|
@@ -967,31 +1018,7 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
967
1018
|
async addToCart(card, btn, variantId) {
|
|
968
1019
|
this.beacon(card.botRef);
|
|
969
1020
|
btn.dataset.state = "busy";
|
|
970
|
-
|
|
971
|
-
try {
|
|
972
|
-
if (this.onAddToCart) {
|
|
973
|
-
handled = await this.onAddToCart({
|
|
974
|
-
productId: card.productId,
|
|
975
|
-
variantId: variantId ?? null,
|
|
976
|
-
quantity: 1
|
|
977
|
-
}) !== false;
|
|
978
|
-
} else {
|
|
979
|
-
const ev = new CustomEvent("brainerce:bot:add-to-cart", {
|
|
980
|
-
detail: {
|
|
981
|
-
productId: card.productId,
|
|
982
|
-
variantId: variantId ?? null,
|
|
983
|
-
quantity: 1,
|
|
984
|
-
connectionId: this.connectionId
|
|
985
|
-
},
|
|
986
|
-
cancelable: true,
|
|
987
|
-
bubbles: true,
|
|
988
|
-
composed: true
|
|
989
|
-
});
|
|
990
|
-
handled = !window.dispatchEvent(ev);
|
|
991
|
-
}
|
|
992
|
-
} catch {
|
|
993
|
-
handled = false;
|
|
994
|
-
}
|
|
1021
|
+
const handled = await this.dispatchAdd(card.productId, variantId ?? null);
|
|
995
1022
|
if (handled) {
|
|
996
1023
|
btn.dataset.state = "done";
|
|
997
1024
|
btn.replaceChildren(icon("check"), document.createTextNode(this.t("added")));
|
|
@@ -1006,6 +1033,34 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
1006
1033
|
if (isSafeUrl(card.url)) window.location.href = card.url;
|
|
1007
1034
|
}
|
|
1008
1035
|
}
|
|
1036
|
+
/**
|
|
1037
|
+
* The host-cart chain shared by card buttons and bot-initiated actions:
|
|
1038
|
+
* onAddToCart option -> cancelable CustomEvent. Returns whether a host
|
|
1039
|
+
* took the add.
|
|
1040
|
+
*/
|
|
1041
|
+
async dispatchAdd(productId, variantId) {
|
|
1042
|
+
try {
|
|
1043
|
+
if (this.onAddToCart) {
|
|
1044
|
+
return await this.onAddToCart({ productId, variantId, quantity: 1 }) !== false;
|
|
1045
|
+
}
|
|
1046
|
+
const ev = new CustomEvent("brainerce:bot:add-to-cart", {
|
|
1047
|
+
detail: { productId, variantId, quantity: 1, connectionId: this.connectionId },
|
|
1048
|
+
cancelable: true,
|
|
1049
|
+
bubbles: true,
|
|
1050
|
+
composed: true
|
|
1051
|
+
});
|
|
1052
|
+
return !window.dispatchEvent(ev);
|
|
1053
|
+
} catch {
|
|
1054
|
+
return false;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
/** Bot-initiated widget actions (the model called the addToCart tool). */
|
|
1058
|
+
async handleAction(frame) {
|
|
1059
|
+
if (frame.action !== "add_to_cart") return;
|
|
1060
|
+
if (frame.botRef) this.beacon(frame.botRef);
|
|
1061
|
+
const ok = await this.dispatchAdd(frame.productId, frame.variantId);
|
|
1062
|
+
if (!ok) this.appendMessage("err", this.t("addFailed"));
|
|
1063
|
+
}
|
|
1009
1064
|
/** The durable conversion signal — fire-and-forget, never blocks. */
|
|
1010
1065
|
beacon(botRef) {
|
|
1011
1066
|
try {
|
package/dist/bot/index.mjs
CHANGED
|
@@ -19,7 +19,9 @@ var CHROME = {
|
|
|
19
19
|
added: "Added",
|
|
20
20
|
view: "View",
|
|
21
21
|
chooseOptions: "View product",
|
|
22
|
-
results: "From the store"
|
|
22
|
+
results: "From the store",
|
|
23
|
+
poweredBy: "Powered by Brainerce",
|
|
24
|
+
addFailed: "I couldn\u2019t add that to the cart \u2014 try the button on the product card."
|
|
23
25
|
},
|
|
24
26
|
he: {
|
|
25
27
|
online: "\u05DE\u05D7\u05D5\u05D1\u05E8",
|
|
@@ -38,7 +40,9 @@ var CHROME = {
|
|
|
38
40
|
added: "\u05E0\u05D5\u05E1\u05E3",
|
|
39
41
|
view: "\u05E6\u05E4\u05D9\u05D9\u05D4",
|
|
40
42
|
chooseOptions: "\u05DC\u05E6\u05E4\u05D5\u05EA \u05D1\u05DE\u05D5\u05E6\u05E8",
|
|
41
|
-
results: "\u05DE\u05EA\u05D5\u05DA \u05D4\u05D7\u05E0\u05D5\u05EA"
|
|
43
|
+
results: "\u05DE\u05EA\u05D5\u05DA \u05D4\u05D7\u05E0\u05D5\u05EA",
|
|
44
|
+
poweredBy: "\u05DE\u05D5\u05E4\u05E2\u05DC \u05E2\u05DC \u05D9\u05D3\u05D9 Brainerce",
|
|
45
|
+
addFailed: "\u05DC\u05D0 \u05D4\u05E6\u05DC\u05D7\u05EA\u05D9 \u05DC\u05D4\u05D5\u05E1\u05D9\u05E3 \u05DC\u05E1\u05DC \u2014 \u05E0\u05E1\u05D5 \u05D3\u05E8\u05DA \u05D4\u05DB\u05E4\u05EA\u05D5\u05E8 \u05D1\u05DB\u05E8\u05D8\u05D9\u05E1 \u05D4\u05DE\u05D5\u05E6\u05E8."
|
|
42
46
|
}
|
|
43
47
|
};
|
|
44
48
|
var ICONS = {
|
|
@@ -50,7 +54,10 @@ var ICONS = {
|
|
|
50
54
|
send: '<svg viewBox="0 0 24 24" fill="none"><path d="M4.4 11.2 19 4.6c.7-.3 1.4.4 1.1 1.1l-6.6 14.6c-.3.7-1.3.6-1.5-.1l-1.7-5.4a1 1 0 0 0-.6-.6l-5.4-1.7c-.7-.2-.8-1.2-.1-1.5Z" fill="currentColor"/></svg>',
|
|
51
55
|
cart: '<svg viewBox="0 0 24 24" fill="none"><path d="M3 4h2l2.4 11.2A2 2 0 0 0 9.36 17H17.5a2 2 0 0 0 1.95-1.55L21 8H6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/><circle cx="10" cy="20.5" r="1.4" fill="currentColor"/><circle cx="17" cy="20.5" r="1.4" fill="currentColor"/></svg>',
|
|
52
56
|
check: '<svg viewBox="0 0 24 24" fill="none"><path d="m5 12.5 4.5 4.5L19 7.5" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
53
|
-
arrow: '<svg viewBox="0 0 24 24" fill="none"><path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>'
|
|
57
|
+
arrow: '<svg viewBox="0 0 24 24" fill="none"><path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
58
|
+
// Brainerce brand mark for the Powered-by footer: the gradient "B"
|
|
59
|
+
// (emerald→violet), matching the dashboard BrandedLoader identity.
|
|
60
|
+
brand: '<svg viewBox="0 0 24 24"><defs><linearGradient id="bb-brand-g" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#34d399"/><stop offset="1" stop-color="#8b5cf6"/></linearGradient></defs><rect x="1.5" y="1.5" width="21" height="21" rx="6" fill="url(#bb-brand-g)"/><text x="12" y="17" text-anchor="middle" font-family="ui-sans-serif,system-ui,sans-serif" font-size="14" font-weight="700" fill="#fff">B</text></svg>'
|
|
54
61
|
};
|
|
55
62
|
var svgParser = typeof DOMParser !== "undefined" ? new DOMParser() : null;
|
|
56
63
|
function icon(name) {
|
|
@@ -144,6 +151,11 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
144
151
|
this.pendingText = "";
|
|
145
152
|
this.cardsRow = null;
|
|
146
153
|
this.cardIds = /* @__PURE__ */ new Set();
|
|
154
|
+
/**
|
|
155
|
+
* The large dialog owns the screen — the page behind must not scroll.
|
|
156
|
+
* Locks <body> while a big surface is open; restores on close/collapse.
|
|
157
|
+
*/
|
|
158
|
+
this.prevBodyOverflow = null;
|
|
147
159
|
this.connectionId = options.connectionId;
|
|
148
160
|
this.baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
149
161
|
this.storageKey = `brainerce-bot:${this.connectionId}`;
|
|
@@ -161,6 +173,10 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
161
173
|
}
|
|
162
174
|
destroy() {
|
|
163
175
|
this.destroyed = true;
|
|
176
|
+
if (this.prevBodyOverflow !== null) {
|
|
177
|
+
document.body.style.overflow = this.prevBodyOverflow;
|
|
178
|
+
this.prevBodyOverflow = null;
|
|
179
|
+
}
|
|
164
180
|
this.host?.remove();
|
|
165
181
|
}
|
|
166
182
|
// ---------------------------------------------------------------------------
|
|
@@ -455,6 +471,15 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
455
471
|
.bb-send .bb-ic { width: 17px; height: 17px; ${dir === "rtl" ? "transform: scaleX(-1);" : ""} }
|
|
456
472
|
.bb-send:hover { filter: brightness(1.08); transform: scale(1.06); }
|
|
457
473
|
.bb-send:disabled { opacity: .4; pointer-events: none; }
|
|
474
|
+
|
|
475
|
+
/* Powered-by footer */
|
|
476
|
+
.bb-foot { flex-shrink: 0; display: flex; justify-content: center; align-items: center;
|
|
477
|
+
padding: 5px 12px 7px; background: #fff; }
|
|
478
|
+
.bb-foot a { display: inline-flex; align-items: center; gap: 5px; font-size: 10.5px;
|
|
479
|
+
font-weight: 500; color: #aab0bd; text-decoration: none; letter-spacing: .005em;
|
|
480
|
+
transition: color .12s ease; }
|
|
481
|
+
.bb-foot a:hover { color: #6b7280; }
|
|
482
|
+
.bb-foot .bb-ic { width: 13px; height: 13px; border-radius: 4px; }
|
|
458
483
|
`;
|
|
459
484
|
}
|
|
460
485
|
render(target) {
|
|
@@ -575,6 +600,16 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
575
600
|
composer.appendChild(this.inputEl);
|
|
576
601
|
composer.appendChild(this.sendBtn);
|
|
577
602
|
this.windowEl.appendChild(composer);
|
|
603
|
+
const foot = document.createElement("div");
|
|
604
|
+
foot.className = "bb-foot";
|
|
605
|
+
const credit = document.createElement("a");
|
|
606
|
+
credit.href = "https://brainerce.com";
|
|
607
|
+
credit.target = "_blank";
|
|
608
|
+
credit.rel = "noopener noreferrer";
|
|
609
|
+
credit.appendChild(icon("brand"));
|
|
610
|
+
credit.appendChild(document.createTextNode(this.t("poweredBy")));
|
|
611
|
+
foot.appendChild(credit);
|
|
612
|
+
this.windowEl.appendChild(foot);
|
|
578
613
|
this.launcherEl = document.createElement("button");
|
|
579
614
|
this.launcherEl.className = "bb-launcher";
|
|
580
615
|
this.launcherEl.setAttribute("aria-label", name);
|
|
@@ -614,6 +649,7 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
614
649
|
el?.classList.add("open");
|
|
615
650
|
const takeover = (this.settings.displayMode ?? "floating") === "full_screen" || window.innerWidth <= 520;
|
|
616
651
|
el?.classList.toggle("big", takeover || this.expanded);
|
|
652
|
+
this.syncBodyScroll();
|
|
617
653
|
if (this.messagesEl && this.messagesEl.childElementCount === 0) {
|
|
618
654
|
void this.primeThread();
|
|
619
655
|
}
|
|
@@ -624,11 +660,23 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
624
660
|
const el = this.root?.querySelector(".bb");
|
|
625
661
|
el?.classList.remove("open");
|
|
626
662
|
if (!this.expanded) el?.classList.remove("big");
|
|
663
|
+
this.syncBodyScroll();
|
|
664
|
+
}
|
|
665
|
+
syncBodyScroll() {
|
|
666
|
+
const big = this.opened && !!this.root?.querySelector(".bb")?.classList.contains("big");
|
|
667
|
+
if (big && this.prevBodyOverflow === null) {
|
|
668
|
+
this.prevBodyOverflow = document.body.style.overflow || "";
|
|
669
|
+
document.body.style.overflow = "hidden";
|
|
670
|
+
} else if (!big && this.prevBodyOverflow !== null) {
|
|
671
|
+
document.body.style.overflow = this.prevBodyOverflow;
|
|
672
|
+
this.prevBodyOverflow = null;
|
|
673
|
+
}
|
|
627
674
|
}
|
|
628
675
|
toggleExpand() {
|
|
629
676
|
this.expanded = !this.expanded;
|
|
630
677
|
this.root?.querySelector(".bb")?.classList.toggle("expanded", this.expanded);
|
|
631
678
|
this.root?.querySelector(".bb")?.classList.toggle("big", this.expanded);
|
|
679
|
+
this.syncBodyScroll();
|
|
632
680
|
if (this.expandBtn) {
|
|
633
681
|
this.expandBtn.replaceChildren(icon(this.expanded ? "collapse" : "expand"));
|
|
634
682
|
const label = this.t(this.expanded ? "collapse" : "expand");
|
|
@@ -754,6 +802,9 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
754
802
|
case "card":
|
|
755
803
|
this.appendCard(frame.card);
|
|
756
804
|
return botBubble;
|
|
805
|
+
case "action":
|
|
806
|
+
void this.handleAction(frame);
|
|
807
|
+
return botBubble;
|
|
757
808
|
case "error":
|
|
758
809
|
this.appendMessage("err", frame.message || this.t("error"));
|
|
759
810
|
return botBubble;
|
|
@@ -941,31 +992,7 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
941
992
|
async addToCart(card, btn, variantId) {
|
|
942
993
|
this.beacon(card.botRef);
|
|
943
994
|
btn.dataset.state = "busy";
|
|
944
|
-
|
|
945
|
-
try {
|
|
946
|
-
if (this.onAddToCart) {
|
|
947
|
-
handled = await this.onAddToCart({
|
|
948
|
-
productId: card.productId,
|
|
949
|
-
variantId: variantId ?? null,
|
|
950
|
-
quantity: 1
|
|
951
|
-
}) !== false;
|
|
952
|
-
} else {
|
|
953
|
-
const ev = new CustomEvent("brainerce:bot:add-to-cart", {
|
|
954
|
-
detail: {
|
|
955
|
-
productId: card.productId,
|
|
956
|
-
variantId: variantId ?? null,
|
|
957
|
-
quantity: 1,
|
|
958
|
-
connectionId: this.connectionId
|
|
959
|
-
},
|
|
960
|
-
cancelable: true,
|
|
961
|
-
bubbles: true,
|
|
962
|
-
composed: true
|
|
963
|
-
});
|
|
964
|
-
handled = !window.dispatchEvent(ev);
|
|
965
|
-
}
|
|
966
|
-
} catch {
|
|
967
|
-
handled = false;
|
|
968
|
-
}
|
|
995
|
+
const handled = await this.dispatchAdd(card.productId, variantId ?? null);
|
|
969
996
|
if (handled) {
|
|
970
997
|
btn.dataset.state = "done";
|
|
971
998
|
btn.replaceChildren(icon("check"), document.createTextNode(this.t("added")));
|
|
@@ -980,6 +1007,34 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
980
1007
|
if (isSafeUrl(card.url)) window.location.href = card.url;
|
|
981
1008
|
}
|
|
982
1009
|
}
|
|
1010
|
+
/**
|
|
1011
|
+
* The host-cart chain shared by card buttons and bot-initiated actions:
|
|
1012
|
+
* onAddToCart option -> cancelable CustomEvent. Returns whether a host
|
|
1013
|
+
* took the add.
|
|
1014
|
+
*/
|
|
1015
|
+
async dispatchAdd(productId, variantId) {
|
|
1016
|
+
try {
|
|
1017
|
+
if (this.onAddToCart) {
|
|
1018
|
+
return await this.onAddToCart({ productId, variantId, quantity: 1 }) !== false;
|
|
1019
|
+
}
|
|
1020
|
+
const ev = new CustomEvent("brainerce:bot:add-to-cart", {
|
|
1021
|
+
detail: { productId, variantId, quantity: 1, connectionId: this.connectionId },
|
|
1022
|
+
cancelable: true,
|
|
1023
|
+
bubbles: true,
|
|
1024
|
+
composed: true
|
|
1025
|
+
});
|
|
1026
|
+
return !window.dispatchEvent(ev);
|
|
1027
|
+
} catch {
|
|
1028
|
+
return false;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
/** Bot-initiated widget actions (the model called the addToCart tool). */
|
|
1032
|
+
async handleAction(frame) {
|
|
1033
|
+
if (frame.action !== "add_to_cart") return;
|
|
1034
|
+
if (frame.botRef) this.beacon(frame.botRef);
|
|
1035
|
+
const ok = await this.dispatchAdd(frame.productId, frame.variantId);
|
|
1036
|
+
if (!ok) this.appendMessage("err", this.t("addFailed"));
|
|
1037
|
+
}
|
|
983
1038
|
/** The durable conversion signal — fire-and-forget, never blocks. */
|
|
984
1039
|
beacon(botRef) {
|
|
985
1040
|
try {
|
package/dist/index.d.mts
CHANGED
|
@@ -5168,6 +5168,61 @@ interface ModifierValidationError {
|
|
|
5168
5168
|
/** Modifier the error belongs to (when applicable). */
|
|
5169
5169
|
modifierId?: string;
|
|
5170
5170
|
}
|
|
5171
|
+
type BlogPostStatus = 'DRAFT' | 'SCHEDULED' | 'PUBLISHED';
|
|
5172
|
+
interface BlogPost {
|
|
5173
|
+
id: string;
|
|
5174
|
+
storeId: string;
|
|
5175
|
+
title: string;
|
|
5176
|
+
slug: string;
|
|
5177
|
+
category?: string;
|
|
5178
|
+
content: string;
|
|
5179
|
+
excerpt?: string;
|
|
5180
|
+
coverImageUrl?: string;
|
|
5181
|
+
coverImageAlt?: string;
|
|
5182
|
+
author?: string;
|
|
5183
|
+
tags: string[];
|
|
5184
|
+
metadata: Record<string, unknown>;
|
|
5185
|
+
status: BlogPostStatus;
|
|
5186
|
+
publishedAt?: string;
|
|
5187
|
+
seoTitle?: string;
|
|
5188
|
+
seoDescription?: string;
|
|
5189
|
+
ogImageUrl?: string;
|
|
5190
|
+
createdAt: string;
|
|
5191
|
+
updatedAt: string;
|
|
5192
|
+
}
|
|
5193
|
+
interface CreateBlogPostInput {
|
|
5194
|
+
title: string;
|
|
5195
|
+
slug?: string;
|
|
5196
|
+
category?: string;
|
|
5197
|
+
content?: string;
|
|
5198
|
+
excerpt?: string;
|
|
5199
|
+
coverImageUrl?: string;
|
|
5200
|
+
coverImageAlt?: string;
|
|
5201
|
+
author?: string;
|
|
5202
|
+
tags?: string[];
|
|
5203
|
+
metadata?: Record<string, unknown>;
|
|
5204
|
+
status?: BlogPostStatus;
|
|
5205
|
+
publishedAt?: string | null;
|
|
5206
|
+
seoTitle?: string;
|
|
5207
|
+
seoDescription?: string;
|
|
5208
|
+
ogImageUrl?: string;
|
|
5209
|
+
}
|
|
5210
|
+
type UpdateBlogPostInput = Partial<CreateBlogPostInput>;
|
|
5211
|
+
interface BlogPostListParams {
|
|
5212
|
+
page?: number;
|
|
5213
|
+
limit?: number;
|
|
5214
|
+
category?: string;
|
|
5215
|
+
tag?: string;
|
|
5216
|
+
}
|
|
5217
|
+
interface BlogPostListResponse {
|
|
5218
|
+
data: BlogPost[];
|
|
5219
|
+
meta: {
|
|
5220
|
+
page: number;
|
|
5221
|
+
limit: number;
|
|
5222
|
+
total: number;
|
|
5223
|
+
totalPages: number;
|
|
5224
|
+
};
|
|
5225
|
+
}
|
|
5171
5226
|
interface BrainerceApiError {
|
|
5172
5227
|
statusCode: number;
|
|
5173
5228
|
message: string;
|
|
@@ -6637,6 +6692,42 @@ declare class BrainerceClient {
|
|
|
6637
6692
|
/** Hard delete the row. Admin mode only. */
|
|
6638
6693
|
remove: (id: string) => Promise<void>;
|
|
6639
6694
|
};
|
|
6695
|
+
/**
|
|
6696
|
+
* Read and manage blog posts.
|
|
6697
|
+
*
|
|
6698
|
+
* ```typescript
|
|
6699
|
+
* // Storefront / vibe-coded: list published posts
|
|
6700
|
+
* const { data: posts } = await brainerce.blog.getPosts({ category: 'news' });
|
|
6701
|
+
*
|
|
6702
|
+
* // Fetch one by slug
|
|
6703
|
+
* const post = await brainerce.blog.getPost('my-first-post');
|
|
6704
|
+
*
|
|
6705
|
+
* // Admin: create a draft
|
|
6706
|
+
* const draft = await brainerce.blog.create({ title: 'Hello World' });
|
|
6707
|
+
* ```
|
|
6708
|
+
*/
|
|
6709
|
+
blog: {
|
|
6710
|
+
/**
|
|
6711
|
+
* List published posts. Works in all modes.
|
|
6712
|
+
* Filters: `category`, `tag`, `page`, `limit`.
|
|
6713
|
+
*/
|
|
6714
|
+
getPosts: (params?: BlogPostListParams) => Promise<BlogPostListResponse>;
|
|
6715
|
+
/**
|
|
6716
|
+
* Fetch one published post by its slug. Returns `null` on 404.
|
|
6717
|
+
* Works in all modes.
|
|
6718
|
+
*/
|
|
6719
|
+
getPost: (slug: string) => Promise<BlogPost | null>;
|
|
6720
|
+
/** Create a blog post in DRAFT status. Admin mode only. */
|
|
6721
|
+
create: (input: CreateBlogPostInput) => Promise<BlogPost>;
|
|
6722
|
+
/** Update a blog post by ID. Admin mode only. */
|
|
6723
|
+
update: (id: string, input: UpdateBlogPostInput) => Promise<BlogPost>;
|
|
6724
|
+
/** Transition status → PUBLISHED (sets publishedAt = now if unset). Admin mode only. */
|
|
6725
|
+
publish: (id: string) => Promise<BlogPost>;
|
|
6726
|
+
/** Transition status PUBLISHED → DRAFT. Admin mode only. */
|
|
6727
|
+
unpublish: (id: string) => Promise<BlogPost>;
|
|
6728
|
+
/** Hard-delete a blog post. Admin mode only. */
|
|
6729
|
+
remove: (id: string) => Promise<void>;
|
|
6730
|
+
};
|
|
6640
6731
|
/**
|
|
6641
6732
|
* Submit a contact inquiry from a storefront contact form.
|
|
6642
6733
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -5168,6 +5168,61 @@ interface ModifierValidationError {
|
|
|
5168
5168
|
/** Modifier the error belongs to (when applicable). */
|
|
5169
5169
|
modifierId?: string;
|
|
5170
5170
|
}
|
|
5171
|
+
type BlogPostStatus = 'DRAFT' | 'SCHEDULED' | 'PUBLISHED';
|
|
5172
|
+
interface BlogPost {
|
|
5173
|
+
id: string;
|
|
5174
|
+
storeId: string;
|
|
5175
|
+
title: string;
|
|
5176
|
+
slug: string;
|
|
5177
|
+
category?: string;
|
|
5178
|
+
content: string;
|
|
5179
|
+
excerpt?: string;
|
|
5180
|
+
coverImageUrl?: string;
|
|
5181
|
+
coverImageAlt?: string;
|
|
5182
|
+
author?: string;
|
|
5183
|
+
tags: string[];
|
|
5184
|
+
metadata: Record<string, unknown>;
|
|
5185
|
+
status: BlogPostStatus;
|
|
5186
|
+
publishedAt?: string;
|
|
5187
|
+
seoTitle?: string;
|
|
5188
|
+
seoDescription?: string;
|
|
5189
|
+
ogImageUrl?: string;
|
|
5190
|
+
createdAt: string;
|
|
5191
|
+
updatedAt: string;
|
|
5192
|
+
}
|
|
5193
|
+
interface CreateBlogPostInput {
|
|
5194
|
+
title: string;
|
|
5195
|
+
slug?: string;
|
|
5196
|
+
category?: string;
|
|
5197
|
+
content?: string;
|
|
5198
|
+
excerpt?: string;
|
|
5199
|
+
coverImageUrl?: string;
|
|
5200
|
+
coverImageAlt?: string;
|
|
5201
|
+
author?: string;
|
|
5202
|
+
tags?: string[];
|
|
5203
|
+
metadata?: Record<string, unknown>;
|
|
5204
|
+
status?: BlogPostStatus;
|
|
5205
|
+
publishedAt?: string | null;
|
|
5206
|
+
seoTitle?: string;
|
|
5207
|
+
seoDescription?: string;
|
|
5208
|
+
ogImageUrl?: string;
|
|
5209
|
+
}
|
|
5210
|
+
type UpdateBlogPostInput = Partial<CreateBlogPostInput>;
|
|
5211
|
+
interface BlogPostListParams {
|
|
5212
|
+
page?: number;
|
|
5213
|
+
limit?: number;
|
|
5214
|
+
category?: string;
|
|
5215
|
+
tag?: string;
|
|
5216
|
+
}
|
|
5217
|
+
interface BlogPostListResponse {
|
|
5218
|
+
data: BlogPost[];
|
|
5219
|
+
meta: {
|
|
5220
|
+
page: number;
|
|
5221
|
+
limit: number;
|
|
5222
|
+
total: number;
|
|
5223
|
+
totalPages: number;
|
|
5224
|
+
};
|
|
5225
|
+
}
|
|
5171
5226
|
interface BrainerceApiError {
|
|
5172
5227
|
statusCode: number;
|
|
5173
5228
|
message: string;
|
|
@@ -6637,6 +6692,42 @@ declare class BrainerceClient {
|
|
|
6637
6692
|
/** Hard delete the row. Admin mode only. */
|
|
6638
6693
|
remove: (id: string) => Promise<void>;
|
|
6639
6694
|
};
|
|
6695
|
+
/**
|
|
6696
|
+
* Read and manage blog posts.
|
|
6697
|
+
*
|
|
6698
|
+
* ```typescript
|
|
6699
|
+
* // Storefront / vibe-coded: list published posts
|
|
6700
|
+
* const { data: posts } = await brainerce.blog.getPosts({ category: 'news' });
|
|
6701
|
+
*
|
|
6702
|
+
* // Fetch one by slug
|
|
6703
|
+
* const post = await brainerce.blog.getPost('my-first-post');
|
|
6704
|
+
*
|
|
6705
|
+
* // Admin: create a draft
|
|
6706
|
+
* const draft = await brainerce.blog.create({ title: 'Hello World' });
|
|
6707
|
+
* ```
|
|
6708
|
+
*/
|
|
6709
|
+
blog: {
|
|
6710
|
+
/**
|
|
6711
|
+
* List published posts. Works in all modes.
|
|
6712
|
+
* Filters: `category`, `tag`, `page`, `limit`.
|
|
6713
|
+
*/
|
|
6714
|
+
getPosts: (params?: BlogPostListParams) => Promise<BlogPostListResponse>;
|
|
6715
|
+
/**
|
|
6716
|
+
* Fetch one published post by its slug. Returns `null` on 404.
|
|
6717
|
+
* Works in all modes.
|
|
6718
|
+
*/
|
|
6719
|
+
getPost: (slug: string) => Promise<BlogPost | null>;
|
|
6720
|
+
/** Create a blog post in DRAFT status. Admin mode only. */
|
|
6721
|
+
create: (input: CreateBlogPostInput) => Promise<BlogPost>;
|
|
6722
|
+
/** Update a blog post by ID. Admin mode only. */
|
|
6723
|
+
update: (id: string, input: UpdateBlogPostInput) => Promise<BlogPost>;
|
|
6724
|
+
/** Transition status → PUBLISHED (sets publishedAt = now if unset). Admin mode only. */
|
|
6725
|
+
publish: (id: string) => Promise<BlogPost>;
|
|
6726
|
+
/** Transition status PUBLISHED → DRAFT. Admin mode only. */
|
|
6727
|
+
unpublish: (id: string) => Promise<BlogPost>;
|
|
6728
|
+
/** Hard-delete a blog post. Admin mode only. */
|
|
6729
|
+
remove: (id: string) => Promise<void>;
|
|
6730
|
+
};
|
|
6640
6731
|
/**
|
|
6641
6732
|
* Submit a contact inquiry from a storefront contact form.
|
|
6642
6733
|
*
|
package/dist/index.js
CHANGED
|
@@ -489,6 +489,120 @@ var BrainerceClient = class {
|
|
|
489
489
|
remove: removeById
|
|
490
490
|
};
|
|
491
491
|
})();
|
|
492
|
+
// -------------------- Blog --------------------
|
|
493
|
+
/**
|
|
494
|
+
* Read and manage blog posts.
|
|
495
|
+
*
|
|
496
|
+
* ```typescript
|
|
497
|
+
* // Storefront / vibe-coded: list published posts
|
|
498
|
+
* const { data: posts } = await brainerce.blog.getPosts({ category: 'news' });
|
|
499
|
+
*
|
|
500
|
+
* // Fetch one by slug
|
|
501
|
+
* const post = await brainerce.blog.getPost('my-first-post');
|
|
502
|
+
*
|
|
503
|
+
* // Admin: create a draft
|
|
504
|
+
* const draft = await brainerce.blog.create({ title: 'Hello World' });
|
|
505
|
+
* ```
|
|
506
|
+
*/
|
|
507
|
+
this.blog = /* @__PURE__ */ (() => {
|
|
508
|
+
const publicBase = "/blog/posts";
|
|
509
|
+
const adminBase = "/blog/posts";
|
|
510
|
+
const requireAdmin = (action) => {
|
|
511
|
+
if (this.isVibeCodedMode() || this.storeId && !this.apiKey) {
|
|
512
|
+
throw new BrainerceError(
|
|
513
|
+
`client.blog.${action}() requires admin mode (apiKey). Vibe-coded and storefront modes are read-only.`,
|
|
514
|
+
403
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
const onNotFound = (err) => {
|
|
519
|
+
if (err instanceof BrainerceError && err.statusCode === 404) return null;
|
|
520
|
+
throw err;
|
|
521
|
+
};
|
|
522
|
+
const buildQuery = (params) => Object.fromEntries(
|
|
523
|
+
Object.entries(params).filter(([, v]) => v != null).map(([k, v]) => [k, String(v)])
|
|
524
|
+
);
|
|
525
|
+
return {
|
|
526
|
+
/**
|
|
527
|
+
* List published posts. Works in all modes.
|
|
528
|
+
* Filters: `category`, `tag`, `page`, `limit`.
|
|
529
|
+
*/
|
|
530
|
+
getPosts: (params = {}) => {
|
|
531
|
+
const query = buildQuery(params);
|
|
532
|
+
if (this.isVibeCodedMode()) {
|
|
533
|
+
return this.vibeCodedRequest(
|
|
534
|
+
"GET",
|
|
535
|
+
publicBase,
|
|
536
|
+
void 0,
|
|
537
|
+
query
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
if (this.storeId && !this.apiKey) {
|
|
541
|
+
return this.storefrontRequest(
|
|
542
|
+
"GET",
|
|
543
|
+
publicBase,
|
|
544
|
+
void 0,
|
|
545
|
+
query
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
return this.adminRequest(
|
|
549
|
+
"GET",
|
|
550
|
+
adminBase,
|
|
551
|
+
void 0,
|
|
552
|
+
query
|
|
553
|
+
);
|
|
554
|
+
},
|
|
555
|
+
/**
|
|
556
|
+
* Fetch one published post by its slug. Returns `null` on 404.
|
|
557
|
+
* Works in all modes.
|
|
558
|
+
*/
|
|
559
|
+
getPost: (slug) => {
|
|
560
|
+
const path = `${publicBase}/${encodeURIComponent(slug)}`;
|
|
561
|
+
if (this.isVibeCodedMode()) {
|
|
562
|
+
return this.vibeCodedRequest("GET", path).catch(onNotFound);
|
|
563
|
+
}
|
|
564
|
+
if (this.storeId && !this.apiKey) {
|
|
565
|
+
return this.storefrontRequest("GET", path).catch(onNotFound);
|
|
566
|
+
}
|
|
567
|
+
return this.adminRequest("GET", path).catch(onNotFound);
|
|
568
|
+
},
|
|
569
|
+
/** Create a blog post in DRAFT status. Admin mode only. */
|
|
570
|
+
create: (input) => {
|
|
571
|
+
requireAdmin("create");
|
|
572
|
+
return this.adminRequest("POST", adminBase, input);
|
|
573
|
+
},
|
|
574
|
+
/** Update a blog post by ID. Admin mode only. */
|
|
575
|
+
update: (id, input) => {
|
|
576
|
+
requireAdmin("update");
|
|
577
|
+
return this.adminRequest(
|
|
578
|
+
"PATCH",
|
|
579
|
+
`${adminBase}/${encodeURIComponent(id)}`,
|
|
580
|
+
input
|
|
581
|
+
);
|
|
582
|
+
},
|
|
583
|
+
/** Transition status → PUBLISHED (sets publishedAt = now if unset). Admin mode only. */
|
|
584
|
+
publish: (id) => {
|
|
585
|
+
requireAdmin("publish");
|
|
586
|
+
return this.adminRequest(
|
|
587
|
+
"POST",
|
|
588
|
+
`${adminBase}/${encodeURIComponent(id)}/publish`
|
|
589
|
+
);
|
|
590
|
+
},
|
|
591
|
+
/** Transition status PUBLISHED → DRAFT. Admin mode only. */
|
|
592
|
+
unpublish: (id) => {
|
|
593
|
+
requireAdmin("unpublish");
|
|
594
|
+
return this.adminRequest(
|
|
595
|
+
"POST",
|
|
596
|
+
`${adminBase}/${encodeURIComponent(id)}/unpublish`
|
|
597
|
+
);
|
|
598
|
+
},
|
|
599
|
+
/** Hard-delete a blog post. Admin mode only. */
|
|
600
|
+
remove: async (id) => {
|
|
601
|
+
requireAdmin("remove");
|
|
602
|
+
await this.adminRequest("DELETE", `${adminBase}/${encodeURIComponent(id)}`);
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
})();
|
|
492
606
|
// -------------------- Local Cart (Client-Side for Guests) --------------------
|
|
493
607
|
// These methods store cart data in localStorage - NO API calls!
|
|
494
608
|
// Use for guest users in vibe-coded sites
|
|
@@ -8112,6 +8226,9 @@ var ALLOWED_PAYMENT_HOSTS = [
|
|
|
8112
8226
|
"grow.security",
|
|
8113
8227
|
// CreditGuard
|
|
8114
8228
|
"creditguard.co.il",
|
|
8229
|
+
// Takbull
|
|
8230
|
+
"takbull.co.il",
|
|
8231
|
+
"api.takbull.co.il",
|
|
8115
8232
|
// Brainerce-hosted payment embeds (backend payment-embed proxy at
|
|
8116
8233
|
// `/api/payment/embed/...` that fronts provider apps' embed shells —
|
|
8117
8234
|
// e.g. cardcom-payments OpenFields wrapper). The match also covers
|
package/dist/index.mjs
CHANGED
|
@@ -419,6 +419,120 @@ var BrainerceClient = class {
|
|
|
419
419
|
remove: removeById
|
|
420
420
|
};
|
|
421
421
|
})();
|
|
422
|
+
// -------------------- Blog --------------------
|
|
423
|
+
/**
|
|
424
|
+
* Read and manage blog posts.
|
|
425
|
+
*
|
|
426
|
+
* ```typescript
|
|
427
|
+
* // Storefront / vibe-coded: list published posts
|
|
428
|
+
* const { data: posts } = await brainerce.blog.getPosts({ category: 'news' });
|
|
429
|
+
*
|
|
430
|
+
* // Fetch one by slug
|
|
431
|
+
* const post = await brainerce.blog.getPost('my-first-post');
|
|
432
|
+
*
|
|
433
|
+
* // Admin: create a draft
|
|
434
|
+
* const draft = await brainerce.blog.create({ title: 'Hello World' });
|
|
435
|
+
* ```
|
|
436
|
+
*/
|
|
437
|
+
this.blog = /* @__PURE__ */ (() => {
|
|
438
|
+
const publicBase = "/blog/posts";
|
|
439
|
+
const adminBase = "/blog/posts";
|
|
440
|
+
const requireAdmin = (action) => {
|
|
441
|
+
if (this.isVibeCodedMode() || this.storeId && !this.apiKey) {
|
|
442
|
+
throw new BrainerceError(
|
|
443
|
+
`client.blog.${action}() requires admin mode (apiKey). Vibe-coded and storefront modes are read-only.`,
|
|
444
|
+
403
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
const onNotFound = (err) => {
|
|
449
|
+
if (err instanceof BrainerceError && err.statusCode === 404) return null;
|
|
450
|
+
throw err;
|
|
451
|
+
};
|
|
452
|
+
const buildQuery = (params) => Object.fromEntries(
|
|
453
|
+
Object.entries(params).filter(([, v]) => v != null).map(([k, v]) => [k, String(v)])
|
|
454
|
+
);
|
|
455
|
+
return {
|
|
456
|
+
/**
|
|
457
|
+
* List published posts. Works in all modes.
|
|
458
|
+
* Filters: `category`, `tag`, `page`, `limit`.
|
|
459
|
+
*/
|
|
460
|
+
getPosts: (params = {}) => {
|
|
461
|
+
const query = buildQuery(params);
|
|
462
|
+
if (this.isVibeCodedMode()) {
|
|
463
|
+
return this.vibeCodedRequest(
|
|
464
|
+
"GET",
|
|
465
|
+
publicBase,
|
|
466
|
+
void 0,
|
|
467
|
+
query
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
if (this.storeId && !this.apiKey) {
|
|
471
|
+
return this.storefrontRequest(
|
|
472
|
+
"GET",
|
|
473
|
+
publicBase,
|
|
474
|
+
void 0,
|
|
475
|
+
query
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
return this.adminRequest(
|
|
479
|
+
"GET",
|
|
480
|
+
adminBase,
|
|
481
|
+
void 0,
|
|
482
|
+
query
|
|
483
|
+
);
|
|
484
|
+
},
|
|
485
|
+
/**
|
|
486
|
+
* Fetch one published post by its slug. Returns `null` on 404.
|
|
487
|
+
* Works in all modes.
|
|
488
|
+
*/
|
|
489
|
+
getPost: (slug) => {
|
|
490
|
+
const path = `${publicBase}/${encodeURIComponent(slug)}`;
|
|
491
|
+
if (this.isVibeCodedMode()) {
|
|
492
|
+
return this.vibeCodedRequest("GET", path).catch(onNotFound);
|
|
493
|
+
}
|
|
494
|
+
if (this.storeId && !this.apiKey) {
|
|
495
|
+
return this.storefrontRequest("GET", path).catch(onNotFound);
|
|
496
|
+
}
|
|
497
|
+
return this.adminRequest("GET", path).catch(onNotFound);
|
|
498
|
+
},
|
|
499
|
+
/** Create a blog post in DRAFT status. Admin mode only. */
|
|
500
|
+
create: (input) => {
|
|
501
|
+
requireAdmin("create");
|
|
502
|
+
return this.adminRequest("POST", adminBase, input);
|
|
503
|
+
},
|
|
504
|
+
/** Update a blog post by ID. Admin mode only. */
|
|
505
|
+
update: (id, input) => {
|
|
506
|
+
requireAdmin("update");
|
|
507
|
+
return this.adminRequest(
|
|
508
|
+
"PATCH",
|
|
509
|
+
`${adminBase}/${encodeURIComponent(id)}`,
|
|
510
|
+
input
|
|
511
|
+
);
|
|
512
|
+
},
|
|
513
|
+
/** Transition status → PUBLISHED (sets publishedAt = now if unset). Admin mode only. */
|
|
514
|
+
publish: (id) => {
|
|
515
|
+
requireAdmin("publish");
|
|
516
|
+
return this.adminRequest(
|
|
517
|
+
"POST",
|
|
518
|
+
`${adminBase}/${encodeURIComponent(id)}/publish`
|
|
519
|
+
);
|
|
520
|
+
},
|
|
521
|
+
/** Transition status PUBLISHED → DRAFT. Admin mode only. */
|
|
522
|
+
unpublish: (id) => {
|
|
523
|
+
requireAdmin("unpublish");
|
|
524
|
+
return this.adminRequest(
|
|
525
|
+
"POST",
|
|
526
|
+
`${adminBase}/${encodeURIComponent(id)}/unpublish`
|
|
527
|
+
);
|
|
528
|
+
},
|
|
529
|
+
/** Hard-delete a blog post. Admin mode only. */
|
|
530
|
+
remove: async (id) => {
|
|
531
|
+
requireAdmin("remove");
|
|
532
|
+
await this.adminRequest("DELETE", `${adminBase}/${encodeURIComponent(id)}`);
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
})();
|
|
422
536
|
// -------------------- Local Cart (Client-Side for Guests) --------------------
|
|
423
537
|
// These methods store cart data in localStorage - NO API calls!
|
|
424
538
|
// Use for guest users in vibe-coded sites
|
|
@@ -8042,6 +8156,9 @@ var ALLOWED_PAYMENT_HOSTS = [
|
|
|
8042
8156
|
"grow.security",
|
|
8043
8157
|
// CreditGuard
|
|
8044
8158
|
"creditguard.co.il",
|
|
8159
|
+
// Takbull
|
|
8160
|
+
"takbull.co.il",
|
|
8161
|
+
"api.takbull.co.il",
|
|
8045
8162
|
// Brainerce-hosted payment embeds (backend payment-embed proxy at
|
|
8046
8163
|
// `/api/payment/embed/...` that fronts provider apps' embed shells —
|
|
8047
8164
|
// e.g. cardcom-payments OpenFields wrapper). The match also covers
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brainerce",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.34.0",
|
|
4
4
|
"description": "Official SDK for building e-commerce storefronts with Brainerce Platform. Perfect for vibe-coded sites, AI-built stores (Cursor, Lovable, v0), and custom storefronts.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|