From b8b8684344647675675a77aa7c8c1bbc10904f30 Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Sat, 7 Nov 2026 18:52:24 +0000 Subject: [PATCH] Update API Configuration and Introduce String ID Utilities - Changed the backend URL in the development configuration for the API. - Integrated a new utility function `bindStringIds` to enhance socket communication by automatically stringifying IDs. - Added a new file `stringIds.js` containing functions for handling and converting IDs, improving data consistency across the application. --- .../Dashboard/context/ApiServerContext.jsx | 17 ++-- src/config.js | 2 +- src/utils/stringIds.js | 99 +++++++++++++++++++ 3 files changed, 110 insertions(+), 8 deletions(-) create mode 100644 src/utils/stringIds.js diff --git a/src/components/Dashboard/context/ApiServerContext.jsx b/src/components/Dashboard/context/ApiServerContext.jsx index c6da708b..830f94bb 100644 --- a/src/components/Dashboard/context/ApiServerContext.jsx +++ b/src/components/Dashboard/context/ApiServerContext.jsx @@ -9,6 +9,7 @@ import { useMemo } from 'react' import io from 'socket.io-client' +import { bindStringIds } from '../../../utils/stringIds' import { message, Modal, Space, Button, Typography, Flex } from 'antd' import PropTypes from 'prop-types' import { AuthContext } from './AuthContext' @@ -683,13 +684,15 @@ const ApiServerProvider = ({ children }) => { logger.debug('Token is available, connecting to api server...') - const newSocket = io(config.apiServerUrl, { - reconnection: false, - reconnectionAttempts: 0, - forceNew: true, - timeout: 3000, - auth: { type: 'user' } - }) + const newSocket = bindStringIds( + io(config.apiServerUrl, { + reconnection: false, + reconnectionAttempts: 0, + forceNew: true, + timeout: 3000, + auth: { type: 'user' } + }) + ) if (typeof newSocket.io?.reconnection === 'function') { newSocket.io.reconnection(false) } diff --git a/src/config.js b/src/config.js index 8de831bd..0bfd03bd 100644 --- a/src/config.js +++ b/src/config.js @@ -1,6 +1,6 @@ const config = { development: { - backendUrl: 'https://dev.tombutcher.work/api', + backendUrl: 'https://dev.tombutcher.work/fcapi', webAppUrl: 'https://dev.tombutcher.work', printServerUrl: 'ws://192.168.68.53:8081', apiServerUrl: 'https://dev-wss.tombutcher.work', diff --git a/src/utils/stringIds.js b/src/utils/stringIds.js new file mode 100644 index 00000000..7d86844c --- /dev/null +++ b/src/utils/stringIds.js @@ -0,0 +1,99 @@ +function bytesOf(value) { + if (value == null) return null + if (value instanceof Uint8Array) return value.byteLength === 12 ? value : null + if (typeof value !== 'object' || Array.isArray(value) || value instanceof Date) { + return null + } + + const keys = Object.keys(value) + if (keys.length !== 12) return null + const bytes = new Uint8Array(12) + for (let index = 0; index < 12; index += 1) { + if (!Object.prototype.hasOwnProperty.call(value, String(index)) && + !Object.prototype.hasOwnProperty.call(value, index)) { + return null + } + const byte = Number(value[index]) + if (!Number.isInteger(byte) || byte < 0 || byte > 255) return null + bytes[index] = byte + } + return bytes +} + +function toHex(bytes) { + return Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('') +} + +const PURE_REF_KEYS = new Set(['_id', 'id', 'buffer', '$oid']) + +export function refId(value) { + if (value == null || value === '') return null + if (typeof value === 'string') return value + if (typeof value === 'object' && typeof value.toHexString === 'function') { + return value.toHexString() + } + if (typeof value !== 'object' || Array.isArray(value) || value instanceof Date) { + return null + } + if (typeof value.$oid === 'string') return refId(value.$oid) + + const keys = Object.keys(value) + if ( + keys.length > 0 && + keys.every((key) => key === 'buffer' || key === 'id' || key === 'type' || key === 'data') + ) { + const bytes = bytesOf(value.buffer) || bytesOf(value.id) || bytesOf(value) + if (bytes) return toHex(bytes) + } + + if (value._id != null) return refId(value._id) + return null +} + +function isPureRef(value) { + if (value == null || typeof value !== 'object') return false + if (typeof value.toHexString === 'function') return true + if (Array.isArray(value) || value instanceof Date) return false + const keys = Object.keys(value) + return keys.length > 0 && keys.every((key) => PURE_REF_KEYS.has(key)) && refId(value) != null +} + +function isBinaryPayload(value) { + if (value == null || typeof value !== 'object') return false + if (value instanceof Uint8Array || value instanceof ArrayBuffer) return true + const name = value.constructor?.name + return name === 'Blob' || name === 'File' +} + +export function stringifyIds(value) { + if (value == null) return value + if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { + return value + } + if (typeof value.toHexString === 'function') return value.toHexString() + if (value instanceof Date || typeof value === 'function') return value + if (isBinaryPayload(value)) return value + if (Array.isArray(value)) return value.map((item) => stringifyIds(item)) + if (typeof value === 'object') { + if (isPureRef(value)) { + const id = refId(value) + if (typeof id === 'string') return id + } + const copy = {} + for (const [key, child] of Object.entries(value)) { + copy[key] = stringifyIds(child) + } + return copy + } + return value +} + +export function bindStringIds(socket) { + const emit = socket.emit.bind(socket) + socket.emit = (event, ...args) => + emit( + event, + ...args.map((arg) => (typeof arg === 'function' ? arg : stringifyIds(arg))) + ) + return socket +}