Compare commits
4 Commits
feature/IO
...
hotfix/202
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce02d90c3c | ||
|
|
95a71bea6e | ||
|
|
3b27120d77 | ||
|
|
c2fb010a59 |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -132,10 +132,3 @@ server/job/test/fixtures
|
||||
.github
|
||||
_reference/ragmate/.ragmate.env
|
||||
docker_data
|
||||
/.cursorrules
|
||||
/AGENTS.md
|
||||
/AI_CONTEXT.md
|
||||
/CLAUDE.md
|
||||
/COPILOT.md
|
||||
/GEMINI.md
|
||||
/_reference/select-component-test-plan.md
|
||||
|
||||
@@ -443,6 +443,30 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* DMS top panels: prevent card/table overflow into adjacent column at desktop+zoom */
|
||||
.dms-top-panel-col {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.dms-top-panel-col > .ant-card {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.dms-top-panel-col > .ant-card .ant-card-body {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.dms-top-panel-col .ant-table-wrapper,
|
||||
.dms-top-panel-col .ant-tabs,
|
||||
.dms-top-panel-col .ant-tabs-content,
|
||||
.dms-top-panel-col .ant-tabs-tabpane {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
//.rbc-time-header-gutter {
|
||||
// padding: 0;
|
||||
//}
|
||||
|
||||
@@ -4,6 +4,7 @@ import dayjs from "../../utils/day";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectDarkMode } from "../../redux/application/application.selectors.js";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
isDarkMode: selectDarkMode
|
||||
@@ -21,23 +22,32 @@ export function DmsLogEvents({
|
||||
colorizeJson = false,
|
||||
showDetails = true
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [openSet, setOpenSet] = useState(() => new Set());
|
||||
const [copiedKey, setCopiedKey] = useState(null);
|
||||
|
||||
// Inject JSON highlight styles once (only when colorize is enabled)
|
||||
useEffect(() => {
|
||||
if (!colorizeJson) return;
|
||||
if (typeof document === "undefined") return;
|
||||
if (document.getElementById("json-highlight-styles")) return;
|
||||
const style = document.createElement("style");
|
||||
style.id = "json-highlight-styles";
|
||||
let style = document.getElementById("json-highlight-styles");
|
||||
if (!style) {
|
||||
style = document.createElement("style");
|
||||
style.id = "json-highlight-styles";
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
style.textContent = `
|
||||
.json-key { color: #fa8c16; }
|
||||
.json-string { color: #52c41a; }
|
||||
.json-number { color: #722ed1; }
|
||||
.json-boolean { color: #1890ff; }
|
||||
.json-null { color: #faad14; }
|
||||
.xml-tag { color: #1677ff; }
|
||||
.xml-attr { color: #d46b08; }
|
||||
.xml-value { color: #389e0d; }
|
||||
.xml-decl { color: #7c3aed; }
|
||||
.xml-comment { color: #8c8c8c; }
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}, [colorizeJson]);
|
||||
|
||||
// Trim openSet if logs shrink
|
||||
@@ -65,6 +75,13 @@ export function DmsLogEvents({
|
||||
// Only treat meta as "present" when we are allowed to show details
|
||||
const hasMeta = !isEmpty(meta) && showDetails;
|
||||
const isOpen = hasMeta && openSet.has(idx);
|
||||
const xml = hasMeta ? extractXmlFromMeta(meta) : { request: null, response: null };
|
||||
const hasRequestXml = !!xml.request;
|
||||
const hasResponseXml = !!xml.response;
|
||||
const copyPayload = hasMeta ? getCopyPayload(meta) : null;
|
||||
const copyPayloadKey = `copy-${idx}`;
|
||||
const copyReqKey = `copy-req-${idx}`;
|
||||
const copyResKey = `copy-res-${idx}`;
|
||||
|
||||
return {
|
||||
key: idx,
|
||||
@@ -92,10 +109,42 @@ export function DmsLogEvents({
|
||||
return next;
|
||||
})
|
||||
}
|
||||
style={{ cursor: "pointer", userSelect: "none" }}
|
||||
style={{ cursor: "pointer", userSelect: "none", fontSize: 11 }}
|
||||
>
|
||||
{isOpen ? "Hide details" : "Details"}
|
||||
{isOpen ? t("dms.labels.hide_details") : t("dms.labels.details")}
|
||||
</a>
|
||||
<Divider orientation="vertical" />
|
||||
<a
|
||||
role="button"
|
||||
onClick={() => handleCopyAction(copyPayloadKey, copyPayload, setCopiedKey)}
|
||||
style={{ cursor: "pointer", userSelect: "none", fontSize: 11 }}
|
||||
>
|
||||
{copiedKey === copyPayloadKey ? t("dms.labels.copied") : t("dms.labels.copy")}
|
||||
</a>
|
||||
{hasRequestXml && (
|
||||
<>
|
||||
<Divider orientation="vertical" />
|
||||
<a
|
||||
role="button"
|
||||
onClick={() => handleCopyAction(copyReqKey, xml.request, setCopiedKey)}
|
||||
style={{ cursor: "pointer", userSelect: "none", fontSize: 11 }}
|
||||
>
|
||||
{copiedKey === copyReqKey ? t("dms.labels.copied") : t("dms.labels.copy_request")}
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
{hasResponseXml && (
|
||||
<>
|
||||
<Divider orientation="vertical" />
|
||||
<a
|
||||
role="button"
|
||||
onClick={() => handleCopyAction(copyResKey, xml.response, setCopiedKey)}
|
||||
style={{ cursor: "pointer", userSelect: "none", fontSize: 11 }}
|
||||
>
|
||||
{copiedKey === copyResKey ? t("dms.labels.copied") : t("dms.labels.copy_response")}
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Space>
|
||||
@@ -103,14 +152,30 @@ export function DmsLogEvents({
|
||||
{/* Row 2: details body (only when open) */}
|
||||
{hasMeta && isOpen && (
|
||||
<div style={{ marginLeft: 6 }}>
|
||||
<JsonBlock isDarkMode={isDarkMode} data={meta} colorize={colorizeJson} />
|
||||
<JsonBlock isDarkMode={isDarkMode} data={removeXmlFromMeta(meta)} colorize={colorizeJson} />
|
||||
{hasRequestXml && (
|
||||
<XmlBlock
|
||||
isDarkMode={isDarkMode}
|
||||
title={t("dms.labels.request_xml")}
|
||||
xmlText={xml.request}
|
||||
colorize={colorizeJson}
|
||||
/>
|
||||
)}
|
||||
{hasResponseXml && (
|
||||
<XmlBlock
|
||||
isDarkMode={isDarkMode}
|
||||
title={t("dms.labels.response_xml")}
|
||||
xmlText={xml.response}
|
||||
colorize={colorizeJson}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Space>
|
||||
)
|
||||
};
|
||||
}),
|
||||
[logs, openSet, colorizeJson, isDarkMode, showDetails]
|
||||
[logs, openSet, colorizeJson, copiedKey, isDarkMode, showDetails, t]
|
||||
);
|
||||
|
||||
return <Timeline reverse items={items} />;
|
||||
@@ -179,6 +244,121 @@ const safeStringify = (obj, spaces = 2) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get request/response XML from various Reynolds log meta shapes.
|
||||
* @param meta
|
||||
* @returns {{request: string|null, response: string|null}}
|
||||
*/
|
||||
const extractXmlFromMeta = (meta) => {
|
||||
const request =
|
||||
firstString(meta?.requestXml) ||
|
||||
firstString(meta?.xml?.request) ||
|
||||
firstString(meta?.response?.xml?.request) ||
|
||||
firstString(meta?.response?.requestXml);
|
||||
|
||||
const response =
|
||||
firstString(meta?.responseXml) || firstString(meta?.xml?.response) || firstString(meta?.response?.xml?.response);
|
||||
|
||||
return { request, response };
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the value to copy when clicking the "Copy" action.
|
||||
* @param meta
|
||||
* @returns {*}
|
||||
*/
|
||||
const getCopyPayload = (meta) => {
|
||||
if (meta?.payload != null) return meta.payload;
|
||||
return meta;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove bulky XML fields from object shown in JSON block (XML is rendered separately).
|
||||
* @param meta
|
||||
* @returns {*}
|
||||
*/
|
||||
const removeXmlFromMeta = (meta) => {
|
||||
if (meta == null || typeof meta !== "object") return meta;
|
||||
const cloned = safeClone(meta);
|
||||
if (cloned == null || typeof cloned !== "object") return meta;
|
||||
|
||||
if (typeof cloned.requestXml === "string") delete cloned.requestXml;
|
||||
if (typeof cloned.responseXml === "string") delete cloned.responseXml;
|
||||
|
||||
if (cloned.xml && typeof cloned.xml === "object") {
|
||||
if (typeof cloned.xml.request === "string") delete cloned.xml.request;
|
||||
if (typeof cloned.xml.response === "string") delete cloned.xml.response;
|
||||
if (isEmpty(cloned.xml)) delete cloned.xml;
|
||||
}
|
||||
|
||||
if (cloned.response?.xml && typeof cloned.response.xml === "object") {
|
||||
if (typeof cloned.response.xml.request === "string") delete cloned.response.xml.request;
|
||||
if (typeof cloned.response.xml.response === "string") delete cloned.response.xml.response;
|
||||
if (isEmpty(cloned.response.xml)) delete cloned.response.xml;
|
||||
}
|
||||
|
||||
return cloned;
|
||||
};
|
||||
|
||||
/**
|
||||
* Safe deep clone for plain JSON structures.
|
||||
* @param value
|
||||
* @returns {*}
|
||||
*/
|
||||
const safeClone = (value) => {
|
||||
try {
|
||||
return JSON.parse(JSON.stringify(value));
|
||||
} catch {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* First non-empty string helper.
|
||||
* @param value
|
||||
* @returns {string|null}
|
||||
*/
|
||||
const firstString = (value) => {
|
||||
if (typeof value !== "string") return null;
|
||||
const trimmed = value.trim();
|
||||
return trimmed ? trimmed : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Copy arbitrary text/object to clipboard.
|
||||
* @param key
|
||||
* @param value
|
||||
* @param setCopied
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const handleCopyAction = async (key, value, setCopied) => {
|
||||
const text = typeof value === "string" ? value : safeStringify(value, 2);
|
||||
if (!text) return;
|
||||
const copied = await copyTextToClipboard(text);
|
||||
if (!copied) return;
|
||||
setCopied(key);
|
||||
setTimeout(() => {
|
||||
setCopied((prev) => (prev === key ? null : prev));
|
||||
}, 1200);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clipboard helper (modern async Clipboard API).
|
||||
* @param text
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
const copyTextToClipboard = async (text) => {
|
||||
if (typeof navigator === "undefined" || !navigator.clipboard?.writeText) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* JSON display block with optional syntax highlighting.
|
||||
* @param data
|
||||
@@ -210,6 +390,105 @@ const JsonBlock = ({ data, colorize, isDarkMode }) => {
|
||||
return <pre style={preStyle}>{jsonText}</pre>;
|
||||
};
|
||||
|
||||
/**
|
||||
* XML display block with normalized indentation.
|
||||
* @param title
|
||||
* @param xmlText
|
||||
* @param isDarkMode
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const XmlBlock = ({ title, xmlText, isDarkMode, colorize = false }) => {
|
||||
const base = {
|
||||
margin: "8px 0 0",
|
||||
maxWidth: 720,
|
||||
overflowX: "auto",
|
||||
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
|
||||
fontSize: 12,
|
||||
lineHeight: 1.45,
|
||||
padding: 8,
|
||||
borderRadius: 6,
|
||||
background: isDarkMode ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.04)",
|
||||
border: isDarkMode ? "1px solid rgba(255,255,255,0.12)" : "1px solid rgba(0,0,0,0.08)",
|
||||
color: isDarkMode ? "var(--card-text-fallback)" : "#141414",
|
||||
whiteSpace: "pre"
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: 6 }}>
|
||||
<div style={{ fontSize: 11, fontWeight: 600 }}>{title}</div>
|
||||
{colorize ? (
|
||||
<pre style={base} dangerouslySetInnerHTML={{ __html: highlightXml(formatXml(xmlText)) }} />
|
||||
) : (
|
||||
<pre style={base}>{formatXml(xmlText)}</pre>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Basic XML pretty-printer.
|
||||
* @param xml
|
||||
* @returns {string}
|
||||
*/
|
||||
const formatXml = (xml) => {
|
||||
if (typeof xml !== "string") return "";
|
||||
const normalized = xml.replace(/\r\n/g, "\n").replace(/>\s*</g, ">\n<").trim();
|
||||
const lines = normalized.split("\n");
|
||||
let indent = 0;
|
||||
const out = [];
|
||||
|
||||
for (const rawLine of lines) {
|
||||
const line = rawLine.trim();
|
||||
if (!line) continue;
|
||||
|
||||
if (/^<\/[^>]+>/.test(line)) indent = Math.max(indent - 1, 0);
|
||||
out.push(`${" ".repeat(indent)}${line}`);
|
||||
|
||||
const opens = (line.match(/<[^/!?][^>]*>/g) || []).length;
|
||||
const closes = (line.match(/<\/[^>]+>/g) || []).length;
|
||||
const selfClosing = (line.match(/<[^>]+\/>/g) || []).length;
|
||||
const declaration = /^<\?xml/.test(line) ? 1 : 0;
|
||||
|
||||
indent += opens - closes - selfClosing - declaration;
|
||||
if (indent < 0) indent = 0;
|
||||
}
|
||||
|
||||
return out.join("\n");
|
||||
};
|
||||
|
||||
/**
|
||||
* Syntax highlight pretty-printed XML text for HTML display.
|
||||
* @param xmlText
|
||||
* @returns {string}
|
||||
*/
|
||||
const highlightXml = (xmlText) => {
|
||||
const esc = String(xmlText || "")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
const lines = esc.split("\n");
|
||||
|
||||
return lines
|
||||
.map((line) => {
|
||||
let out = line;
|
||||
|
||||
out = out.replace(/(<!--[\s\S]*?-->)/g, '<span class="xml-comment">$1</span>');
|
||||
out = out.replace(/(<\?xml[\s\S]*?\?>)/g, '<span class="xml-decl">$1</span>');
|
||||
|
||||
out = out.replace(/(<\/?)([A-Za-z_][\w:.-]*)([\s\S]*?)(\/?>)/g, (_m, open, tag, attrs, close) => {
|
||||
const coloredAttrs = attrs.replace(
|
||||
/([A-Za-z_][\w:.-]*)(=)("[^"]*"|'[^']*'|"[\s\S]*?"|'[\s\S]*?')/g,
|
||||
'<span class="xml-attr">$1</span>$2<span class="xml-value">$3</span>'
|
||||
);
|
||||
return `${open}<span class="xml-tag">${tag}</span>${coloredAttrs}${close}`;
|
||||
});
|
||||
|
||||
return out;
|
||||
})
|
||||
.join("\n");
|
||||
};
|
||||
|
||||
/**
|
||||
* Syntax highlight JSON text for HTML display.
|
||||
* @param jsonText
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import SocketIO from "socket.io-client";
|
||||
import { auth } from "../../firebase/firebase.utils";
|
||||
import { store } from "../../redux/store";
|
||||
@@ -18,7 +18,6 @@ import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react";
|
||||
import { INITIAL_NOTIFICATIONS, SocketContext } from "./useSocket.js";
|
||||
|
||||
const LIMIT = INITIAL_NOTIFICATIONS;
|
||||
const TOKEN_SYNC_INTERVAL_MS = 10 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* Socket Provider - Scenario Notifications / Web Socket related items
|
||||
@@ -31,7 +30,6 @@ const TOKEN_SYNC_INTERVAL_MS = 10 * 60 * 1000;
|
||||
*/
|
||||
const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
|
||||
const socketRef = useRef(null);
|
||||
const tokenSyncIntervalRef = useRef(null);
|
||||
const [clientId, setClientId] = useState(null);
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const notification = useNotification();
|
||||
@@ -149,30 +147,6 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
|
||||
onError: (err) => console.error("MARK_ALL_NOTIFICATIONS_READ error:", err)
|
||||
});
|
||||
|
||||
const reconnectSocket = useCallback(
|
||||
async ({ forceRefreshToken = true } = {}) => {
|
||||
const socketInstance = socketRef.current;
|
||||
if (!socketInstance || !auth.currentUser || !bodyshop?.id) return false;
|
||||
|
||||
try {
|
||||
const token = await auth.currentUser.getIdToken(forceRefreshToken);
|
||||
socketInstance.auth = { token, bodyshopId: bodyshop.id };
|
||||
|
||||
if (socketInstance.connected) {
|
||||
socketInstance.emit("update-token", { token, bodyshopId: bodyshop.id });
|
||||
}
|
||||
|
||||
socketInstance.disconnect();
|
||||
socketInstance.connect();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Socket reconnect failed:", error?.message || error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
[bodyshop?.id]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const initializeSocket = async (token) => {
|
||||
if (!bodyshop?.id || socketRef.current) return;
|
||||
@@ -280,60 +254,25 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const syncCurrentTokenToSocket = async () => {
|
||||
try {
|
||||
if (!auth.currentUser || !bodyshop?.id) return;
|
||||
const token = await auth.currentUser.getIdToken();
|
||||
socketInstance.auth = { token, bodyshopId: bodyshop.id };
|
||||
socketInstance.emit("update-token", { token, bodyshopId: bodyshop.id });
|
||||
} catch (error) {
|
||||
console.error("Failed to sync token to socket:", error?.message || error);
|
||||
}
|
||||
};
|
||||
|
||||
const forceRefreshAndSyncToken = async () => {
|
||||
try {
|
||||
if (!auth.currentUser || !bodyshop?.id) return;
|
||||
const token = await auth.currentUser.getIdToken(true);
|
||||
socketInstance.auth = { token, bodyshopId: bodyshop.id };
|
||||
socketInstance.emit("update-token", { token, bodyshopId: bodyshop.id });
|
||||
} catch (error) {
|
||||
console.error("Failed to force-refresh token for socket:", error?.message || error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleConnect = () => {
|
||||
socketInstance.emit("join-bodyshop-room", bodyshop.id);
|
||||
syncCurrentTokenToSocket();
|
||||
setClientId(socketInstance.id);
|
||||
setIsConnected(true);
|
||||
store.dispatch(setWssStatus("connected"));
|
||||
};
|
||||
|
||||
const handleReconnect = () => {
|
||||
forceRefreshAndSyncToken();
|
||||
setIsConnected(true);
|
||||
store.dispatch(setWssStatus("connected"));
|
||||
};
|
||||
|
||||
const handleTokenUpdated = ({ success, error }) => {
|
||||
if (success) return;
|
||||
const err = String(error || "");
|
||||
if (/stale token|id-token-expired/i.test(err)) {
|
||||
forceRefreshAndSyncToken();
|
||||
}
|
||||
};
|
||||
|
||||
const handleConnectionError = (err) => {
|
||||
console.error("Socket connection error:", err);
|
||||
setIsConnected(false);
|
||||
if (err?.message?.includes("auth/id-token-expired")) {
|
||||
if (err.message.includes("auth/id-token-expired")) {
|
||||
console.warn("Token expired, refreshing...");
|
||||
auth.currentUser?.getIdToken(true).then((newToken) => {
|
||||
socketInstance.auth = { token: newToken, bodyshopId: bodyshop.id };
|
||||
if (socketInstance.connected) {
|
||||
socketInstance.emit("update-token", { token: newToken, bodyshopId: bodyshop.id });
|
||||
}
|
||||
socketInstance.auth = { token: newToken };
|
||||
socketInstance.connect();
|
||||
});
|
||||
} else {
|
||||
@@ -574,23 +513,10 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
|
||||
socketInstance.on("notification", handleNotification);
|
||||
socketInstance.on("sync-notification-read", handleSyncNotificationRead);
|
||||
socketInstance.on("sync-all-notifications-read", handleSyncAllNotificationsRead);
|
||||
socketInstance.on("token-updated", handleTokenUpdated);
|
||||
|
||||
if (tokenSyncIntervalRef.current) {
|
||||
clearInterval(tokenSyncIntervalRef.current);
|
||||
}
|
||||
tokenSyncIntervalRef.current = setInterval(() => {
|
||||
if (!socketInstance.connected) return;
|
||||
syncCurrentTokenToSocket();
|
||||
}, TOKEN_SYNC_INTERVAL_MS);
|
||||
};
|
||||
|
||||
const unsubscribe = auth.onIdTokenChanged(async (user) => {
|
||||
if (!user) {
|
||||
if (tokenSyncIntervalRef.current) {
|
||||
clearInterval(tokenSyncIntervalRef.current);
|
||||
tokenSyncIntervalRef.current = null;
|
||||
}
|
||||
socketRef.current?.disconnect();
|
||||
socketRef.current = null;
|
||||
setIsConnected(false);
|
||||
@@ -599,10 +525,7 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
|
||||
|
||||
const token = await user.getIdToken();
|
||||
if (socketRef.current) {
|
||||
socketRef.current.auth = { token, bodyshopId: bodyshop.id };
|
||||
if (socketRef.current.connected) {
|
||||
socketRef.current.emit("update-token", { token, bodyshopId: bodyshop.id });
|
||||
}
|
||||
socketRef.current.emit("update-token", { token, bodyshopId: bodyshop.id });
|
||||
} else {
|
||||
initializeSocket(token).catch((err) =>
|
||||
console.error("Something went wrong Initializing Sockets:", err?.message || "")
|
||||
@@ -612,10 +535,6 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
if (tokenSyncIntervalRef.current) {
|
||||
clearInterval(tokenSyncIntervalRef.current);
|
||||
tokenSyncIntervalRef.current = null;
|
||||
}
|
||||
if (socketRef.current) {
|
||||
socketRef.current.disconnect();
|
||||
socketRef.current = null;
|
||||
@@ -630,7 +549,6 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
|
||||
socket: socketRef.current,
|
||||
clientId,
|
||||
isConnected,
|
||||
reconnectSocket,
|
||||
markNotificationRead,
|
||||
markAllNotificationsRead,
|
||||
scenarioNotificationsOn: Realtime_Notifications_UI?.treatment === "on"
|
||||
|
||||
@@ -77,7 +77,6 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
const { t } = useTranslation();
|
||||
const [resetAfterReconnect, setResetAfterReconnect] = useState(false);
|
||||
const [allocationsSummary, setAllocationsSummary] = useState(null);
|
||||
const [reconnectNonce, setReconnectNonce] = useState(0);
|
||||
|
||||
// Compute a single normalized mode and pick the proper socket
|
||||
const mode = getDmsMode(bodyshop, Fortellis.treatment); // "rr" | "fortellis" | "cdk" | "pbs" | "none"
|
||||
@@ -115,7 +114,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
|
||||
const notification = useNotification();
|
||||
|
||||
const { socket: wsssocket, reconnectSocket } = useSocket();
|
||||
const { socket: wsssocket } = useSocket();
|
||||
const activeSocket = useMemo(() => (isWssMode(mode) ? wsssocket : legacySocket), [mode, wsssocket]);
|
||||
|
||||
const [isConnected, setIsConnected] = useState(!!activeSocket?.connected);
|
||||
@@ -164,42 +163,23 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
const providerLabel = useMemo(
|
||||
() =>
|
||||
({
|
||||
[DMS_MAP.reynolds]: "Reynolds",
|
||||
[DMS_MAP.fortellis]: "Fortellis",
|
||||
[DMS_MAP.cdk]: "CDK",
|
||||
[DMS_MAP.pbs]: "PBS"
|
||||
})[mode] || "DMS",
|
||||
[mode]
|
||||
[DMS_MAP.reynolds]: t("dms.labels.provider_reynolds"),
|
||||
[DMS_MAP.fortellis]: t("dms.labels.provider_fortellis"),
|
||||
[DMS_MAP.cdk]: t("dms.labels.provider_cdk"),
|
||||
[DMS_MAP.pbs]: t("dms.labels.provider_pbs")
|
||||
})[mode] || t("dms.labels.provider_dms"),
|
||||
[mode, t]
|
||||
);
|
||||
|
||||
const transportLabel = isWssMode(mode) ? "(WSS)" : "(WS)";
|
||||
const transportLabel = isWssMode(mode) ? t("dms.labels.transport_wss") : t("dms.labels.transport_ws");
|
||||
|
||||
const bannerMessage = `Posting to ${providerLabel} | ${transportLabel} | ${
|
||||
isConnected ? "Connected" : "Disconnected"
|
||||
}`;
|
||||
const bannerMessage = t("dms.labels.banner_message", {
|
||||
provider: providerLabel,
|
||||
transport: transportLabel,
|
||||
status: isConnected ? t("dms.labels.banner_status_connected") : t("dms.labels.banner_status_disconnected")
|
||||
});
|
||||
|
||||
const resetKey = useMemo(() => `${mode || "none"}-${jobId || "none"}`, [mode, jobId]);
|
||||
const customerSelectorKey = useMemo(() => `${resetKey}-${reconnectNonce}`, [resetKey, reconnectNonce]);
|
||||
|
||||
const handleReconnectClick = async () => {
|
||||
setResetAfterReconnect(true);
|
||||
setReconnectNonce((n) => n + 1);
|
||||
|
||||
if (!activeSocket) return;
|
||||
|
||||
if (isWssMode(mode)) {
|
||||
setActiveLogLevel(logLevel);
|
||||
const didReconnect = await reconnectSocket?.({ forceRefreshToken: true });
|
||||
if (!didReconnect) {
|
||||
activeSocket.disconnect();
|
||||
setTimeout(() => activeSocket.connect(), 100);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
activeSocket.disconnect();
|
||||
setTimeout(() => activeSocket.connect(), 100);
|
||||
};
|
||||
|
||||
// 🔄 Hard reset of local + server-side DMS context when the page/job loads
|
||||
useEffect(() => {
|
||||
@@ -246,7 +226,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
errText ||
|
||||
t("dms.errors.exportfailedgeneric", "We couldn't complete the export. Please try again.");
|
||||
|
||||
const vendorTitle = title || (isRrMode ? "Reynolds" : "DMS");
|
||||
const vendorTitle = title || (isRrMode ? t("dms.labels.provider_reynolds") : t("dms.labels.provider_dms"));
|
||||
|
||||
const isRrOpenRoLimit =
|
||||
isRrMode &&
|
||||
@@ -321,7 +301,9 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
{
|
||||
timestamp: new Date(),
|
||||
level: "warn",
|
||||
message: `Reconnected to ${isRrMode ? "RR" : mode === DMS_MAP.fortellis ? "Fortellis" : "DMS"} Export Service`
|
||||
message: t("dms.labels.reconnected_export_service", {
|
||||
provider: isRrMode ? t("dms.labels.provider_reynolds") : providerLabel
|
||||
})
|
||||
}
|
||||
]);
|
||||
};
|
||||
@@ -380,14 +362,12 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
{
|
||||
timestamp: new Date(),
|
||||
level: "INFO",
|
||||
message:
|
||||
"Repair Order created in Reynolds. Complete validation in Reynolds, then click Finished/Close to finalize."
|
||||
message: t("dms.labels.rr_validation_message")
|
||||
}
|
||||
]);
|
||||
notification.info({
|
||||
title: "Reynolds RO created",
|
||||
description:
|
||||
"Complete validation in Reynolds, then click Finished/Close to finalize and mark this export complete.",
|
||||
title: t("dms.labels.rr_validation_notice_title"),
|
||||
description: t("dms.labels.rr_validation_notice_description"),
|
||||
duration: 8
|
||||
});
|
||||
};
|
||||
@@ -399,8 +379,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
{
|
||||
timestamp: new Date(),
|
||||
level: "INFO",
|
||||
message:
|
||||
"Repair Order created in Reynolds. Complete validation in Reynolds, then click Finished/Close to finalize.",
|
||||
message: t("dms.labels.rr_validation_message"),
|
||||
meta: { payload }
|
||||
}
|
||||
]);
|
||||
@@ -428,7 +407,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
activeSocket.disconnect();
|
||||
}
|
||||
};
|
||||
}, [mode, activeSocket, channels, logLevel, notification, t, insertAuditTrail, history]);
|
||||
}, [mode, activeSocket, channels, logLevel, notification, t, insertAuditTrail, history, isRrMode, providerLabel]);
|
||||
|
||||
// RR finalize callback (unchanged public behavior)
|
||||
const handleRrValidationFinished = () => {
|
||||
@@ -471,7 +450,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
<AlertComponent style={{ marginBottom: 10 }} title={bannerMessage} type="warning" showIcon closable />
|
||||
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col md={24} lg={10} className="dms-equal-height-col">
|
||||
<Col xs={24} xxl={10} className="dms-equal-height-col dms-top-panel-col">
|
||||
{!isRrMode ? (
|
||||
<DmsAllocationsSummary
|
||||
key={resetKey}
|
||||
@@ -511,7 +490,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
)}
|
||||
</Col>
|
||||
|
||||
<Col md={24} lg={14} className="dms-equal-height-col">
|
||||
<Col xs={24} xxl={14} className="dms-equal-height-col dms-top-panel-col">
|
||||
<DmsPostForm
|
||||
key={resetKey}
|
||||
socket={activeSocket}
|
||||
@@ -525,7 +504,6 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
</Col>
|
||||
|
||||
<DmsCustomerSelector
|
||||
key={customerSelectorKey}
|
||||
jobid={jobId}
|
||||
job={data?.jobs_by_pk}
|
||||
bodyshop={bodyshop}
|
||||
@@ -550,15 +528,17 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
<Switch
|
||||
checked={colorizeJson}
|
||||
onChange={setColorizeJson}
|
||||
checkedChildren="Color JSON"
|
||||
unCheckedChildren="Plain JSON"
|
||||
checkedChildren={t("dms.labels.color_json")}
|
||||
unCheckedChildren={t("dms.labels.plain_json")}
|
||||
/>
|
||||
<Button onClick={toggleDetailsAll}>{detailsOpen ? "Collapse All" : "Expand All"}</Button>
|
||||
<Button onClick={toggleDetailsAll}>
|
||||
{detailsOpen ? t("dms.labels.collapse_all") : t("dms.labels.expand_all")}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Select
|
||||
placeholder="Log Level"
|
||||
placeholder={t("dms.labels.log_level")}
|
||||
value={logLevel}
|
||||
onChange={(value) => {
|
||||
setLogLevel(value);
|
||||
@@ -571,8 +551,22 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
<Select.Option key="WARN">WARN</Select.Option>
|
||||
<Select.Option key="ERROR">ERROR</Select.Option>
|
||||
</Select>
|
||||
<Button onClick={() => setLogs([])}>Clear Logs</Button>
|
||||
<Button onClick={handleReconnectClick}>Reconnect</Button>
|
||||
<Button onClick={() => setLogs([])}>{t("dms.labels.clear_logs")}</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setLogs([]);
|
||||
setResetAfterReconnect(true);
|
||||
if (isWssMode(mode)) {
|
||||
setActiveLogLevel(logLevel);
|
||||
}
|
||||
if (activeSocket) {
|
||||
activeSocket.disconnect();
|
||||
setTimeout(() => activeSocket.connect(), 100);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t("dms.labels.reconnect")}
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -1052,7 +1052,36 @@
|
||||
"earlyrorequired.message": "This job requires an early Repair Order to be created before posting to Reynolds. Please use the admin panel to create the early RO first."
|
||||
},
|
||||
"labels": {
|
||||
"refreshallocations": "Refresh to see DMS Allocations."
|
||||
"refreshallocations": "Refresh to see DMS Allocations.",
|
||||
"provider_reynolds": "Reynolds",
|
||||
"provider_fortellis": "Fortellis",
|
||||
"provider_cdk": "CDK",
|
||||
"provider_pbs": "PBS",
|
||||
"provider_dms": "DMS",
|
||||
"transport_wss": "(WSS)",
|
||||
"transport_ws": "(WS)",
|
||||
"banner_status_connected": "Connected",
|
||||
"banner_status_disconnected": "Disconnected",
|
||||
"banner_message": "Posting to {{provider}} | {{transport}} | {{status}}",
|
||||
"reconnected_export_service": "Reconnected to {{provider}} Export Service",
|
||||
"rr_validation_message": "Repair Order created in Reynolds. Complete validation in Reynolds, then click Finished/Close to finalize.",
|
||||
"rr_validation_notice_title": "Reynolds RO created",
|
||||
"rr_validation_notice_description": "Complete validation in Reynolds, then click Finished/Close to finalize and mark this export complete.",
|
||||
"color_json": "Color JSON",
|
||||
"plain_json": "Plain JSON",
|
||||
"collapse_all": "Collapse All",
|
||||
"expand_all": "Expand All",
|
||||
"log_level": "Log Level",
|
||||
"clear_logs": "Clear Logs",
|
||||
"reconnect": "Reconnect",
|
||||
"details": "Details",
|
||||
"hide_details": "Hide details",
|
||||
"copy": "Copy",
|
||||
"copied": "Copied",
|
||||
"copy_request": "Copy Request",
|
||||
"copy_response": "Copy Response",
|
||||
"request_xml": "Request XML",
|
||||
"response_xml": "Response XML"
|
||||
}
|
||||
},
|
||||
"documents": {
|
||||
|
||||
@@ -1052,7 +1052,36 @@
|
||||
"earlyrorequired.message": ""
|
||||
},
|
||||
"labels": {
|
||||
"refreshallocations": ""
|
||||
"refreshallocations": "",
|
||||
"provider_reynolds": "",
|
||||
"provider_fortellis": "",
|
||||
"provider_cdk": "",
|
||||
"provider_pbs": "",
|
||||
"provider_dms": "",
|
||||
"transport_wss": "",
|
||||
"transport_ws": "",
|
||||
"banner_status_connected": "",
|
||||
"banner_status_disconnected": "",
|
||||
"banner_message": "",
|
||||
"reconnected_export_service": "",
|
||||
"rr_validation_message": "",
|
||||
"rr_validation_notice_title": "",
|
||||
"rr_validation_notice_description": "",
|
||||
"color_json": "",
|
||||
"plain_json": "",
|
||||
"collapse_all": "",
|
||||
"expand_all": "",
|
||||
"log_level": "",
|
||||
"clear_logs": "",
|
||||
"reconnect": "",
|
||||
"details": "",
|
||||
"hide_details": "",
|
||||
"copy": "",
|
||||
"copied": "",
|
||||
"copy_request": "",
|
||||
"copy_response": "",
|
||||
"request_xml": "",
|
||||
"response_xml": ""
|
||||
}
|
||||
},
|
||||
"documents": {
|
||||
|
||||
@@ -1052,7 +1052,36 @@
|
||||
"earlyrorequired.message": ""
|
||||
},
|
||||
"labels": {
|
||||
"refreshallocations": ""
|
||||
"refreshallocations": "",
|
||||
"provider_reynolds": "",
|
||||
"provider_fortellis": "",
|
||||
"provider_cdk": "",
|
||||
"provider_pbs": "",
|
||||
"provider_dms": "",
|
||||
"transport_wss": "",
|
||||
"transport_ws": "",
|
||||
"banner_status_connected": "",
|
||||
"banner_status_disconnected": "",
|
||||
"banner_message": "",
|
||||
"reconnected_export_service": "",
|
||||
"rr_validation_message": "",
|
||||
"rr_validation_notice_title": "",
|
||||
"rr_validation_notice_description": "",
|
||||
"color_json": "",
|
||||
"plain_json": "",
|
||||
"collapse_all": "",
|
||||
"expand_all": "",
|
||||
"log_level": "",
|
||||
"clear_logs": "",
|
||||
"reconnect": "",
|
||||
"details": "",
|
||||
"hide_details": "",
|
||||
"copy": "",
|
||||
"copied": "",
|
||||
"copy_request": "",
|
||||
"copy_response": "",
|
||||
"request_xml": "",
|
||||
"response_xml": ""
|
||||
}
|
||||
},
|
||||
"documents": {
|
||||
|
||||
@@ -86,9 +86,6 @@ async function FetchSubscriptions({ redisHelpers, socket, jobid, SubscriptionObj
|
||||
logRequest: false
|
||||
});
|
||||
const SubscriptionMeta = subscriptions.data.subscriptions.find((s) => s.subscriptionId === SubscriptionID);
|
||||
if (!SubscriptionMeta) {
|
||||
throw new Error(`Subscription metadata not found for SubscriptionID: ${SubscriptionID}`);
|
||||
}
|
||||
if (setSessionTransactionData) {
|
||||
await setSessionTransactionData(
|
||||
socket.id,
|
||||
@@ -105,15 +102,11 @@ async function FetchSubscriptions({ redisHelpers, socket, jobid, SubscriptionObj
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function GetDepartmentId({ apiName, debug = false, SubscriptionMeta, overrideDepartmentId }) {
|
||||
if (!apiName) throw new Error("apiName not provided. Unable to get department without apiName.");
|
||||
if (!SubscriptionMeta || !Array.isArray(SubscriptionMeta.apiDmsInfo)) {
|
||||
throw new Error("Subscription metadata missing apiDmsInfo.");
|
||||
}
|
||||
if (debug) {
|
||||
console.log("API Names & Departments ");
|
||||
console.log("===========");
|
||||
@@ -125,8 +118,9 @@ async function GetDepartmentId({ apiName, debug = false, SubscriptionMeta, overr
|
||||
.find((info) => info.name === apiName)?.departments; //Departments are categorized by API name and have an array of departments.
|
||||
|
||||
if (overrideDepartmentId) {
|
||||
return departmentIds && departmentIds.find((d) => d.id === overrideDepartmentId)?.id;
|
||||
return departmentIds && departmentIds.find(d => d.id === overrideDepartmentId)?.id
|
||||
} else {
|
||||
|
||||
return departmentIds && departmentIds[0] && departmentIds[0].id; //TODO: This makes the assumption that there is only 1 department.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,52 +180,22 @@ async function FortellisSelectedCustomer({ socket, redisHelpers, selectedCustome
|
||||
getTransactionType(jobid),
|
||||
FortellisCacheEnums.txEnvelope
|
||||
);
|
||||
if (!JobData || !txEnvelope) {
|
||||
const friendlyMessage =
|
||||
"Fortellis export context was lost after reconnect. Click Post again to restart the Fortellis flow.";
|
||||
CreateFortellisLogEvent(socket, "WARN", friendlyMessage, {
|
||||
jobid,
|
||||
hasJobData: !!JobData,
|
||||
hasTxEnvelope: !!txEnvelope
|
||||
});
|
||||
socket.emit("export-failed", {
|
||||
title: "Fortellis",
|
||||
severity: "warning",
|
||||
errorCode: "FORTELLIS_CONTEXT_MISSING",
|
||||
friendlyMessage
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const DMSVid = await redisHelpers.getSessionTransactionData(
|
||||
socket.id,
|
||||
getTransactionType(JobData.id),
|
||||
FortellisCacheEnums.DMSVid
|
||||
);
|
||||
try {
|
||||
const DMSVid = await redisHelpers.getSessionTransactionData(
|
||||
socket.id,
|
||||
getTransactionType(JobData.id),
|
||||
FortellisCacheEnums.DMSVid
|
||||
);
|
||||
if (!DMSVid) {
|
||||
const friendlyMessage =
|
||||
"Fortellis vehicle context is missing after reconnect. Click Post again to restart the Fortellis flow.";
|
||||
CreateFortellisLogEvent(socket, "WARN", friendlyMessage, {
|
||||
jobid,
|
||||
hasDMSVid: !!DMSVid
|
||||
});
|
||||
socket.emit("export-failed", {
|
||||
title: "Fortellis",
|
||||
severity: "warning",
|
||||
errorCode: "FORTELLIS_CONTEXT_MISSING",
|
||||
friendlyMessage
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let DMSCust;
|
||||
if (selectedCustomerId) {
|
||||
CreateFortellisLogEvent(socket, "DEBUG", `{3.1} Querying the Customer using Customer ID: ${selectedCustomerId}`);
|
||||
|
||||
//Get cust list from Redis. Return the item
|
||||
const DMSCustList =
|
||||
(await getSessionTransactionData(socket.id, getTransactionType(jobid), FortellisCacheEnums.DMSCustList)) || [];
|
||||
const DMSCustList = await getSessionTransactionData(
|
||||
socket.id,
|
||||
getTransactionType(jobid),
|
||||
FortellisCacheEnums.DMSCustList
|
||||
);
|
||||
const existingCustomerInDMSCustList = DMSCustList.find((c) => c.customerId === selectedCustomerId);
|
||||
DMSCust = existingCustomerInDMSCustList || {
|
||||
customerId: selectedCustomerId //This is the fall back in case it is the generic customer.
|
||||
@@ -336,7 +306,7 @@ async function FortellisSelectedCustomer({ socket, redisHelpers, selectedCustome
|
||||
//There was something wrong. Throw an error to trigger clean up.
|
||||
//throw new Error("Error posting DMS Batch Transaction");
|
||||
}
|
||||
} catch {
|
||||
} catch (error) {
|
||||
//Clean up the transaction and insert a faild error code
|
||||
// //Get the error code
|
||||
CreateFortellisLogEvent(socket, "DEBUG", `{6.1} Getting errors for Transaction ID ${DMSTransHeader.transID}`);
|
||||
@@ -366,12 +336,6 @@ async function FortellisSelectedCustomer({ socket, redisHelpers, selectedCustome
|
||||
stack: error.stack,
|
||||
data: error.errorData
|
||||
});
|
||||
socket.emit("export-failed", {
|
||||
title: "Fortellis",
|
||||
severity: "error",
|
||||
error: error.message,
|
||||
friendlyMessage: "Fortellis export failed. Please click Post again to retry."
|
||||
});
|
||||
await InsertFailedExportLog({
|
||||
socket,
|
||||
JobData,
|
||||
@@ -793,7 +757,13 @@ async function InsertDmsVehicle({ socket, redisHelpers, JobData, txEnvelope, DMS
|
||||
modelAbrev: txEnvelope.dms_model,
|
||||
// "modelDescription": "SILVERADO 1500 2WD EXT CAB LT",
|
||||
// "modelType": "T",
|
||||
modelYear: JobData.v_model_yr,
|
||||
modelYear:
|
||||
JobData.v_model_yr &&
|
||||
(JobData.v_model_yr < 100
|
||||
? JobData.v_model_yr >= (moment().year() + 1) % 100
|
||||
? 1900 + parseInt(JobData.v_model_yr, 10)
|
||||
: 2000 + parseInt(JobData.v_model_yr, 10)
|
||||
: JobData.v_model_yr),
|
||||
// "numberOfEngineCylinders": 4,
|
||||
odometerStatus: txEnvelope.kmout,
|
||||
// "options": [
|
||||
@@ -952,6 +922,10 @@ async function UpdateDmsVehicle({ socket, redisHelpers, JobData, DMSVeh, DMSCust
|
||||
delete DMSVehToSend.invoice;
|
||||
delete DMSVehToSend.inventoryAccount;
|
||||
|
||||
!DMSVehToSend.vehicle.engineNumber && delete DMSVehToSend.vehicle.engineNumber;
|
||||
!DMSVehToSend.vehicle.saleClassValue && DMSVehToSend.vehicle.saleClassValue === "MISC";
|
||||
!DMSVehToSend.vehicle.exteriorColor && delete DMSVehToSend.vehicle.exteriorColor;
|
||||
|
||||
const result = await MakeFortellisCall({
|
||||
...FortellisActions.UpdateVehicle,
|
||||
requestSearchParams: {},
|
||||
|
||||
@@ -48,6 +48,46 @@ const resolveJobId = (explicit, payload, job) => explicit || payload?.jobId || j
|
||||
*/
|
||||
const resolveVin = ({ tx, job }) => tx?.jobData?.vin || job?.v_vin || null;
|
||||
|
||||
/**
|
||||
* Extract request/response XML from RR response/result shapes.
|
||||
* @param rrObj
|
||||
* @returns {{requestXml: string|null, responseXml: string|null}}
|
||||
*/
|
||||
const extractRRXmlPair = (rrObj) => {
|
||||
const xml = rrObj?.xml;
|
||||
|
||||
let requestXml = null;
|
||||
let responseXml = null;
|
||||
|
||||
if (typeof xml === "string") {
|
||||
requestXml = xml;
|
||||
} else {
|
||||
if (typeof xml?.request === "string") requestXml = xml.request;
|
||||
else if (typeof xml?.req === "string") requestXml = xml.req;
|
||||
else if (typeof xml?.starXml === "string") requestXml = xml.starXml;
|
||||
if (typeof xml?.response === "string") responseXml = xml.response;
|
||||
}
|
||||
|
||||
if (!requestXml && typeof rrObj?.requestXml === "string") requestXml = rrObj.requestXml;
|
||||
if (!responseXml && typeof rrObj?.responseXml === "string") responseXml = rrObj.responseXml;
|
||||
|
||||
return { requestXml, responseXml };
|
||||
};
|
||||
|
||||
/**
|
||||
* Add Reynolds request/response XML to RR log metadata when available.
|
||||
* @param rrObj
|
||||
* @param meta
|
||||
* @returns {*}
|
||||
*/
|
||||
const withRRRequestXml = (rrObj, meta = {}) => {
|
||||
const { requestXml, responseXml } = extractRRXmlPair(rrObj);
|
||||
const xmlMeta = {};
|
||||
if (requestXml) xmlMeta.requestXml = requestXml;
|
||||
if (responseXml) xmlMeta.responseXml = responseXml;
|
||||
return Object.keys(xmlMeta).length ? { ...meta, ...xmlMeta } : meta;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sort vehicle owners first in the list, preserving original order otherwise.
|
||||
* @param list
|
||||
@@ -154,15 +194,13 @@ const setJobDmsIdForSocket = async ({ socket, jobId, dmsId, dmsCustomerId, dmsAd
|
||||
if (!token) throw new Error("Missing auth token for setJobDmsIdForSocket");
|
||||
|
||||
const client = new GraphQLClient(endpoint, {});
|
||||
await client
|
||||
.setHeaders({ Authorization: `Bearer ${token}` })
|
||||
.request(queries.SET_JOB_DMS_ID, {
|
||||
id: jobId,
|
||||
dms_id: String(dmsId),
|
||||
dms_customer_id: dmsCustomerId ? String(dmsCustomerId) : null,
|
||||
dms_advisor_id: dmsAdvisorId ? String(dmsAdvisorId) : null,
|
||||
kmin: mileageIn != null && mileageIn > 0 ? parseInt(mileageIn, 10) : null
|
||||
});
|
||||
await client.setHeaders({ Authorization: `Bearer ${token}` }).request(queries.SET_JOB_DMS_ID, {
|
||||
id: jobId,
|
||||
dms_id: String(dmsId),
|
||||
dms_customer_id: dmsCustomerId ? String(dmsCustomerId) : null,
|
||||
dms_advisor_id: dmsAdvisorId ? String(dmsAdvisorId) : null,
|
||||
kmin: mileageIn != null && mileageIn > 0 ? parseInt(mileageIn, 10) : null
|
||||
});
|
||||
|
||||
CreateRRLogEvent(socket, "INFO", "Linked job.dms_id to RR RO", {
|
||||
jobId,
|
||||
@@ -511,7 +549,11 @@ const registerRREvents = ({ socket, redisHelpers }) => {
|
||||
});
|
||||
|
||||
// Filter out invalid values
|
||||
if (selectedCustNo === "undefined" || selectedCustNo === "null" || (selectedCustNo && selectedCustNo.trim() === "")) {
|
||||
if (
|
||||
selectedCustNo === "undefined" ||
|
||||
selectedCustNo === "null" ||
|
||||
(selectedCustNo && selectedCustNo.trim() === "")
|
||||
) {
|
||||
selectedCustNo = null;
|
||||
}
|
||||
|
||||
@@ -705,42 +747,52 @@ const registerRREvents = ({ socket, redisHelpers }) => {
|
||||
|
||||
const outsdRoNo = data?.outsdRoNo ?? job?.ro_number ?? job?.id ?? null;
|
||||
|
||||
CreateRRLogEvent(socket, "DEBUG", "Early RO created - checking dmsRoNo", {
|
||||
dmsRoNo,
|
||||
resultRoNo: result?.roNo,
|
||||
dataRoNo: data?.dmsRoNo,
|
||||
jobId: rid
|
||||
});
|
||||
CreateRRLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
"Early RO created - checking dmsRoNo",
|
||||
withRRRequestXml(result, {
|
||||
dmsRoNo,
|
||||
resultRoNo: result?.roNo,
|
||||
dataRoNo: data?.dmsRoNo,
|
||||
jobId: rid
|
||||
})
|
||||
);
|
||||
|
||||
// ✅ Persist DMS RO number, customer ID, advisor ID, and mileage on the job
|
||||
if (dmsRoNo) {
|
||||
const mileageIn = txEnvelope?.kmin ?? null;
|
||||
CreateRRLogEvent(socket, "DEBUG", "Calling setJobDmsIdForSocket", {
|
||||
jobId: rid,
|
||||
CreateRRLogEvent(socket, "DEBUG", "Calling setJobDmsIdForSocket", {
|
||||
jobId: rid,
|
||||
dmsId: dmsRoNo,
|
||||
customerId: effectiveCustNo,
|
||||
advisorId: String(advisorNo),
|
||||
mileageIn
|
||||
});
|
||||
await setJobDmsIdForSocket({
|
||||
socket,
|
||||
jobId: rid,
|
||||
await setJobDmsIdForSocket({
|
||||
socket,
|
||||
jobId: rid,
|
||||
dmsId: dmsRoNo,
|
||||
dmsCustomerId: effectiveCustNo,
|
||||
dmsAdvisorId: String(advisorNo),
|
||||
mileageIn
|
||||
});
|
||||
} else {
|
||||
CreateRRLogEvent(socket, "WARN", "RR early RO creation succeeded but no DMS RO number was returned", {
|
||||
jobId: rid,
|
||||
resultPreview: {
|
||||
roNo: result?.roNo,
|
||||
data: {
|
||||
dmsRoNo: data?.dmsRoNo,
|
||||
outsdRoNo: data?.outsdRoNo
|
||||
CreateRRLogEvent(
|
||||
socket,
|
||||
"WARN",
|
||||
"RR early RO creation succeeded but no DMS RO number was returned",
|
||||
withRRRequestXml(result, {
|
||||
jobId: rid,
|
||||
resultPreview: {
|
||||
roNo: result?.roNo,
|
||||
data: {
|
||||
dmsRoNo: data?.dmsRoNo,
|
||||
outsdRoNo: data?.outsdRoNo
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
@@ -758,10 +810,15 @@ const registerRREvents = ({ socket, redisHelpers }) => {
|
||||
defaultRRTTL
|
||||
);
|
||||
|
||||
CreateRRLogEvent(socket, "INFO", `{EARLY-5} Minimal RO created successfully`, {
|
||||
dmsRoNo: dmsRoNo || null,
|
||||
outsdRoNo: outsdRoNo || null
|
||||
});
|
||||
CreateRRLogEvent(
|
||||
socket,
|
||||
"INFO",
|
||||
`{EARLY-5} Minimal RO created successfully`,
|
||||
withRRRequestXml(result, {
|
||||
dmsRoNo: dmsRoNo || null,
|
||||
outsdRoNo: outsdRoNo || null
|
||||
})
|
||||
);
|
||||
|
||||
// Mark success in export logs
|
||||
await markRRExportSuccess({
|
||||
@@ -810,11 +867,16 @@ const registerRREvents = ({ socket, redisHelpers }) => {
|
||||
message: vendorMessage
|
||||
});
|
||||
|
||||
CreateRRLogEvent(socket, "ERROR", `Early RO creation failed`, {
|
||||
roStatus: result?.roStatus,
|
||||
statusBlocks: result?.statusBlocks,
|
||||
classification: cls
|
||||
});
|
||||
CreateRRLogEvent(
|
||||
socket,
|
||||
"ERROR",
|
||||
`Early RO creation failed`,
|
||||
withRRRequestXml(result, {
|
||||
roStatus: result?.roStatus,
|
||||
statusBlocks: result?.statusBlocks,
|
||||
classification: cls
|
||||
})
|
||||
);
|
||||
|
||||
await insertRRFailedExportLog({
|
||||
socket,
|
||||
@@ -940,14 +1002,14 @@ const registerRREvents = ({ socket, redisHelpers }) => {
|
||||
|
||||
// Check if this job already has an early RO - if so, use stored IDs and skip customer search
|
||||
const hasEarlyRO = !!job?.dms_id;
|
||||
|
||||
|
||||
if (hasEarlyRO) {
|
||||
CreateRRLogEvent(socket, "DEBUG", `{2} Early RO exists - using stored customer/advisor`, {
|
||||
dms_id: job.dms_id,
|
||||
dms_customer_id: job.dms_customer_id,
|
||||
dms_advisor_id: job.dms_advisor_id
|
||||
});
|
||||
|
||||
|
||||
// Cache the stored customer/advisor IDs for the next step
|
||||
if (job.dms_customer_id) {
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
@@ -967,18 +1029,18 @@ const registerRREvents = ({ socket, redisHelpers }) => {
|
||||
defaultRRTTL
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Emit empty customer list to frontend (won't show modal)
|
||||
socket.emit("rr-select-customer", []);
|
||||
|
||||
|
||||
// Continue directly with the export by calling the selected customer handler logic inline
|
||||
// This is essentially the same as if user selected the stored customer
|
||||
const selectedCustNo = job.dms_customer_id;
|
||||
|
||||
|
||||
if (!selectedCustNo) {
|
||||
throw new Error("Early RO exists but no customer ID stored");
|
||||
}
|
||||
|
||||
|
||||
// Continue with ensureRRServiceVehicle and export (same as rr-selected-customer handler)
|
||||
const { client, opts } = await buildClientAndOpts(bodyshop);
|
||||
const routing = opts?.routing || client?.opts?.routing || null;
|
||||
@@ -1011,7 +1073,12 @@ const registerRREvents = ({ socket, redisHelpers }) => {
|
||||
redisHelpers
|
||||
});
|
||||
|
||||
const advisorNo = job.dms_advisor_id || readAdvisorNo({ txEnvelope }, await redisHelpers.getSessionTransactionData(socket.id, getTransactionType(rid), RRCacheEnums.AdvisorNo));
|
||||
const advisorNo =
|
||||
job.dms_advisor_id ||
|
||||
readAdvisorNo(
|
||||
{ txEnvelope },
|
||||
await redisHelpers.getSessionTransactionData(socket.id, getTransactionType(rid), RRCacheEnums.AdvisorNo)
|
||||
);
|
||||
|
||||
if (!advisorNo) {
|
||||
throw new Error("Advisor is required (advisorNo).");
|
||||
@@ -1059,15 +1126,20 @@ const registerRREvents = ({ socket, redisHelpers }) => {
|
||||
defaultRRTTL
|
||||
);
|
||||
|
||||
CreateRRLogEvent(socket, "INFO", `RR Repair Order updated successfully`, {
|
||||
dmsRoNo,
|
||||
jobId: rid
|
||||
});
|
||||
CreateRRLogEvent(
|
||||
socket,
|
||||
"INFO",
|
||||
`RR Repair Order updated successfully`,
|
||||
withRRRequestXml(result, {
|
||||
dmsRoNo,
|
||||
jobId: rid
|
||||
})
|
||||
);
|
||||
|
||||
// For early RO flow, only emit validation-required (not export-job:result)
|
||||
// since the export is not complete yet - we're just waiting for validation
|
||||
socket.emit("rr-validation-required", { dmsRoNo, jobId: rid });
|
||||
|
||||
|
||||
return ack?.({ ok: true, skipCustomerSelection: true, dmsRoNo });
|
||||
}
|
||||
|
||||
@@ -1277,25 +1349,25 @@ const registerRREvents = ({ socket, redisHelpers }) => {
|
||||
// When updating an early RO, use stored customer/advisor IDs
|
||||
let finalEffectiveCustNo = effectiveCustNo;
|
||||
let finalAdvisorNo = advisorNo;
|
||||
|
||||
|
||||
if (shouldUpdate && job?.dms_customer_id) {
|
||||
CreateRRLogEvent(socket, "DEBUG", `Using stored customer ID from early RO`, {
|
||||
CreateRRLogEvent(socket, "DEBUG", `Using stored customer ID from early RO`, {
|
||||
storedCustomerId: job.dms_customer_id,
|
||||
originalCustomerId: effectiveCustNo
|
||||
originalCustomerId: effectiveCustNo
|
||||
});
|
||||
finalEffectiveCustNo = String(job.dms_customer_id);
|
||||
}
|
||||
|
||||
|
||||
if (shouldUpdate && job?.dms_advisor_id) {
|
||||
CreateRRLogEvent(socket, "DEBUG", `Using stored advisor ID from early RO`, {
|
||||
CreateRRLogEvent(socket, "DEBUG", `Using stored advisor ID from early RO`, {
|
||||
storedAdvisorId: job.dms_advisor_id,
|
||||
originalAdvisorId: advisorNo
|
||||
originalAdvisorId: advisorNo
|
||||
});
|
||||
finalAdvisorNo = String(job.dms_advisor_id);
|
||||
}
|
||||
|
||||
let result;
|
||||
|
||||
|
||||
if (shouldUpdate) {
|
||||
// UPDATE existing RO with full data
|
||||
CreateRRLogEvent(socket, "DEBUG", `{4} Updating existing RR RO with full data`, { dmsRoNo: existingDmsId });
|
||||
@@ -1344,16 +1416,21 @@ const registerRREvents = ({ socket, redisHelpers }) => {
|
||||
if (dmsRoNo) {
|
||||
await setJobDmsIdForSocket({ socket, jobId: rid, dmsId: dmsRoNo });
|
||||
} else {
|
||||
CreateRRLogEvent(socket, "WARN", "RR export succeeded but no DMS RO number was returned", {
|
||||
jobId: rid,
|
||||
resultPreview: {
|
||||
roNo: result?.roNo,
|
||||
data: {
|
||||
dmsRoNo: data?.dmsRoNo,
|
||||
outsdRoNo: data?.outsdRoNo
|
||||
CreateRRLogEvent(
|
||||
socket,
|
||||
"WARN",
|
||||
"RR export succeeded but no DMS RO number was returned",
|
||||
withRRRequestXml(result, {
|
||||
jobId: rid,
|
||||
resultPreview: {
|
||||
roNo: result?.roNo,
|
||||
data: {
|
||||
dmsRoNo: data?.dmsRoNo,
|
||||
outsdRoNo: data?.outsdRoNo
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
@@ -1370,10 +1447,15 @@ const registerRREvents = ({ socket, redisHelpers }) => {
|
||||
defaultRRTTL
|
||||
);
|
||||
|
||||
CreateRRLogEvent(socket, "INFO", `{5} RO created. Waiting for validation.`, {
|
||||
dmsRoNo: dmsRoNo || null,
|
||||
outsdRoNo: outsdRoNo || null
|
||||
});
|
||||
CreateRRLogEvent(
|
||||
socket,
|
||||
"INFO",
|
||||
`{5} RO created. Waiting for validation.`,
|
||||
withRRRequestXml(result, {
|
||||
dmsRoNo: dmsRoNo || null,
|
||||
outsdRoNo: outsdRoNo || null
|
||||
})
|
||||
);
|
||||
|
||||
// Tell FE to prompt for "Finished/Close"
|
||||
socket.emit("rr-validation-required", { jobId: rid, dmsRoNo, outsdRoNo });
|
||||
@@ -1412,11 +1494,16 @@ const registerRREvents = ({ socket, redisHelpers }) => {
|
||||
message: vendorMessage
|
||||
});
|
||||
|
||||
CreateRRLogEvent(socket, "ERROR", `Export failed (step 1)`, {
|
||||
roStatus: result?.roStatus,
|
||||
statusBlocks: result?.statusBlocks,
|
||||
classification: cls
|
||||
});
|
||||
CreateRRLogEvent(
|
||||
socket,
|
||||
"ERROR",
|
||||
`Export failed (step 1)`,
|
||||
withRRRequestXml(result, {
|
||||
roStatus: result?.roStatus,
|
||||
statusBlocks: result?.statusBlocks,
|
||||
classification: cls
|
||||
})
|
||||
);
|
||||
|
||||
await insertRRFailedExportLog({
|
||||
socket,
|
||||
@@ -1541,7 +1628,12 @@ const registerRREvents = ({ socket, redisHelpers }) => {
|
||||
});
|
||||
|
||||
if (finalizeResult?.success) {
|
||||
CreateRRLogEvent(socket, "INFO", `{7} Finalize success; marking exported`, { dmsRoNo, outsdRoNo });
|
||||
CreateRRLogEvent(
|
||||
socket,
|
||||
"INFO",
|
||||
`{7} Finalize success; marking exported`,
|
||||
withRRRequestXml(finalizeResult, { dmsRoNo, outsdRoNo })
|
||||
);
|
||||
|
||||
// ✅ Mark exported + success log
|
||||
await markRRExportSuccess({
|
||||
@@ -1584,6 +1676,17 @@ const registerRREvents = ({ socket, redisHelpers }) => {
|
||||
message: vendorMessage
|
||||
});
|
||||
|
||||
CreateRRLogEvent(
|
||||
socket,
|
||||
"ERROR",
|
||||
"Finalize failed",
|
||||
withRRRequestXml(finalizeResult, {
|
||||
roStatus: finalizeResult?.roStatus,
|
||||
statusBlocks: finalizeResult?.statusBlocks,
|
||||
classification: cls
|
||||
})
|
||||
);
|
||||
|
||||
await insertRRFailedExportLog({
|
||||
socket,
|
||||
jobId: rid,
|
||||
|
||||
@@ -68,33 +68,12 @@ const fetchBodyshopFromDB = async (bodyshopId, logger) => {
|
||||
* @param logger
|
||||
*/
|
||||
const applyRedisHelpers = ({ pubClient, app, logger }) => {
|
||||
const toRedisJson = (value) => JSON.stringify(value === undefined ? null : value);
|
||||
|
||||
// Store session data in Redis
|
||||
const setSessionData = async (socketId, key, value, ttl) => {
|
||||
try {
|
||||
const sessionKey = `socket:${socketId}`;
|
||||
|
||||
// Supports both forms:
|
||||
// 1) setSessionData(socketId, "field", value, ttl)
|
||||
// 2) setSessionData(socketId, { fieldA: valueA, fieldB: valueB }, ttl)
|
||||
if (key && typeof key === "object" && !Array.isArray(key)) {
|
||||
const entries = Object.entries(key).flatMap(([field, fieldValue]) => [field, toRedisJson(fieldValue)]);
|
||||
|
||||
if (entries.length > 0) {
|
||||
await pubClient.hset(sessionKey, ...entries);
|
||||
}
|
||||
|
||||
const objectTtl = typeof value === "number" ? value : typeof ttl === "number" ? ttl : null;
|
||||
if (objectTtl) {
|
||||
await pubClient.expire(sessionKey, objectTtl);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await pubClient.hset(sessionKey, key, toRedisJson(value)); // Use Redis pubClient
|
||||
await pubClient.hset(`socket:${socketId}`, key, JSON.stringify(value)); // Use Redis pubClient
|
||||
if (ttl && typeof ttl === "number") {
|
||||
await pubClient.expire(sessionKey, ttl);
|
||||
await pubClient.expire(`socket:${socketId}`, ttl);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.log(`Error Setting Session Data for socket ${socketId}: ${error}`, "ERROR", "redis");
|
||||
@@ -109,26 +88,7 @@ const applyRedisHelpers = ({ pubClient, app, logger }) => {
|
||||
*/
|
||||
const getSessionData = async (socketId, key) => {
|
||||
try {
|
||||
const sessionKey = `socket:${socketId}`;
|
||||
|
||||
// Supports:
|
||||
// 1) getSessionData(socketId, "field") -> parsed field value
|
||||
// 2) getSessionData(socketId) -> parsed object of all fields
|
||||
if (typeof key === "undefined") {
|
||||
const raw = await pubClient.hgetall(sessionKey);
|
||||
if (!raw || Object.keys(raw).length === 0) return null;
|
||||
|
||||
return Object.entries(raw).reduce((acc, [field, rawValue]) => {
|
||||
try {
|
||||
acc[field] = JSON.parse(rawValue);
|
||||
} catch {
|
||||
acc[field] = rawValue;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
const data = await pubClient.hget(sessionKey, key);
|
||||
const data = await pubClient.hget(`socket:${socketId}`, key);
|
||||
return data ? JSON.parse(data) : null;
|
||||
} catch (error) {
|
||||
logger.log(`Error Getting Session Data for socket ${socketId}: ${error}`, "ERROR", "redis");
|
||||
@@ -146,7 +106,7 @@ const applyRedisHelpers = ({ pubClient, app, logger }) => {
|
||||
*/
|
||||
const setSessionTransactionData = async (socketId, transactionType, key, value, ttl) => {
|
||||
try {
|
||||
await pubClient.hset(getSocketTransactionkey({ socketId, transactionType }), key, toRedisJson(value)); // Use Redis pubClient
|
||||
await pubClient.hset(getSocketTransactionkey({ socketId, transactionType }), key, JSON.stringify(value)); // Use Redis pubClient
|
||||
if (ttl && typeof ttl === "number") {
|
||||
await pubClient.expire(getSocketTransactionkey({ socketId, transactionType }), ttl);
|
||||
}
|
||||
@@ -200,17 +160,7 @@ const applyRedisHelpers = ({ pubClient, app, logger }) => {
|
||||
*/
|
||||
const clearSessionTransactionData = async (socketId, transactionType) => {
|
||||
try {
|
||||
if (transactionType) {
|
||||
await pubClient.del(getSocketTransactionkey({ socketId, transactionType }));
|
||||
return;
|
||||
}
|
||||
|
||||
// If no transactionType is provided, clear all transaction namespaces for this socket.
|
||||
const pattern = getSocketTransactionkey({ socketId, transactionType: "*" });
|
||||
const keys = await pubClient.keys(pattern);
|
||||
if (Array.isArray(keys) && keys.length > 0) {
|
||||
await pubClient.del(...keys);
|
||||
}
|
||||
await pubClient.del(getSocketTransactionkey({ socketId, transactionType }));
|
||||
} catch (error) {
|
||||
logger.log(
|
||||
`Error Clearing Session Transaction Data for socket ${socketId}:${transactionType}: ${error}`,
|
||||
|
||||
@@ -4,14 +4,11 @@ const { FortellisJobExport, FortellisSelectedCustomer } = require("../fortellis/
|
||||
const CdkCalculateAllocations = require("../cdk/cdk-calculate-allocations").default;
|
||||
const registerRREvents = require("../rr/rr-register-socket-events");
|
||||
|
||||
const SOCKET_SESSION_TTL_SECONDS = 60 * 60 * 24;
|
||||
|
||||
const redisSocketEvents = ({ io, redisHelpers, ioHelpers, logger }) => {
|
||||
// Destructure helpers locally, but keep full objects available for downstream modules
|
||||
const {
|
||||
setSessionData,
|
||||
getSessionData,
|
||||
clearSessionData,
|
||||
addUserSocketMapping,
|
||||
removeUserSocketMapping,
|
||||
refreshUserSocketTTL,
|
||||
@@ -54,16 +51,12 @@ const redisSocketEvents = ({ io, redisHelpers, ioHelpers, logger }) => {
|
||||
}
|
||||
|
||||
// NEW: seed a base session for this socket so downstream handlers can read it
|
||||
await setSessionData(
|
||||
socket.id,
|
||||
{
|
||||
bodyshopId,
|
||||
email: user.email,
|
||||
uid: user.user_id || user.uid,
|
||||
seededAt: Date.now()
|
||||
},
|
||||
SOCKET_SESSION_TTL_SECONDS
|
||||
);
|
||||
await setSessionData(socket.id, {
|
||||
bodyshopId,
|
||||
email: user.email,
|
||||
uid: user.user_id || user.uid,
|
||||
seededAt: Date.now()
|
||||
});
|
||||
|
||||
await addUserSocketMapping(user.email, socket.id, bodyshopId);
|
||||
next();
|
||||
@@ -133,18 +126,14 @@ const redisSocketEvents = ({ io, redisHelpers, ioHelpers, logger }) => {
|
||||
}
|
||||
|
||||
// NEW: refresh (or create) the base session with the latest info
|
||||
await setSessionData(
|
||||
socket.id,
|
||||
{
|
||||
bodyshopId,
|
||||
email: user.email,
|
||||
uid: user.user_id || user.uid,
|
||||
refreshedAt: Date.now()
|
||||
},
|
||||
SOCKET_SESSION_TTL_SECONDS
|
||||
);
|
||||
await setSessionData(socket.id, {
|
||||
bodyshopId,
|
||||
email: user.email,
|
||||
uid: user.user_id || user.uid,
|
||||
refreshedAt: Date.now()
|
||||
});
|
||||
|
||||
await refreshUserSocketTTL(user.email);
|
||||
await refreshUserSocketTTL(user.email, bodyshopId);
|
||||
socket.emit("token-updated", { success: true });
|
||||
} catch (error) {
|
||||
if (error.code === "auth/id-token-expired") {
|
||||
@@ -200,11 +189,6 @@ const redisSocketEvents = ({ io, redisHelpers, ioHelpers, logger }) => {
|
||||
if (socket.user?.email) {
|
||||
await removeUserSocketMapping(socket.user.email, socket.id);
|
||||
}
|
||||
try {
|
||||
await clearSessionData(socket.id);
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
// Optional: clear transactional session
|
||||
try {
|
||||
await clearSessionTransactionData(socket.id);
|
||||
|
||||
Reference in New Issue
Block a user