@bootdesk/js-web-adapter-react 0.3.4 → 0.3.5

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/dist/index.cjs CHANGED
@@ -599,29 +599,101 @@ function useAttachmentUpload(uploadConfig) {
599
599
 
600
600
  // src/hooks/useBridge.ts
601
601
  var import_react7 = require("react");
602
+ function getBridge() {
603
+ return typeof window !== "undefined" ? window.__chatBridge : null;
604
+ }
605
+ function hasNativeBridge() {
606
+ if (typeof window === "undefined") return false;
607
+ return !!getBridge() || !!window.webkit?.messageHandlers?.chatBridge || !!window.ReactNativeWebView || !!window.AndroidBridge;
608
+ }
609
+ function bridgeSend(msg) {
610
+ const bridge = getBridge();
611
+ if (bridge?.send) {
612
+ bridge.send(msg);
613
+ } else if (typeof window !== "undefined") {
614
+ window.parent.postMessage(msg, "*");
615
+ }
616
+ }
602
617
  function useBridge() {
603
618
  const notificationCbRef = (0, import_react7.useRef)(null);
604
619
  const [config, setConfig] = (0, import_react7.useState)(null);
620
+ const [pushState, setPushState] = (0, import_react7.useState)(null);
605
621
  const isInIframe = typeof window !== "undefined" && window !== window.parent;
622
+ const isInWebView = !isInIframe && hasNativeBridge();
606
623
  const notifyMessage = (0, import_react7.useCallback)(
607
624
  (text) => {
608
- if (!isInIframe) return;
609
- window.parent.postMessage({ type: "chat-message", text }, "*");
625
+ if (!isInIframe && !isInWebView) return;
626
+ if (isInWebView) {
627
+ bridgeSend({ type: "chat-message", text });
628
+ } else {
629
+ window.parent.postMessage({ type: "chat-message", text }, "*");
630
+ }
610
631
  },
611
- [isInIframe]
632
+ [isInIframe, isInWebView]
612
633
  );
613
634
  const notifyViewportConfig = (0, import_react7.useCallback)(
614
635
  (viewportContent) => {
615
- if (!isInIframe) return;
616
- window.parent.postMessage({ type: "chat-viewport-config", content: viewportContent }, "*");
636
+ if (!isInIframe && !isInWebView) return;
637
+ if (isInWebView) {
638
+ bridgeSend({ type: "chat-viewport-config", content: viewportContent });
639
+ } else {
640
+ window.parent.postMessage({ type: "chat-viewport-config", content: viewportContent }, "*");
641
+ }
617
642
  },
618
- [isInIframe]
643
+ [isInIframe, isInWebView]
619
644
  );
620
645
  const onNotificationClicked = (0, import_react7.useCallback)((cb) => {
621
646
  notificationCbRef.current = cb;
622
647
  }, []);
648
+ const requestPushSubscribe = (0, import_react7.useCallback)(() => {
649
+ if (!isInIframe && !isInWebView) return;
650
+ if (isInWebView) {
651
+ bridgeSend({ type: "chat-push-subscribe" });
652
+ } else {
653
+ window.parent.postMessage({ type: "chat-push-subscribe" }, "*");
654
+ }
655
+ }, [isInIframe, isInWebView]);
656
+ const requestPushUnsubscribe = (0, import_react7.useCallback)(() => {
657
+ if (!isInIframe && !isInWebView) return;
658
+ if (isInWebView) {
659
+ bridgeSend({ type: "chat-push-unsubscribe" });
660
+ } else {
661
+ window.parent.postMessage({ type: "chat-push-unsubscribe" }, "*");
662
+ }
663
+ }, [isInIframe, isInWebView]);
664
+ const bridgeInScope = typeof window !== "undefined" ? getBridge() : null;
665
+ (0, import_react7.useEffect)(() => {
666
+ if (bridgeInScope?._pushState) {
667
+ setPushState(bridgeInScope._pushState);
668
+ }
669
+ }, [bridgeInScope?._pushState]);
670
+ (0, import_react7.useEffect)(() => {
671
+ if (!isInIframe && !isInWebView) return;
672
+ if (!getBridge()?._ready) {
673
+ const bridge = getBridge();
674
+ if (bridge) {
675
+ bridge._ready = true;
676
+ bridgeSend({ type: "chat-ready" });
677
+ } else {
678
+ if (!window.__chatBridge) {
679
+ window.__chatBridge = {};
680
+ }
681
+ window.__chatBridge._ready = true;
682
+ if (!window.__chatBridge.send) {
683
+ window.__chatBridge.send = function(msg) {
684
+ if (window.ReactNativeWebView?.postMessage) {
685
+ window.ReactNativeWebView.postMessage(JSON.stringify(msg));
686
+ } else {
687
+ window.parent.postMessage(msg, "*");
688
+ }
689
+ };
690
+ }
691
+ window.__chatBridge.send({ type: "chat-ready" });
692
+ }
693
+ }
694
+ }, [isInIframe, isInWebView]);
623
695
  (0, import_react7.useEffect)(() => {
624
- if (!isInIframe) return;
696
+ if (!isInIframe && !isInWebView) return;
625
697
  function handleMessage(event) {
626
698
  const data = event.data;
627
699
  if (!data || typeof data !== "object" || !data.type) return;
@@ -633,11 +705,43 @@ function useBridge() {
633
705
  if (data.type === "chat-notification-clicked") {
634
706
  notificationCbRef.current?.();
635
707
  }
708
+ if (data.type === "chat-push-state" && typeof data.status === "string") {
709
+ setPushState(data.status);
710
+ }
711
+ }
712
+ function handleCustomEvent(event) {
713
+ const detail = event.detail;
714
+ if (!detail || typeof detail !== "object") return;
715
+ if (detail.type === "chat-config") {
716
+ const configData = { ...detail };
717
+ delete configData.type;
718
+ setConfig(configData);
719
+ }
720
+ if (detail.type === "chat-notification-clicked") {
721
+ notificationCbRef.current?.();
722
+ }
723
+ if (detail.type === "chat-push-state" && typeof detail.status === "string") {
724
+ setPushState(detail.status);
725
+ }
636
726
  }
637
727
  window.addEventListener("message", handleMessage);
638
- return () => window.removeEventListener("message", handleMessage);
639
- }, [isInIframe]);
640
- return { config, isInIframe, notifyMessage, notifyViewportConfig, onNotificationClicked };
728
+ window.addEventListener("chat-bridge", handleCustomEvent);
729
+ return () => {
730
+ window.removeEventListener("message", handleMessage);
731
+ window.removeEventListener("chat-bridge", handleCustomEvent);
732
+ };
733
+ }, [isInIframe, isInWebView]);
734
+ return {
735
+ config,
736
+ isInIframe,
737
+ isInWebView,
738
+ notifyMessage,
739
+ notifyViewportConfig,
740
+ onNotificationClicked,
741
+ pushState,
742
+ requestPushSubscribe,
743
+ requestPushUnsubscribe
744
+ };
641
745
  }
642
746
 
643
747
  // src/i18n/LocaleProvider.tsx
@@ -4037,17 +4141,23 @@ function ChatWidget({
4037
4141
  const {
4038
4142
  config: iframeConfig,
4039
4143
  isInIframe,
4144
+ isInWebView,
4040
4145
  notifyMessage,
4041
4146
  notifyViewportConfig,
4042
- onNotificationClicked
4147
+ onNotificationClicked,
4148
+ pushState: bridgePushState,
4149
+ requestPushSubscribe,
4150
+ requestPushUnsubscribe
4043
4151
  } = useBridge();
4044
- const effectiveLocale = localeProp ?? (isInIframe ? iframeConfig?.locale : void 0) ?? useLocale().locale;
4152
+ const hasBridgePush = (isInIframe || isInWebView) && bridgePushState !== null;
4153
+ const inBridge = isInIframe || isInWebView;
4154
+ const effectiveLocale = localeProp ?? (inBridge ? iframeConfig?.locale : void 0) ?? useLocale().locale;
4045
4155
  const merged = mergeLocale(effectiveLocale);
4046
4156
  const dir = merged.direction;
4047
4157
  (0, import_react14.useEffect)(() => {
4048
4158
  client.setLocaleHeader(effectiveLocale);
4049
4159
  }, [client, effectiveLocale]);
4050
- const autoEmbedded = isInIframe && embedded !== false;
4160
+ const autoEmbedded = inBridge && embedded !== false;
4051
4161
  const effectiveEmbedded = embedded === true || autoEmbedded;
4052
4162
  const effectiveMode = effectiveEmbedded ? "embedded" : initialMode;
4053
4163
  const [theme, setTheme] = (0, import_react14.useState)(() => {
@@ -4118,7 +4228,7 @@ function ChatWidget({
4118
4228
  if (!isSmallScreen) setIsOpen(true);
4119
4229
  }, [initialMode, isSmallScreen]);
4120
4230
  (0, import_react14.useEffect)(() => {
4121
- if (isInIframe) {
4231
+ if (inBridge) {
4122
4232
  notifyViewportConfig("interactive-widget=resizes-content");
4123
4233
  return () => notifyViewportConfig("");
4124
4234
  }
@@ -4135,14 +4245,15 @@ function ChatWidget({
4135
4245
  meta.setAttribute("content", original);
4136
4246
  };
4137
4247
  }
4138
- }, [isOpen, displayMode, isSmallScreen, isInIframe, notifyViewportConfig]);
4248
+ }, [isOpen, displayMode, isSmallScreen, inBridge, notifyViewportConfig]);
4139
4249
  const { messages, sendMessage, loading, isLoadingHistory, reloadMessages } = useMessages(
4140
4250
  client,
4141
4251
  (isOpen || effectiveEmbedded) && !isPreEntry
4142
4252
  );
4143
4253
  const { isSomeoneTyping } = useTyping(client);
4254
+ const webPushEnabled = !!pushConfig && !hasBridgePush;
4144
4255
  const push = usePushNotifications({
4145
- enabled: !!pushConfig,
4256
+ enabled: webPushEnabled,
4146
4257
  getVapidPublicKey: pushConfig?.getVapidPublicKey ?? (() => Promise.resolve("")),
4147
4258
  onSubscribe: pushConfig?.onSubscribe ?? (async () => {
4148
4259
  }),
@@ -4154,11 +4265,24 @@ function ChatWidget({
4154
4265
  notificationOptions: pushConfig?.notificationOptions
4155
4266
  });
4156
4267
  const handlePushToggle = (0, import_react14.useCallback)(() => {
4157
- if (push.isSubscribed) push.unsubscribe();
4158
- else push.subscribe();
4159
- }, [push.isSubscribed, push.subscribe, push.unsubscribe]);
4268
+ if (hasBridgePush) {
4269
+ if (bridgePushState === "subscribed") requestPushUnsubscribe();
4270
+ else requestPushSubscribe();
4271
+ } else {
4272
+ if (push.isSubscribed) push.unsubscribe();
4273
+ else push.subscribe();
4274
+ }
4275
+ }, [
4276
+ hasBridgePush,
4277
+ bridgePushState,
4278
+ requestPushSubscribe,
4279
+ requestPushUnsubscribe,
4280
+ push.isSubscribed,
4281
+ push.subscribe,
4282
+ push.unsubscribe
4283
+ ]);
4160
4284
  (0, import_react14.useEffect)(() => {
4161
- if (!isInIframe || !iframeConfig) return;
4285
+ if (!inBridge || !iframeConfig) return;
4162
4286
  if (iframeConfig.theme?.cssVariables) {
4163
4287
  const root = document.documentElement;
4164
4288
  for (const [key, value] of Object.entries(iframeConfig.theme.cssVariables)) {
@@ -4169,24 +4293,24 @@ function ChatWidget({
4169
4293
  if (mode === "light" || mode === "dark" || mode === "auto") {
4170
4294
  setTheme(mode);
4171
4295
  }
4172
- }, [isInIframe, iframeConfig]);
4296
+ }, [inBridge, iframeConfig]);
4173
4297
  (0, import_react14.useEffect)(() => {
4174
- if (!isInIframe) return;
4298
+ if (!inBridge) return;
4175
4299
  onNotificationClicked(() => {
4176
4300
  reloadMessages();
4177
4301
  });
4178
- }, [isInIframe, onNotificationClicked, reloadMessages]);
4179
- const effectiveTitle = isInIframe && iframeConfig?.title || title;
4180
- const effectivePlaceholder = isInIframe && iframeConfig?.placeholder || placeholder || merged.chatWidget.placeholder;
4302
+ }, [inBridge, onNotificationClicked, reloadMessages]);
4303
+ const effectiveTitle = inBridge && iframeConfig?.title || title;
4304
+ const effectivePlaceholder = inBridge && iframeConfig?.placeholder || placeholder || merged.chatWidget.placeholder;
4181
4305
  const currentUserId = "getCurrentUserId" in client ? client.getCurrentUserId() : "";
4182
4306
  const handleSend = (0, import_react14.useCallback)(
4183
4307
  async (text, attachments = []) => {
4184
4308
  await sendMessage(text, attachments);
4185
- if (isInIframe) {
4309
+ if (inBridge) {
4186
4310
  notifyMessage(text);
4187
4311
  }
4188
4312
  },
4189
- [sendMessage, isInIframe, notifyMessage]
4313
+ [sendMessage, inBridge, notifyMessage]
4190
4314
  );
4191
4315
  const handleActionClick = (0, import_react14.useCallback)(
4192
4316
  (messageId, actionId, value) => {
@@ -4211,19 +4335,26 @@ function ChatWidget({
4211
4335
  const close = (0, import_react14.useCallback)(() => {
4212
4336
  setIsOpen(false);
4213
4337
  setDisplayMode("floating");
4338
+ if (isInWebView) {
4339
+ window.__chatBridge?.send?.({ type: "chat-close" });
4340
+ } else if (isInIframe) {
4341
+ window.parent.postMessage({ type: "chat-close" }, "*");
4342
+ }
4214
4343
  onClose?.();
4215
- }, [onClose]);
4344
+ }, [onClose, isInIframe, isInWebView]);
4216
4345
  const embeddedClose = (0, import_react14.useCallback)(() => {
4217
- if (isInIframe) {
4346
+ if (isInWebView) {
4347
+ window.__chatBridge?.send?.({ type: "chat-close" });
4348
+ } else if (isInIframe) {
4218
4349
  window.parent.postMessage({ type: "chat-close" }, "*");
4219
4350
  }
4220
- }, [isInIframe]);
4351
+ }, [isInIframe, isInWebView]);
4221
4352
  const panelContent = /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
4222
4353
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4223
4354
  Header,
4224
4355
  {
4225
4356
  title: effectiveTitle,
4226
- onClose: effectiveEmbedded ? isInIframe ? embeddedClose : void 0 : displayMode === "floating" ? close : showClose ? close : void 0,
4357
+ onClose: effectiveEmbedded ? inBridge ? embeddedClose : void 0 : displayMode === "floating" ? close : showClose ? close : void 0,
4227
4358
  onToggleFullscreen: effectiveEmbedded ? void 0 : showFullscreenToggle && !isSmallScreen ? toggleFullscreen : void 0,
4228
4359
  isFullscreen: effectiveEmbedded ? false : displayMode === "fullscreen",
4229
4360
  showConnectionStatus: true,
@@ -4231,8 +4362,8 @@ function ChatWidget({
4231
4362
  className: className?.header,
4232
4363
  theme,
4233
4364
  onThemeChange: handleThemeChange,
4234
- pushStatus: pushConfig ? push.status : void 0,
4235
- onPushToggle: pushConfig ? handlePushToggle : void 0
4365
+ pushStatus: hasBridgePush ? bridgePushState : pushConfig ? push.status : void 0,
4366
+ onPushToggle: hasBridgePush || pushConfig ? handlePushToggle : void 0
4236
4367
  }
4237
4368
  ),
4238
4369
  isPreEntry && preEntry ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex-1 overflow-y-auto p-4", children: preEntry.render({ start: handleStart }) }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
@@ -4248,14 +4379,14 @@ function ChatWidget({
4248
4379
  }
4249
4380
  ),
4250
4381
  isSomeoneTyping && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(TypingIndicator, {}),
4251
- pushConfig ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4382
+ !hasBridgePush && pushConfig ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4252
4383
  PushPermissionPrompt,
4253
4384
  {
4254
4385
  getVapidPublicKey: pushConfig.getVapidPublicKey,
4255
4386
  onSubscribe: pushConfig.onSubscribe,
4256
4387
  onUnsubscribe: pushConfig.onUnsubscribe
4257
4388
  }
4258
- ) : renderPushPrompt?.(),
4389
+ ) : !hasBridgePush ? renderPushPrompt?.() : null,
4259
4390
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4260
4391
  InputArea,
4261
4392
  {