@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.js CHANGED
@@ -526,29 +526,101 @@ function useAttachmentUpload(uploadConfig) {
526
526
 
527
527
  // src/hooks/useBridge.ts
528
528
  import { useState as useState6, useCallback as useCallback4, useEffect as useEffect6, useRef as useRef5 } from "react";
529
+ function getBridge() {
530
+ return typeof window !== "undefined" ? window.__chatBridge : null;
531
+ }
532
+ function hasNativeBridge() {
533
+ if (typeof window === "undefined") return false;
534
+ return !!getBridge() || !!window.webkit?.messageHandlers?.chatBridge || !!window.ReactNativeWebView || !!window.AndroidBridge;
535
+ }
536
+ function bridgeSend(msg) {
537
+ const bridge = getBridge();
538
+ if (bridge?.send) {
539
+ bridge.send(msg);
540
+ } else if (typeof window !== "undefined") {
541
+ window.parent.postMessage(msg, "*");
542
+ }
543
+ }
529
544
  function useBridge() {
530
545
  const notificationCbRef = useRef5(null);
531
546
  const [config, setConfig] = useState6(null);
547
+ const [pushState, setPushState] = useState6(null);
532
548
  const isInIframe = typeof window !== "undefined" && window !== window.parent;
549
+ const isInWebView = !isInIframe && hasNativeBridge();
533
550
  const notifyMessage = useCallback4(
534
551
  (text) => {
535
- if (!isInIframe) return;
536
- window.parent.postMessage({ type: "chat-message", text }, "*");
552
+ if (!isInIframe && !isInWebView) return;
553
+ if (isInWebView) {
554
+ bridgeSend({ type: "chat-message", text });
555
+ } else {
556
+ window.parent.postMessage({ type: "chat-message", text }, "*");
557
+ }
537
558
  },
538
- [isInIframe]
559
+ [isInIframe, isInWebView]
539
560
  );
540
561
  const notifyViewportConfig = useCallback4(
541
562
  (viewportContent) => {
542
- if (!isInIframe) return;
543
- window.parent.postMessage({ type: "chat-viewport-config", content: viewportContent }, "*");
563
+ if (!isInIframe && !isInWebView) return;
564
+ if (isInWebView) {
565
+ bridgeSend({ type: "chat-viewport-config", content: viewportContent });
566
+ } else {
567
+ window.parent.postMessage({ type: "chat-viewport-config", content: viewportContent }, "*");
568
+ }
544
569
  },
545
- [isInIframe]
570
+ [isInIframe, isInWebView]
546
571
  );
547
572
  const onNotificationClicked = useCallback4((cb) => {
548
573
  notificationCbRef.current = cb;
549
574
  }, []);
575
+ const requestPushSubscribe = useCallback4(() => {
576
+ if (!isInIframe && !isInWebView) return;
577
+ if (isInWebView) {
578
+ bridgeSend({ type: "chat-push-subscribe" });
579
+ } else {
580
+ window.parent.postMessage({ type: "chat-push-subscribe" }, "*");
581
+ }
582
+ }, [isInIframe, isInWebView]);
583
+ const requestPushUnsubscribe = useCallback4(() => {
584
+ if (!isInIframe && !isInWebView) return;
585
+ if (isInWebView) {
586
+ bridgeSend({ type: "chat-push-unsubscribe" });
587
+ } else {
588
+ window.parent.postMessage({ type: "chat-push-unsubscribe" }, "*");
589
+ }
590
+ }, [isInIframe, isInWebView]);
591
+ const bridgeInScope = typeof window !== "undefined" ? getBridge() : null;
592
+ useEffect6(() => {
593
+ if (bridgeInScope?._pushState) {
594
+ setPushState(bridgeInScope._pushState);
595
+ }
596
+ }, [bridgeInScope?._pushState]);
597
+ useEffect6(() => {
598
+ if (!isInIframe && !isInWebView) return;
599
+ if (!getBridge()?._ready) {
600
+ const bridge = getBridge();
601
+ if (bridge) {
602
+ bridge._ready = true;
603
+ bridgeSend({ type: "chat-ready" });
604
+ } else {
605
+ if (!window.__chatBridge) {
606
+ window.__chatBridge = {};
607
+ }
608
+ window.__chatBridge._ready = true;
609
+ if (!window.__chatBridge.send) {
610
+ window.__chatBridge.send = function(msg) {
611
+ if (window.ReactNativeWebView?.postMessage) {
612
+ window.ReactNativeWebView.postMessage(JSON.stringify(msg));
613
+ } else {
614
+ window.parent.postMessage(msg, "*");
615
+ }
616
+ };
617
+ }
618
+ window.__chatBridge.send({ type: "chat-ready" });
619
+ }
620
+ }
621
+ }, [isInIframe, isInWebView]);
550
622
  useEffect6(() => {
551
- if (!isInIframe) return;
623
+ if (!isInIframe && !isInWebView) return;
552
624
  function handleMessage(event) {
553
625
  const data = event.data;
554
626
  if (!data || typeof data !== "object" || !data.type) return;
@@ -560,11 +632,43 @@ function useBridge() {
560
632
  if (data.type === "chat-notification-clicked") {
561
633
  notificationCbRef.current?.();
562
634
  }
635
+ if (data.type === "chat-push-state" && typeof data.status === "string") {
636
+ setPushState(data.status);
637
+ }
638
+ }
639
+ function handleCustomEvent(event) {
640
+ const detail = event.detail;
641
+ if (!detail || typeof detail !== "object") return;
642
+ if (detail.type === "chat-config") {
643
+ const configData = { ...detail };
644
+ delete configData.type;
645
+ setConfig(configData);
646
+ }
647
+ if (detail.type === "chat-notification-clicked") {
648
+ notificationCbRef.current?.();
649
+ }
650
+ if (detail.type === "chat-push-state" && typeof detail.status === "string") {
651
+ setPushState(detail.status);
652
+ }
563
653
  }
564
654
  window.addEventListener("message", handleMessage);
565
- return () => window.removeEventListener("message", handleMessage);
566
- }, [isInIframe]);
567
- return { config, isInIframe, notifyMessage, notifyViewportConfig, onNotificationClicked };
655
+ window.addEventListener("chat-bridge", handleCustomEvent);
656
+ return () => {
657
+ window.removeEventListener("message", handleMessage);
658
+ window.removeEventListener("chat-bridge", handleCustomEvent);
659
+ };
660
+ }, [isInIframe, isInWebView]);
661
+ return {
662
+ config,
663
+ isInIframe,
664
+ isInWebView,
665
+ notifyMessage,
666
+ notifyViewportConfig,
667
+ onNotificationClicked,
668
+ pushState,
669
+ requestPushSubscribe,
670
+ requestPushUnsubscribe
671
+ };
568
672
  }
569
673
 
570
674
  // src/i18n/LocaleProvider.tsx
@@ -3964,17 +4068,23 @@ function ChatWidget({
3964
4068
  const {
3965
4069
  config: iframeConfig,
3966
4070
  isInIframe,
4071
+ isInWebView,
3967
4072
  notifyMessage,
3968
4073
  notifyViewportConfig,
3969
- onNotificationClicked
4074
+ onNotificationClicked,
4075
+ pushState: bridgePushState,
4076
+ requestPushSubscribe,
4077
+ requestPushUnsubscribe
3970
4078
  } = useBridge();
3971
- const effectiveLocale = localeProp ?? (isInIframe ? iframeConfig?.locale : void 0) ?? useLocale().locale;
4079
+ const hasBridgePush = (isInIframe || isInWebView) && bridgePushState !== null;
4080
+ const inBridge = isInIframe || isInWebView;
4081
+ const effectiveLocale = localeProp ?? (inBridge ? iframeConfig?.locale : void 0) ?? useLocale().locale;
3972
4082
  const merged = mergeLocale(effectiveLocale);
3973
4083
  const dir = merged.direction;
3974
4084
  useEffect9(() => {
3975
4085
  client.setLocaleHeader(effectiveLocale);
3976
4086
  }, [client, effectiveLocale]);
3977
- const autoEmbedded = isInIframe && embedded !== false;
4087
+ const autoEmbedded = inBridge && embedded !== false;
3978
4088
  const effectiveEmbedded = embedded === true || autoEmbedded;
3979
4089
  const effectiveMode = effectiveEmbedded ? "embedded" : initialMode;
3980
4090
  const [theme, setTheme] = useState10(() => {
@@ -4045,7 +4155,7 @@ function ChatWidget({
4045
4155
  if (!isSmallScreen) setIsOpen(true);
4046
4156
  }, [initialMode, isSmallScreen]);
4047
4157
  useEffect9(() => {
4048
- if (isInIframe) {
4158
+ if (inBridge) {
4049
4159
  notifyViewportConfig("interactive-widget=resizes-content");
4050
4160
  return () => notifyViewportConfig("");
4051
4161
  }
@@ -4062,14 +4172,15 @@ function ChatWidget({
4062
4172
  meta.setAttribute("content", original);
4063
4173
  };
4064
4174
  }
4065
- }, [isOpen, displayMode, isSmallScreen, isInIframe, notifyViewportConfig]);
4175
+ }, [isOpen, displayMode, isSmallScreen, inBridge, notifyViewportConfig]);
4066
4176
  const { messages, sendMessage, loading, isLoadingHistory, reloadMessages } = useMessages(
4067
4177
  client,
4068
4178
  (isOpen || effectiveEmbedded) && !isPreEntry
4069
4179
  );
4070
4180
  const { isSomeoneTyping } = useTyping(client);
4181
+ const webPushEnabled = !!pushConfig && !hasBridgePush;
4071
4182
  const push = usePushNotifications({
4072
- enabled: !!pushConfig,
4183
+ enabled: webPushEnabled,
4073
4184
  getVapidPublicKey: pushConfig?.getVapidPublicKey ?? (() => Promise.resolve("")),
4074
4185
  onSubscribe: pushConfig?.onSubscribe ?? (async () => {
4075
4186
  }),
@@ -4081,11 +4192,24 @@ function ChatWidget({
4081
4192
  notificationOptions: pushConfig?.notificationOptions
4082
4193
  });
4083
4194
  const handlePushToggle = useCallback6(() => {
4084
- if (push.isSubscribed) push.unsubscribe();
4085
- else push.subscribe();
4086
- }, [push.isSubscribed, push.subscribe, push.unsubscribe]);
4195
+ if (hasBridgePush) {
4196
+ if (bridgePushState === "subscribed") requestPushUnsubscribe();
4197
+ else requestPushSubscribe();
4198
+ } else {
4199
+ if (push.isSubscribed) push.unsubscribe();
4200
+ else push.subscribe();
4201
+ }
4202
+ }, [
4203
+ hasBridgePush,
4204
+ bridgePushState,
4205
+ requestPushSubscribe,
4206
+ requestPushUnsubscribe,
4207
+ push.isSubscribed,
4208
+ push.subscribe,
4209
+ push.unsubscribe
4210
+ ]);
4087
4211
  useEffect9(() => {
4088
- if (!isInIframe || !iframeConfig) return;
4212
+ if (!inBridge || !iframeConfig) return;
4089
4213
  if (iframeConfig.theme?.cssVariables) {
4090
4214
  const root = document.documentElement;
4091
4215
  for (const [key, value] of Object.entries(iframeConfig.theme.cssVariables)) {
@@ -4096,24 +4220,24 @@ function ChatWidget({
4096
4220
  if (mode === "light" || mode === "dark" || mode === "auto") {
4097
4221
  setTheme(mode);
4098
4222
  }
4099
- }, [isInIframe, iframeConfig]);
4223
+ }, [inBridge, iframeConfig]);
4100
4224
  useEffect9(() => {
4101
- if (!isInIframe) return;
4225
+ if (!inBridge) return;
4102
4226
  onNotificationClicked(() => {
4103
4227
  reloadMessages();
4104
4228
  });
4105
- }, [isInIframe, onNotificationClicked, reloadMessages]);
4106
- const effectiveTitle = isInIframe && iframeConfig?.title || title;
4107
- const effectivePlaceholder = isInIframe && iframeConfig?.placeholder || placeholder || merged.chatWidget.placeholder;
4229
+ }, [inBridge, onNotificationClicked, reloadMessages]);
4230
+ const effectiveTitle = inBridge && iframeConfig?.title || title;
4231
+ const effectivePlaceholder = inBridge && iframeConfig?.placeholder || placeholder || merged.chatWidget.placeholder;
4108
4232
  const currentUserId = "getCurrentUserId" in client ? client.getCurrentUserId() : "";
4109
4233
  const handleSend = useCallback6(
4110
4234
  async (text, attachments = []) => {
4111
4235
  await sendMessage(text, attachments);
4112
- if (isInIframe) {
4236
+ if (inBridge) {
4113
4237
  notifyMessage(text);
4114
4238
  }
4115
4239
  },
4116
- [sendMessage, isInIframe, notifyMessage]
4240
+ [sendMessage, inBridge, notifyMessage]
4117
4241
  );
4118
4242
  const handleActionClick = useCallback6(
4119
4243
  (messageId, actionId, value) => {
@@ -4138,19 +4262,26 @@ function ChatWidget({
4138
4262
  const close = useCallback6(() => {
4139
4263
  setIsOpen(false);
4140
4264
  setDisplayMode("floating");
4265
+ if (isInWebView) {
4266
+ window.__chatBridge?.send?.({ type: "chat-close" });
4267
+ } else if (isInIframe) {
4268
+ window.parent.postMessage({ type: "chat-close" }, "*");
4269
+ }
4141
4270
  onClose?.();
4142
- }, [onClose]);
4271
+ }, [onClose, isInIframe, isInWebView]);
4143
4272
  const embeddedClose = useCallback6(() => {
4144
- if (isInIframe) {
4273
+ if (isInWebView) {
4274
+ window.__chatBridge?.send?.({ type: "chat-close" });
4275
+ } else if (isInIframe) {
4145
4276
  window.parent.postMessage({ type: "chat-close" }, "*");
4146
4277
  }
4147
- }, [isInIframe]);
4278
+ }, [isInIframe, isInWebView]);
4148
4279
  const panelContent = /* @__PURE__ */ jsxs13(Fragment2, { children: [
4149
4280
  /* @__PURE__ */ jsx17(
4150
4281
  Header,
4151
4282
  {
4152
4283
  title: effectiveTitle,
4153
- onClose: effectiveEmbedded ? isInIframe ? embeddedClose : void 0 : displayMode === "floating" ? close : showClose ? close : void 0,
4284
+ onClose: effectiveEmbedded ? inBridge ? embeddedClose : void 0 : displayMode === "floating" ? close : showClose ? close : void 0,
4154
4285
  onToggleFullscreen: effectiveEmbedded ? void 0 : showFullscreenToggle && !isSmallScreen ? toggleFullscreen : void 0,
4155
4286
  isFullscreen: effectiveEmbedded ? false : displayMode === "fullscreen",
4156
4287
  showConnectionStatus: true,
@@ -4158,8 +4289,8 @@ function ChatWidget({
4158
4289
  className: className?.header,
4159
4290
  theme,
4160
4291
  onThemeChange: handleThemeChange,
4161
- pushStatus: pushConfig ? push.status : void 0,
4162
- onPushToggle: pushConfig ? handlePushToggle : void 0
4292
+ pushStatus: hasBridgePush ? bridgePushState : pushConfig ? push.status : void 0,
4293
+ onPushToggle: hasBridgePush || pushConfig ? handlePushToggle : void 0
4163
4294
  }
4164
4295
  ),
4165
4296
  isPreEntry && preEntry ? /* @__PURE__ */ jsx17("div", { className: "flex-1 overflow-y-auto p-4", children: preEntry.render({ start: handleStart }) }) : /* @__PURE__ */ jsxs13(Fragment2, { children: [
@@ -4175,14 +4306,14 @@ function ChatWidget({
4175
4306
  }
4176
4307
  ),
4177
4308
  isSomeoneTyping && /* @__PURE__ */ jsx17(TypingIndicator, {}),
4178
- pushConfig ? /* @__PURE__ */ jsx17(
4309
+ !hasBridgePush && pushConfig ? /* @__PURE__ */ jsx17(
4179
4310
  PushPermissionPrompt,
4180
4311
  {
4181
4312
  getVapidPublicKey: pushConfig.getVapidPublicKey,
4182
4313
  onSubscribe: pushConfig.onSubscribe,
4183
4314
  onUnsubscribe: pushConfig.onUnsubscribe
4184
4315
  }
4185
- ) : renderPushPrompt?.(),
4316
+ ) : !hasBridgePush ? renderPushPrompt?.() : null,
4186
4317
  /* @__PURE__ */ jsx17(
4187
4318
  InputArea,
4188
4319
  {