fix: config fetched on runtime

This commit is contained in:
Yassine Sallemi 2024-09-20 16:21:30 +01:00
parent aa05fe1704
commit cdf8461312
16 changed files with 70 additions and 80 deletions

View File

@ -1,8 +1,6 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
import withTM from "next-transpile-modules"; import withTM from "next-transpile-modules";
const apiUrl = process.env.NEXT_PUBLIC_API_ORIGIN || "http://localhost:4000/";
const url = new URL(apiUrl);
const nextConfig = withTM(["hexabot-widget"])({ const nextConfig = withTM(["hexabot-widget"])({
async rewrites() { async rewrites() {
return [ return [
@ -16,29 +14,11 @@ const nextConfig = withTM(["hexabot-widget"])({
return config; return config;
}, },
publicRuntimeConfig: { publicRuntimeConfig: {
apiUrl,
ssoEnabled: process.env.NEXT_PUBLIC_SSO_ENABLED === "true",
lang: { lang: {
default: "en", default: "en",
}, },
}, },
output: "standalone", output: "standalone",
images: {
remotePatterns: [
{
protocol: "https",
hostname: url.hostname,
port: url.port,
pathname: "/attachment/**",
},
{
protocol: "http",
hostname: url.hostname,
port: url.port,
pathname: "/attachment/**",
},
],
},
}); });
export default nextConfig; export default nextConfig;

View File

@ -11,12 +11,15 @@ import { Grid } from "@mui/material";
import { GridRenderCellParams } from "@mui/x-data-grid"; import { GridRenderCellParams } from "@mui/x-data-grid";
import { getAvatarSrc } from "@/components/inbox/helpers/mapMessages"; import { getAvatarSrc } from "@/components/inbox/helpers/mapMessages";
import { useConfig } from "@/hooks/useConfig";
import { EntityType } from "@/services/types"; import { EntityType } from "@/services/types";
export const buildRenderPicture = ( export const buildRenderPicture = (
entityType: EntityType.USER | EntityType.SUBSCRIBER, entityType: EntityType.USER | EntityType.SUBSCRIBER,
) => ) =>
function RenderPicture(params: GridRenderCellParams) { function RenderPicture(params: GridRenderCellParams) {
const { apiUrl } = useConfig();
return ( return (
<Grid <Grid
container container
@ -28,6 +31,7 @@ export const buildRenderPicture = (
> >
<img <img
src={getAvatarSrc( src={getAvatarSrc(
apiUrl,
entityType, entityType,
entityType === EntityType.USER entityType === EntityType.USER
? params.row.id ? params.row.id

View File

@ -21,9 +21,9 @@ import { useTranslation } from "react-i18next";
import { useCreate } from "@/hooks/crud/useCreate"; import { useCreate } from "@/hooks/crud/useCreate";
import { useAuth } from "@/hooks/useAuth"; import { useAuth } from "@/hooks/useAuth";
import { useConfig } from "@/hooks/useConfig";
import { EntityType } from "@/services/types"; import { EntityType } from "@/services/types";
import { ChatActions } from "./ChatActions"; import { ChatActions } from "./ChatActions";
import { ChatHeader } from "./ChatHeader"; import { ChatHeader } from "./ChatHeader";
import { import {
@ -35,6 +35,7 @@ import { useChat } from "../hooks/ChatContext";
import { useInfinitedLiveMessages } from "../hooks/useInfiniteLiveMessages"; import { useInfinitedLiveMessages } from "../hooks/useInfiniteLiveMessages";
export function Chat() { export function Chat() {
const { apiUrl } = useConfig();
const { t, i18n } = useTranslation(); const { t, i18n } = useTranslation();
const { subscriber } = useChat(); const { subscriber } = useChat();
const { user } = useAuth(); const { user } = useAuth();
@ -69,7 +70,11 @@ export function Chat() {
<ConversationHeader> <ConversationHeader>
<Avatar <Avatar
name={subscriber?.first_name} name={subscriber?.first_name}
src={getAvatarSrc(EntityType.SUBSCRIBER, subscriber.foreign_id)} src={getAvatarSrc(
apiUrl,
EntityType.SUBSCRIBER,
subscriber.foreign_id,
)}
/> />
<ConversationHeader.Content> <ConversationHeader.Content>
<ChatHeader /> <ChatHeader />
@ -118,6 +123,7 @@ export function Chat() {
i18n.language, i18n.language,
)}`} )}`}
src={getAvatarSrc( src={getAvatarSrc(
apiUrl,
message.sender message.sender
? EntityType.SUBSCRIBER ? EntityType.SUBSCRIBER
: EntityType.USER, : EntityType.USER,

View File

@ -18,12 +18,14 @@ import { Input } from "@/app-components/inputs/Input";
import { useFind } from "@/hooks/crud/useFind"; import { useFind } from "@/hooks/crud/useFind";
import { useUpdate } from "@/hooks/crud/useUpdate"; import { useUpdate } from "@/hooks/crud/useUpdate";
import { useAuth } from "@/hooks/useAuth"; import { useAuth } from "@/hooks/useAuth";
import { useConfig } from "@/hooks/useConfig";
import { EntityType } from "@/services/types"; import { EntityType } from "@/services/types";
import { getAvatarSrc } from "../helpers/mapMessages"; import { getAvatarSrc } from "../helpers/mapMessages";
import { useChat } from "../hooks/ChatContext"; import { useChat } from "../hooks/ChatContext";
export const ChatActions = () => { export const ChatActions = () => {
const { apiUrl } = useConfig();
const { t } = useTranslation(); const { t } = useTranslation();
const { subscriber: activeChat } = useChat(); const { subscriber: activeChat } = useChat();
const [takeoverBy, setTakeoverBy] = useState<string>( const [takeoverBy, setTakeoverBy] = useState<string>(
@ -57,7 +59,7 @@ export const ChatActions = () => {
<Avatar <Avatar
size="sm" size="sm"
name={user.first_name} name={user.first_name}
src={getAvatarSrc(EntityType.USER, user.id)} src={getAvatarSrc(apiUrl, EntityType.USER, user.id)}
/> />
</Grid> </Grid>
<Grid> <Grid>

View File

@ -16,6 +16,7 @@ import InboxIcon from "@mui/icons-material/MoveToInbox";
import { Chip, debounce, Grid } from "@mui/material"; import { Chip, debounce, Grid } from "@mui/material";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useConfig } from "@/hooks/useConfig";
import { Title } from "@/layout/content/Title"; import { Title } from "@/layout/content/Title";
import { EntityType } from "@/services/types"; import { EntityType } from "@/services/types";
@ -29,6 +30,7 @@ export const SubscribersList = (props: {
searchPayload: any; searchPayload: any;
assignedTo: AssignedTo; assignedTo: AssignedTo;
}) => { }) => {
const { apiUrl } = useConfig();
const { t, i18n } = useTranslation(); const { t, i18n } = useTranslation();
const chat = useChat(); const chat = useChat();
const { fetchNextPage, isFetching, subscribers, hasNextPage } = const { fetchNextPage, isFetching, subscribers, hasNextPage } =
@ -58,6 +60,7 @@ export const SubscribersList = (props: {
> >
<Avatar <Avatar
src={getAvatarSrc( src={getAvatarSrc(
apiUrl,
EntityType.SUBSCRIBER, EntityType.SUBSCRIBER,
conversation.foreign_id, conversation.foreign_id,
)} )}

View File

@ -11,7 +11,6 @@ import { Message, MessageModel } from "@chatscope/chat-ui-kit-react";
import MenuRoundedIcon from "@mui/icons-material/MenuRounded"; import MenuRoundedIcon from "@mui/icons-material/MenuRounded";
import ReplyIcon from "@mui/icons-material/Reply"; import ReplyIcon from "@mui/icons-material/Reply";
import { Chip, Grid } from "@mui/material"; import { Chip, Grid } from "@mui/material";
import getConfig from "next/config";
import { ReactNode } from "react"; import { ReactNode } from "react";
import { ROUTES } from "@/services/api.class"; import { ROUTES } from "@/services/api.class";
@ -21,8 +20,6 @@ import { IMessage, IMessageFull } from "@/types/message.types";
import { AttachmentViewer } from "../components/AttachmentViewer"; import { AttachmentViewer } from "../components/AttachmentViewer";
import { Carousel } from "../components/Carousel"; import { Carousel } from "../components/Carousel";
const { publicRuntimeConfig } = getConfig();
function hasSameSender( function hasSameSender(
m1: IMessage | IMessageFull, m1: IMessage | IMessageFull,
m2: IMessage | IMessageFull, m2: IMessage | IMessageFull,
@ -133,13 +130,14 @@ export function getMessageContent(
* @description Returns the avatar of the subscriber * @description Returns the avatar of the subscriber
*/ */
export function getAvatarSrc( export function getAvatarSrc(
apiUrl: string,
entity: EntityType.USER | EntityType.SUBSCRIBER, entity: EntityType.USER | EntityType.SUBSCRIBER,
id?: string, id?: string,
) { ) {
//remove trailing slash //remove trailing slash
return `${String(publicRuntimeConfig.apiUrl).replace(/\/$/, "")}${ return `${String(apiUrl).replace(/\/$/, "")}${ROUTES[entity]}/${
ROUTES[entity] id || "bot"
}/${id || "bot"}/profile_pic`; }/profile_pic`;
} }
export function getMessagePosition( export function getMessagePosition(

View File

@ -21,7 +21,6 @@ import {
MenuItem, MenuItem,
} from "@mui/material"; } from "@mui/material";
import { GridColDef } from "@mui/x-data-grid"; import { GridColDef } from "@mui/x-data-grid";
import getConfig from "next/config";
import { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@ -38,6 +37,7 @@ import { DataGrid } from "@/app-components/tables/DataGrid";
import { useDelete } from "@/hooks/crud/useDelete"; import { useDelete } from "@/hooks/crud/useDelete";
import { useFind } from "@/hooks/crud/useFind"; import { useFind } from "@/hooks/crud/useFind";
import { useGetFromCache } from "@/hooks/crud/useGet"; import { useGetFromCache } from "@/hooks/crud/useGet";
import { useConfig } from "@/hooks/useConfig";
import { getDisplayDialogs, useDialog } from "@/hooks/useDialog"; import { getDisplayDialogs, useDialog } from "@/hooks/useDialog";
import { useHasPermission } from "@/hooks/useHasPermission"; import { useHasPermission } from "@/hooks/useHasPermission";
import { useSearch } from "@/hooks/useSearch"; import { useSearch } from "@/hooks/useSearch";
@ -56,7 +56,6 @@ import { buildURL } from "@/utils/URL";
import { NlpImportDialog } from "../NlpImportDialog"; import { NlpImportDialog } from "../NlpImportDialog";
import { NlpSampleDialog } from "../NlpSampleDialog"; import { NlpSampleDialog } from "../NlpSampleDialog";
const { publicRuntimeConfig } = getConfig();
const NLP_SAMPLE_TYPE_COLORS = { const NLP_SAMPLE_TYPE_COLORS = {
test: "#e6a23c", test: "#e6a23c",
train: "#67c23a", train: "#67c23a",
@ -64,6 +63,7 @@ const NLP_SAMPLE_TYPE_COLORS = {
}; };
export default function NlpSample() { export default function NlpSample() {
const { apiUrl } = useConfig();
const { toast } = useToast(); const { toast } = useToast();
const { t } = useTranslation(); const { t } = useTranslation();
const [dataset, setDataSet] = useState(""); const [dataset, setDataSet] = useState("");
@ -287,7 +287,7 @@ export default function NlpSample() {
<Button <Button
variant="contained" variant="contained"
href={buildURL( href={buildURL(
publicRuntimeConfig.apiUrl, apiUrl,
`nlpsample/export${dataset ? `?type=${dataset}` : ""}`, `nlpsample/export${dataset ? `?type=${dataset}` : ""}`,
)} )}
startIcon={<DownloadIcon />} startIcon={<DownloadIcon />}

View File

@ -11,7 +11,6 @@ import { faUsers } from "@fortawesome/free-solid-svg-icons";
import PersonAddAlt1Icon from "@mui/icons-material/PersonAddAlt1"; import PersonAddAlt1Icon from "@mui/icons-material/PersonAddAlt1";
import { Button, Grid, Paper } from "@mui/material"; import { Button, Grid, Paper } from "@mui/material";
import { GridColDef } from "@mui/x-data-grid"; import { GridColDef } from "@mui/x-data-grid";
import getConfig from "next/config";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { ChipEntity } from "@/app-components/displays/ChipEntity"; import { ChipEntity } from "@/app-components/displays/ChipEntity";
@ -25,6 +24,7 @@ import { buildRenderPicture } from "@/app-components/tables/columns/renderPictur
import { DataGrid } from "@/app-components/tables/DataGrid"; import { DataGrid } from "@/app-components/tables/DataGrid";
import { useFind } from "@/hooks/crud/useFind"; import { useFind } from "@/hooks/crud/useFind";
import { useUpdate } from "@/hooks/crud/useUpdate"; import { useUpdate } from "@/hooks/crud/useUpdate";
import { useConfig } from "@/hooks/useConfig";
import { getDisplayDialogs, useDialog } from "@/hooks/useDialog"; import { getDisplayDialogs, useDialog } from "@/hooks/useDialog";
import { useHasPermission } from "@/hooks/useHasPermission"; import { useHasPermission } from "@/hooks/useHasPermission";
import { useSearch } from "@/hooks/useSearch"; import { useSearch } from "@/hooks/useSearch";
@ -39,9 +39,8 @@ import { getDateTimeFormatter } from "@/utils/date";
import { EditUserDialog } from "./EditUserDialog"; import { EditUserDialog } from "./EditUserDialog";
import { InvitationDialog } from "./InvitationDialog"; import { InvitationDialog } from "./InvitationDialog";
const { publicRuntimeConfig } = getConfig();
export const Users = () => { export const Users = () => {
const { ssoEnabled } = useConfig();
const { t } = useTranslation(); const { t } = useTranslation();
const { toast } = useToast(); const { toast } = useToast();
const { mutateAsync: updateUser } = useUpdate(EntityType.USER, { const { mutateAsync: updateUser } = useUpdate(EntityType.USER, {
@ -157,7 +156,7 @@ export const Users = () => {
}, },
}); });
}} }}
disabled={publicRuntimeConfig.ssoEnabled} disabled={ssoEnabled}
> >
{t(params.row.state ? "label.enabled" : "label.disabled")} {t(params.row.state ? "label.enabled" : "label.disabled")}
</Button> </Button>
@ -188,7 +187,7 @@ export const Users = () => {
valueGetter: (params) => valueGetter: (params) =>
t("datetime.updated_at", getDateTimeFormatter(params)), t("datetime.updated_at", getDateTimeFormatter(params)),
}, },
...(!publicRuntimeConfig.ssoEnabled ? [actionColumns] : []), ...(!ssoEnabled ? [actionColumns] : []),
]; ];
return ( return (
@ -207,7 +206,7 @@ export const Users = () => {
<Grid item> <Grid item>
<FilterTextfield onChange={onSearch} /> <FilterTextfield onChange={onSearch} />
</Grid> </Grid>
{!publicRuntimeConfig.ssoEnabled && {!ssoEnabled &&
hasPermission(EntityType.USER, PermissionAction.CREATE) ? ( hasPermission(EntityType.USER, PermissionAction.CREATE) ? (
<Grid item> <Grid item>
<Button <Button

View File

@ -8,7 +8,6 @@
*/ */
import axios from "axios"; import axios from "axios";
import getConfig from "next/config";
import { stringify } from "qs"; import { stringify } from "qs";
import React, { createContext, ReactNode, useContext, useMemo } from "react"; import React, { createContext, ReactNode, useContext, useMemo } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@ -18,17 +17,17 @@ import { EntityType } from "@/services/types";
import { IBaseSchema } from "@/types/base.types"; import { IBaseSchema } from "@/types/base.types";
import { useLogoutRedirection } from "./useAuth"; import { useLogoutRedirection } from "./useAuth";
import { useConfig } from "./useConfig";
import { useToast } from "./useToast"; import { useToast } from "./useToast";
const { publicRuntimeConfig } = getConfig();
export const useAxiosInstance = () => { export const useAxiosInstance = () => {
const { apiUrl } = useConfig();
const { logoutRedirection } = useLogoutRedirection(); const { logoutRedirection } = useLogoutRedirection();
const { toast } = useToast(); const { toast } = useToast();
const { t } = useTranslation(); const { t } = useTranslation();
const axiosInstance = useMemo(() => { const axiosInstance = useMemo(() => {
const instance = axios.create({ const instance = axios.create({
baseURL: publicRuntimeConfig.apiUrl, baseURL: apiUrl,
withCredentials: true, withCredentials: true,
}); });
// Added the same Query String (de)Serializer as NestJS, // Added the same Query String (de)Serializer as NestJS,

View File

@ -1,13 +1,10 @@
import { createContext, useContext, useEffect, useState } from "react"; import { createContext, useContext, useEffect, useState } from "react";
const ConfigContext = createContext(null); const ConfigContext = createContext<IConfig | null>(null);
export interface IConfig { export interface IConfig {
NEXT_PUBLIC_API_ORIGIN: string; apiUrl: string;
NEXT_PUBLIC_SSO_ENABLED: boolean; ssoEnabled: boolean;
REACT_APP_WIDGET_API_URL: string;
REACT_APP_WIDGET_CHANNEL: string;
REACT_APP_WIDGET_TOKEN: string;
} }
export const ConfigProvider = ({ children }) => { export const ConfigProvider = ({ children }) => {

View File

@ -18,7 +18,6 @@ import {
styled, styled,
} from "@mui/material"; } from "@mui/material";
import MuiAppBar, { AppBarProps as MuiAppBarProps } from "@mui/material/AppBar"; import MuiAppBar, { AppBarProps as MuiAppBarProps } from "@mui/material/AppBar";
import getConfig from "next/config";
import { FC, useEffect, useRef, useState } from "react"; import { FC, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@ -26,12 +25,11 @@ import { HexabotLogo } from "@/app-components/logos/HexabotLogo";
import { PopoverMenu } from "@/app-components/menus/PopoverMenu"; import { PopoverMenu } from "@/app-components/menus/PopoverMenu";
import { getAvatarSrc } from "@/components/inbox/helpers/mapMessages"; import { getAvatarSrc } from "@/components/inbox/helpers/mapMessages";
import { useAuth } from "@/hooks/useAuth"; import { useAuth } from "@/hooks/useAuth";
import { useConfig } from "@/hooks/useConfig";
import { EntityType } from "@/services/types"; import { EntityType } from "@/services/types";
import { borderLine, theme } from "./themes/theme"; import { borderLine, theme } from "./themes/theme";
const { publicRuntimeConfig } = getConfig();
interface AppBarProps extends MuiAppBarProps { interface AppBarProps extends MuiAppBarProps {
open?: boolean; open?: boolean;
} }
@ -75,6 +73,7 @@ export type HeaderProps = {
onToggleSidebar: () => void; onToggleSidebar: () => void;
}; };
export const Header: FC<HeaderProps> = ({ isSideBarOpen, onToggleSidebar }) => { export const Header: FC<HeaderProps> = ({ isSideBarOpen, onToggleSidebar }) => {
const { apiUrl, ssoEnabled } = useConfig();
const { t } = useTranslation(); const { t } = useTranslation();
const anchorRef = useRef(null); const anchorRef = useRef(null);
const [isMenuPopoverOpen, setIsMenuPopoverOpen] = useState(false); const [isMenuPopoverOpen, setIsMenuPopoverOpen] = useState(false);
@ -160,7 +159,7 @@ export const Header: FC<HeaderProps> = ({ isSideBarOpen, onToggleSidebar }) => {
</Typography> </Typography>
</Box> </Box>
<Avatar <Avatar
src={getAvatarSrc(EntityType.USER, user?.id).concat( src={getAvatarSrc(apiUrl, EntityType.USER, user?.id).concat(
`?${randomSeed}`, `?${randomSeed}`,
)} )}
color={theme.palette.text.secondary} color={theme.palette.text.secondary}
@ -176,7 +175,7 @@ export const Header: FC<HeaderProps> = ({ isSideBarOpen, onToggleSidebar }) => {
last_name: user.last_name, last_name: user.last_name,
}} }}
links={ links={
!publicRuntimeConfig.ssoEnabled !ssoEnabled
? [ ? [
{ text: t("menu.home"), href: "/" }, { text: t("menu.home"), href: "/" },
{ text: t("menu.edit_account"), href: "/profile" }, { text: t("menu.edit_account"), href: "/profile" },

View File

@ -31,7 +31,6 @@ import SettingsAccessibilityRoundedIcon from "@mui/icons-material/SettingsAccess
import { CSSObject, Grid, IconButton, styled, Theme } from "@mui/material"; import { CSSObject, Grid, IconButton, styled, Theme } from "@mui/material";
import MuiDrawer from "@mui/material/Drawer"; import MuiDrawer from "@mui/material/Drawer";
import { OverridableComponent } from "@mui/material/OverridableComponent"; import { OverridableComponent } from "@mui/material/OverridableComponent";
import getConfig from "next/config";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { FC, useMemo } from "react"; import { FC, useMemo } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@ -39,12 +38,12 @@ import { useTranslation } from "react-i18next";
import { HexabotLogo } from "@/app-components/logos/HexabotLogo"; import { HexabotLogo } from "@/app-components/logos/HexabotLogo";
import { Sidebar } from "@/app-components/menus/Sidebar"; import { Sidebar } from "@/app-components/menus/Sidebar";
import { useAuth } from "@/hooks/useAuth"; import { useAuth } from "@/hooks/useAuth";
import { useConfig } from "@/hooks/useConfig";
import { useHasPermission } from "@/hooks/useHasPermission"; import { useHasPermission } from "@/hooks/useHasPermission";
import { EntityType } from "@/services/types"; import { EntityType } from "@/services/types";
import { PermissionAction } from "@/types/permission.types"; import { PermissionAction } from "@/types/permission.types";
import { getLayout } from "@/utils/laylout"; import { getLayout } from "@/utils/laylout";
const { publicRuntimeConfig } = getConfig();
const drawerWidth = 280; const drawerWidth = 280;
const openedMixin = (theme: Theme): CSSObject => ({ const openedMixin = (theme: Theme): CSSObject => ({
width: drawerWidth, width: drawerWidth,
@ -107,7 +106,7 @@ export type MenuItem = {
submenuItems?: MenuItem[]; submenuItems?: MenuItem[];
}; };
const MENU_ITEMS: MenuItem[] = [ const getMenuItems = (ssoEnabled: boolean): MenuItem[] => [
{ {
text: "menu.dashboard", text: "menu.dashboard",
href: "/", href: "/",
@ -236,7 +235,7 @@ const MENU_ITEMS: MenuItem[] = [
[EntityType.USER]: [PermissionAction.READ], [EntityType.USER]: [PermissionAction.READ],
}, },
}, },
...(!publicRuntimeConfig.ssoEnabled ...(!ssoEnabled
? [ ? [
{ {
text: "menu.roles", text: "menu.roles",
@ -271,13 +270,15 @@ export const VerticalMenu: FC<VerticalMenuProps> = ({
onToggleIn, onToggleIn,
onToggleOut, onToggleOut,
}) => { }) => {
const { ssoEnabled } = useConfig();
const { t } = useTranslation(); const { t } = useTranslation();
const { isAuthenticated } = useAuth(); const { isAuthenticated } = useAuth();
const router = useRouter(); const router = useRouter();
const hasPermission = useHasPermission(); const hasPermission = useHasPermission();
const menuItems = getMenuItems(ssoEnabled);
// Filter menu item to which user is allowed access // Filter menu item to which user is allowed access
const availableMenuItems = useMemo(() => { const availableMenuItems = useMemo(() => {
return MENU_ITEMS.filter(({ requires: requiredPermissions }) => { return menuItems.filter(({ requires: requiredPermissions }) => {
return ( return (
!requiredPermissions || !requiredPermissions ||
Object.entries(requiredPermissions).every((permission) => { Object.entries(requiredPermissions).every((permission) => {

View File

@ -10,7 +10,7 @@ export default function handler(
res: NextApiResponse<ResponseData>, res: NextApiResponse<ResponseData>,
) { ) {
res.status(200).json({ res.status(200).json({
apiUrl: process.env.NEXT_PUBLIC_API_ORIGIN || "http://localhost:3000", apiUrl: process.env.NEXT_PUBLIC_API_ORIGIN || "http://localhost:4000",
ssoEnabled: process.env.NEXT_PUBLIC_SSO_ENABLED === "true" || false, ssoEnabled: process.env.NEXT_PUBLIC_SSO_ENABLED === "true" || false,
}); });
} }

View File

@ -10,17 +10,16 @@
import ChatIcon from "@mui/icons-material/Chat"; import ChatIcon from "@mui/icons-material/Chat";
import { Avatar, Box, Typography } from "@mui/material"; import { Avatar, Box, Typography } from "@mui/material";
import UiChatWidget from "hexabot-widget/src/UiChatWidget"; import UiChatWidget from "hexabot-widget/src/UiChatWidget";
import getConfig from "next/config";
import { ReactElement } from "react"; import { ReactElement } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { getAvatarSrc } from "@/components/inbox/helpers/mapMessages"; import { getAvatarSrc } from "@/components/inbox/helpers/mapMessages";
import { VisualEditor } from "@/components/visual-editor"; import { VisualEditor } from "@/components/visual-editor";
import { useConfig } from "@/hooks/useConfig";
import i18n from "@/i18n/config"; import i18n from "@/i18n/config";
import { Layout } from "@/layout"; import { Layout } from "@/layout";
import { EntityType } from "@/services/types"; import { EntityType } from "@/services/types";
const { publicRuntimeConfig } = getConfig();
const CustomWidgetHeader = () => { const CustomWidgetHeader = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -34,12 +33,14 @@ const CustomWidgetHeader = () => {
); );
}; };
const VisualEditorPage = () => { const VisualEditorPage = () => {
const { apiUrl } = useConfig();
return ( return (
<> <>
<VisualEditor /> <VisualEditor />
<UiChatWidget <UiChatWidget
config={{ config={{
apiUrl: publicRuntimeConfig.apiUrl, apiUrl,
channel: "live-chat-tester", channel: "live-chat-tester",
token: "test", token: "test",
language: i18n.language, language: i18n.language,
@ -48,7 +49,7 @@ const VisualEditorPage = () => {
CustomAvatar={() => ( CustomAvatar={() => (
<Avatar <Avatar
sx={{ width: "32px", height: "32px", fontSize: ".75rem" }} sx={{ width: "32px", height: "32px", fontSize: ".75rem" }}
src={getAvatarSrc(EntityType.USER, "bot")} src={getAvatarSrc(apiUrl, EntityType.USER, "bot")}
/> />
)} )}
/> />

View File

@ -7,13 +7,10 @@
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited. * 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
*/ */
import getConfig from "next/config";
import { io, Socket, ManagerOptions, SocketOptions } from "socket.io-client"; import { io, Socket, ManagerOptions, SocketOptions } from "socket.io-client";
import { IOIncomingMessage, IOOutgoingMessage } from "./types/io-message"; import { IOIncomingMessage, IOOutgoingMessage } from "./types/io-message";
const { publicRuntimeConfig } = getConfig();
type SocketIoClientConfig = Partial<ManagerOptions & SocketOptions>; type SocketIoClientConfig = Partial<ManagerOptions & SocketOptions>;
export class SocketIoClient { export class SocketIoClient {
@ -52,13 +49,13 @@ export class SocketIoClient {
private initialized: boolean = false; private initialized: boolean = false;
constructor(socketConfig?: SocketIoClientConfig) { constructor(apiUrl: string, socketConfig?: SocketIoClientConfig) {
this.config = { this.config = {
...SocketIoClient.defaultConfig, ...SocketIoClient.defaultConfig,
...socketConfig, ...socketConfig,
autoConnect: false, autoConnect: false,
}; };
const url = new URL(publicRuntimeConfig.apiUrl); const url = new URL(apiUrl);
this.socket = io(url.origin, this.config); this.socket = io(url.origin, this.config);
} }
@ -160,5 +157,3 @@ export class SocketIoClient {
}); });
} }
} }
export const socketIoClient = new SocketIoClient();

View File

@ -12,22 +12,24 @@ import {
PropsWithChildren, PropsWithChildren,
useContext, useContext,
useEffect, useEffect,
useMemo,
useState, useState,
} from "react"; } from "react";
import { QueryOptions, useQuery } from "react-query"; import { QueryOptions, useQuery } from "react-query";
import { useAuth } from "@/hooks/useAuth"; import { useAuth } from "@/hooks/useAuth";
import { useConfig } from "@/hooks/useConfig";
import { useToast } from "@/hooks/useToast"; import { useToast } from "@/hooks/useToast";
import { SocketIoClient, socketIoClient } from "./SocketIoClient"; import { SocketIoClient } from "./SocketIoClient";
interface socketContext { interface socketContext {
socket: SocketIoClient; socket: SocketIoClient | null;
connected: boolean; connected: boolean;
} }
const socketContext = createContext<socketContext>({ const socketContext = createContext<socketContext>({
socket: socketIoClient, socket: null,
connected: false, connected: false,
}); });
@ -36,13 +38,14 @@ export const useSocket = () => {
}; };
export const SocketProvider = (props: PropsWithChildren) => { export const SocketProvider = (props: PropsWithChildren) => {
const { socket } = useSocket(); const { apiUrl } = useConfig();
const [connected, setConnected] = useState(false); const [connected, setConnected] = useState(false);
const { toast } = useToast(); const { toast } = useToast();
const { user } = useAuth(); const { user } = useAuth();
const socket = useMemo(() => new SocketIoClient(apiUrl), [apiUrl]);
useEffect(() => { useEffect(() => {
if (user) if (user && apiUrl)
socket.init({ socket.init({
onConnect: () => { onConnect: () => {
setConnected(true); setConnected(true);
@ -54,7 +57,7 @@ export const SocketProvider = (props: PropsWithChildren) => {
setConnected(false); setConnected(false);
}, },
}); });
}, [socket, toast, user]); }, [socket, toast, user, apiUrl]);
return ( return (
<socketContext.Provider value={{ socket, connected }}> <socketContext.Provider value={{ socket, connected }}>
@ -73,9 +76,9 @@ export const useSubscribe = <T,>(event: string, callback: (arg: T) => void) => {
const { socket } = useSocket(); const { socket } = useSocket();
useEffect(() => { useEffect(() => {
socket.on<T>(event, callback); socket?.on<T>(event, callback);
return () => socket.off(event, callback); return () => socket?.off(event, callback);
}, [event, callback, socket]); }, [event, callback, socket]);
}; };
@ -83,14 +86,17 @@ export const useSocketGetQuery = <T,>(
url: string, url: string,
options?: Omit<QueryOptions<T, Error, T, string[]>, "queryFn">, options?: Omit<QueryOptions<T, Error, T, string[]>, "queryFn">,
) => { ) => {
const { socket } = useSocket();
const query = useQuery({ const query = useQuery({
...options, ...options,
queryKey: ["socket", "get", url], queryKey: ["socket", "get", url],
queryFn: async () => { queryFn: async () => {
const response = await socketIoClient.get<T>(url); if (!socket) throw new Error("Socket not initialized");
const response = await socket.get<T>(url);
return response.body; return response.body;
}, },
enabled: !!socket,
}); });
return query; return query;