From e25174ff97a5230729eb25a5452bafa1c57b0cd4 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Fri, 27 Feb 2026 13:15:10 -0800 Subject: [PATCH] IO-2433 Basic embedded authoring. --- client/package-lock.json | 11 + client/package.json | 1 + client/src/App/App.styles.scss | 6 + .../esignature-modal.container.jsx | 70 ++ .../print-center-item.component.jsx | 49 +- .../pages/manage/manage.page.component.jsx | 10 +- client/src/redux/modals/modals.reducer.js | 3 +- client/src/redux/modals/modals.selectors.js | 1 + package-lock.json | 730 ++++++++++++++++++ package.json | 2 + server.js | 1 + server/esign/esign-new.js | 236 ++++++ server/esign/webhook.js | 150 ++++ server/routes/esignRoutes.js | 16 + server/utils/utils.js | 5 +- 15 files changed, 1284 insertions(+), 7 deletions(-) create mode 100644 client/src/components/esignature-modal/esignature-modal.container.jsx create mode 100644 server/esign/esign-new.js create mode 100644 server/esign/webhook.js create mode 100644 server/routes/esignRoutes.js diff --git a/client/package-lock.json b/client/package-lock.json index 225d2ba06..007edc5ac 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -16,6 +16,7 @@ "@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", + "@documenso/embed-react": "^0.5.1", "@emotion/is-prop-valid": "^1.4.0", "@fingerprintjs/fingerprintjs": "^5.0.1", "@firebase/analytics": "^0.10.19", @@ -2564,6 +2565,16 @@ "react": ">=16.8.0" } }, + "node_modules/@documenso/embed-react": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@documenso/embed-react/-/embed-react-0.5.1.tgz", + "integrity": "sha512-PlkZ3vrdZVBTc0J3xfG2wtPVGmxCxWgpQ/SsdR2oBMdTwsR+rDbj9k+CeTv+M9Xi5tKbLr5Y78bS9Sb8K+ltTQ==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, "node_modules/@dotenvx/dotenvx": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.52.0.tgz", diff --git a/client/package.json b/client/package.json index b0cbdf6b8..95ac7a96a 100644 --- a/client/package.json +++ b/client/package.json @@ -15,6 +15,7 @@ "@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", + "@documenso/embed-react": "^0.5.1", "@emotion/is-prop-valid": "^1.4.0", "@fingerprintjs/fingerprintjs": "^5.0.1", "@firebase/analytics": "^0.10.19", diff --git a/client/src/App/App.styles.scss b/client/src/App/App.styles.scss index 657520e49..1701289af 100644 --- a/client/src/App/App.styles.scss +++ b/client/src/App/App.styles.scss @@ -475,3 +475,9 @@ margin-left: auto; flex: 0 0 auto; } + + +.esignature-embed { +width: 100%; +height: 100%; +} \ No newline at end of file diff --git a/client/src/components/esignature-modal/esignature-modal.container.jsx b/client/src/components/esignature-modal/esignature-modal.container.jsx new file mode 100644 index 000000000..847efa1e2 --- /dev/null +++ b/client/src/components/esignature-modal/esignature-modal.container.jsx @@ -0,0 +1,70 @@ +import { Button, Modal } from "antd"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { toggleModalVisible } from "../../redux/modals/modals.actions"; +import { selectEsignature } from "../../redux/modals/modals.selectors"; +import { EmbedUpdateDocumentV1 } from "@documenso/embed-react"; +import axios from "axios"; + +const mapStateToProps = createStructuredSelector({ + esignatureModal: selectEsignature +}); + +const mapDispatchToProps = (dispatch) => ({ + toggleModalVisible: () => dispatch(toggleModalVisible("esignature")) +}); + +export function EsignatureModalContainer({ esignatureModal, toggleModalVisible }) { + const { t } = useTranslation(); + const { open, context } = esignatureModal; + const { token, envelopeId, documentId } = context; + + return ( + { + toggleModalVisible(); + }} + onCancel={() => { + toggleModalVisible(); + }} + cancelButtonProps={{ style: { display: "none" } }} + width="90%" + destroyOnHidden + > +
+ {token ? ( + { + console.log("Document updated:", data.documentId); + }} + /> + ) : ( +
No token...
+ )} + +
+
+ ); +} + +export default connect(mapStateToProps, mapDispatchToProps)(EsignatureModalContainer); diff --git a/client/src/components/print-center-item/print-center-item.component.jsx b/client/src/components/print-center-item/print-center-item.component.jsx index 7af2b15f1..e325e1daf 100644 --- a/client/src/components/print-center-item/print-center-item.component.jsx +++ b/client/src/components/print-center-item/print-center-item.component.jsx @@ -1,4 +1,4 @@ -import { MailOutlined, PrinterOutlined } from "@ant-design/icons"; +import { MailOutlined, PrinterOutlined, SignatureFilled } from "@ant-design/icons"; import { Space, Spin } from "antd"; import { useState } from "react"; import { connect } from "react-redux"; @@ -10,6 +10,8 @@ import { GenerateDocument } from "../../utils/RenderTemplate"; import LockWrapperComponent from "../lock-wrapper/lock-wrapper.component"; import { HasFeatureAccess } from "./../feature-wrapper/feature-wrapper.component"; import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; +import axios from "axios"; +import { setModalContext } from "../../redux/modals/modals.actions.js"; const mapStateToProps = createStructuredSelector({ printCenterModal: selectPrintCenter, @@ -17,9 +19,25 @@ const mapStateToProps = createStructuredSelector({ technician: selectTechnician }); -const mapDispatchToProps = () => ({}); +const mapDispatchToProps = (dispatch) => ({ + setEsignatureContext: (context) => + dispatch( + setModalContext({ + context: context, + modal: "esignature" + }) + ) +}); -export function PrintCenterItemComponent({ printCenterModal, item, id, bodyshop, disabled, technician }) { +export function PrintCenterItemComponent({ + printCenterModal, + setEsignatureContext, + item, + id, + bodyshop, + disabled, + technician +}) { const [loading, setLoading] = useState(false); const { context } = printCenterModal; const notification = useNotification(); @@ -39,6 +57,30 @@ export function PrintCenterItemComponent({ printCenterModal, item, id, bodyshop, setLoading(false); }; + const esignatureGenerate = async () => { + setLoading(true); + try { + const { + data: { token, documentId, evnelopeId } + } = await axios.post("/esign/new", { + name: item.key, + variables: { id: id }, + context, + bodyshop, + templateObject: { + name: item.key, + variables: { id: id } + } + }); + + setEsignatureContext({ context: { token, documentId, evnelopeId }, jobid: id }); + } catch (error) { + console.log(error); + } finally { + setLoading(false); + } + }; + if ( disabled || (item.featureNameRestricted && !HasFeatureAccess({ featureName: item.featureNameRestricted, bodyshop })) @@ -54,6 +96,7 @@ export function PrintCenterItemComponent({ printCenterModal, item, id, bodyshop,
  • {item.title} + {!technician ? ( import("../../components/print-center-modal/print-center-modal.container") @@ -68,7 +69,9 @@ const FeatureRequestPage = lazyDev(() => import("../feature-request/feature-requ const JobCostingModal = lazyDev(() => import("../../components/job-costing-modal/job-costing-modal.container")); const ReportCenterModal = lazyDev(() => import("../../components/report-center-modal/report-center-modal.container")); const BillEnterModalContainer = lazyDev(() => import("../../components/bill-enter-modal/bill-enter-modal.container")); -const TimeTicketModalContainer = lazyDev(() => import("../../components/time-ticket-modal/time-ticket-modal.container")); +const TimeTicketModalContainer = lazyDev( + () => import("../../components/time-ticket-modal/time-ticket-modal.container") +); const TimeTicketModalTask = lazyDev( () => import("../../components/time-ticket-task-modal/time-ticket-task-modal.container") ); @@ -110,7 +113,9 @@ const TtApprovals = lazyDev(() => import("../tt-approvals/tt-approvals.page.cont const MyTasksPage = lazyDev(() => import("../tasks/myTasksPageContainer.jsx")); const AllTasksPage = lazyDev(() => import("../tasks/allTasksPageContainer.jsx")); -const TaskUpsertModalContainer = lazyDev(() => import("../../components/task-upsert-modal/task-upsert-modal.container")); +const TaskUpsertModalContainer = lazyDev( + () => import("../../components/task-upsert-modal/task-upsert-modal.container") +); const { Content } = Layout; const mapStateToProps = createStructuredSelector({ @@ -178,6 +183,7 @@ export function Manage({ conflict, bodyshop, partsManagementOnly, isDarkMode, cu + diff --git a/client/src/redux/modals/modals.reducer.js b/client/src/redux/modals/modals.reducer.js index 0035ba17b..b7e67823f 100644 --- a/client/src/redux/modals/modals.reducer.js +++ b/client/src/redux/modals/modals.reducer.js @@ -27,7 +27,8 @@ const INITIAL_STATE = { contractFinder: { ...baseModal }, inventoryUpsert: { ...baseModal }, ca_bc_eftTableConvert: { ...baseModal }, - cardPayment: { ...baseModal } + cardPayment: { ...baseModal }, + esignature: { ...baseModal } }; const modalsReducer = (state = INITIAL_STATE, action) => { diff --git a/client/src/redux/modals/modals.selectors.js b/client/src/redux/modals/modals.selectors.js index cd836a3d1..ad796f8da 100644 --- a/client/src/redux/modals/modals.selectors.js +++ b/client/src/redux/modals/modals.selectors.js @@ -36,3 +36,4 @@ export const selectInventoryUpsert = createSelector([selectModals], (modals) => export const selectCaBcEtfTableConvert = createSelector([selectModals], (modals) => modals.ca_bc_eftTableConvert); export const selectCardPayment = createSelector([selectModals], (modals) => modals.cardPayment); +export const selectEsignature = createSelector([selectModals], (modals) => modals.esignature); diff --git a/package-lock.json b/package-lock.json index a72ac3df6..0b3e1381f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,8 @@ "@aws-sdk/credential-provider-node": "^3.972.3", "@aws-sdk/lib-storage": "^3.978.0", "@aws-sdk/s3-request-presigner": "^3.978.0", + "@documenso/sdk-typescript": "^0.8.0", + "@jsreport/nodejs-client": "^4.1.0", "@opensearch-project/opensearch": "^2.13.0", "@socket.io/admin-ui": "^0.5.1", "@socket.io/redis-adapter": "^8.3.0", @@ -1400,6 +1402,18 @@ "kuler": "^2.0.0" } }, + "node_modules/@documenso/sdk-typescript": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@documenso/sdk-typescript/-/sdk-typescript-0.8.0.tgz", + "integrity": "sha512-Emzd5j+v8tA8gxtL+M/svVuzSOKMZw3/U4bS8zRoagvQEqkt+XNU2JraPEAJzxTjf3ww6EnlURXydbglBmR7AQ==", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.26.0", + "zod": "^3.25.0 || ^4.0.0" + }, + "bin": { + "mcp": "bin/mcp-server.js" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", @@ -2268,6 +2282,18 @@ "node": ">=6" } }, + "node_modules/@hono/node-server": { + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", + "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -2396,12 +2422,364 @@ "url": "https://opencollective.com/js-sdsl" } }, + "node_modules/@jsreport/nodejs-client": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@jsreport/nodejs-client/-/nodejs-client-4.1.0.tgz", + "integrity": "sha512-QWupUQzMzxWFvY+AlSdUZGlinJv4cKhYmVE9rIe+he7rn4B24tezFmNdnrDcTSFv3hj4x7sTNqpeHT0fItfs5Q==", + "dependencies": { + "axios": "1.13.2", + "concat-stream": "2.0.0", + "mimic-response": "2.1.0" + }, + "engines": { + "node": ">=22.18" + } + }, + "node_modules/@jsreport/nodejs-client/node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/@kurkle/color": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", "license": "MIT" }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.27.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", + "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", @@ -4270,6 +4648,45 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -4728,6 +5145,100 @@ "node": "*" } }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/body-parser/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -6441,6 +6952,27 @@ "node": ">=0.8.x" } }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/expect-type": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", @@ -6497,6 +7029,24 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express-rate-limit": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", + "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", + "license": "MIT", + "dependencies": { + "ip-address": "10.0.1" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, "node_modules/express/node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -6619,6 +7169,22 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fast-xml-parser": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.4.tgz", @@ -7374,6 +7940,15 @@ "node": ">= 0.4" } }, + "node_modules/hono": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.3.tgz", + "integrity": "sha512-SFsVSjp8sj5UumXOOFlkZOG6XS9SJDKw0TbwFeV+AJ8xlST8kxK5Z/5EYa111UY8732lK2S/xB653ceuaoGwpg==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/hpagent": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.2.0.tgz", @@ -7632,6 +8207,15 @@ "url": "https://opencollective.com/ioredis" } }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -7873,6 +8457,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -8146,6 +8736,12 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -8645,6 +9241,18 @@ "node": ">= 0.6" } }, + "node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -9358,6 +9966,15 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -9590,6 +10207,66 @@ "node": ">= 0.6" } }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -9755,6 +10432,15 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "2.0.0-next.5", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", @@ -9926,6 +10612,32 @@ "fsevents": "~2.3.2" } }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/rsa-pem-from-mod-exp": { "version": "0.8.6", "resolved": "https://registry.npmjs.org/rsa-pem-from-mod-exp/-/rsa-pem-from-mod-exp-0.8.6.tgz", @@ -12196,6 +12908,24 @@ "engines": { "node": ">= 14" } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } } } } diff --git a/package.json b/package.json index 8f7c36816..ff7943be7 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,8 @@ "@aws-sdk/credential-provider-node": "^3.972.3", "@aws-sdk/lib-storage": "^3.978.0", "@aws-sdk/s3-request-presigner": "^3.978.0", + "@documenso/sdk-typescript": "^0.8.0", + "@jsreport/nodejs-client": "^4.1.0", "@opensearch-project/opensearch": "^2.13.0", "@socket.io/admin-ui": "^0.5.1", "@socket.io/redis-adapter": "^8.3.0", diff --git a/server.js b/server.js index 33cbe014c..b73100863 100644 --- a/server.js +++ b/server.js @@ -128,6 +128,7 @@ const applyRoutes = ({ app }) => { app.use("/sso", require("./server/routes/ssoRoutes")); app.use("/integrations", require("./server/routes/intergrationRoutes")); app.use("/chatter", require("./server/routes/chatterRoutes")); + app.use("/esign", require("./server/routes/eSignRoutes")); // Default route for forbidden access app.get("/", (req, res) => { diff --git a/server/esign/esign-new.js b/server/esign/esign-new.js new file mode 100644 index 000000000..387d49f8a --- /dev/null +++ b/server/esign/esign-new.js @@ -0,0 +1,236 @@ + +const { Documenso } = require("@documenso/sdk-typescript"); +const axios = require("axios"); +const { jsrAuthString } = require("../utils/utils"); +const DOCUMENSO_API_KEY = "api_asojim0czruv13ud";//Done on a by team basis, +const documenso = new Documenso({ + apiKey: DOCUMENSO_API_KEY,//Done on a by team basis, + serverURL: "https://stg-app.documenso.com/api/v2", +}); +const jsreport = require("@jsreport/nodejs-client"); + + +async function distributeDocument(req, res) { + try { + const { documentId } = req.body; + const distributeResult = await documenso.documents.distribute({ + documentId, + }); + res.json({ success: true, distributeResult }); + } catch (error) { + console.error("Error distributing document:", error?.data); + res.status(500).json({ error: "An error occurred while distributing the document." }); + } +} + +async function newEsignDocument(req, res) { + + try { + const client = req.userGraphQLClient; + const { pdf: fileBuffer, esigFields } = await RenderTemplate({ client, req }) + const fileBlob = new Blob([fileBuffer], { type: "application/pdf" }); + + const createDocumentResponse = await documenso.documents.create({ + payload: { + title: `Repair Authorization - ${new Date().toLocaleString()}`, + recipients: [ + { + email: "patrick.fic@convenient-brands.com", + name: "Customer Fullname", + role: "SIGNER", + } + ], + meta: { + timezone: "America/Vancouver", + dateFormat: "MM/dd/yyyy hh:mm a", + language: "en", + subject: "Repair Authorization for ABC Collision", + message: "To perform repairs on your vehicle, we must receive digital authorization. Please review and sign the document to proceed with repairs. ", + } + }, + file: fileBlob + }); + + const documentResult = await documenso.documents.get({ + documentId: createDocumentResponse.id, + }); + + if (esigFields && esigFields.length > 0) { + console.log("Adding placeholder fields.") + try { + // await axios.post(`https://stg-app.documenso.com/api/v2/envelope/field/create-many`, { + // envelopeId: createDocumentResponse.envelopeId, + // data: esigFields.map(sigField => ({ ...sigField, recipientId: result.recipients[0].id, })) + // }, { + // headers: { + // Authorization: DOCUMENSO_API_KEY + // } + // }) + const fieldResult = await documenso.envelopes.fields.createMany({ + envelopeId: createDocumentResponse.envelopeId, + data: esigFields.map(sigField => ({ ...sigField, recipientId: documentResult.recipients[0].id, })) + + }); + } catch (error) { + console.log("Error adding placeholders", JSON.stringify(error, null, 2)); + } + } + + + + const presignToken = await documenso.embedding.embeddingPresignCreateEmbeddingPresignToken({}) + + res.json({ token: presignToken.token, documentId: createDocumentResponse.id, envelopeId: createDocumentResponse.envelopeId }); + } + catch (error) { + console.error("Error in newEsignDocument:", error); + res.status(500).json({ error: "An error occurred while creating the e-sign document." }); + } +} + + +async function RenderTemplate({ req }) { + //TODO Refactor to pull + const jsrAuth = jsrAuthString() + + const jsreportClient = new jsreport("https://reports.test.imex.online", process.env.JSR_USER, process.env.JSR_PASSWORD); + const { templateObject, bodyshop } = req.body; + let { contextData, useShopSpecificTemplate, shopSpecificFolder, esigFields } = await fetchContextData({ templateObject, jsrAuth, req }); + //TODO - Refactor to pull template content and render on server instead of posting back to client for rendering. This is necessary to get the rendered PDF buffer that we can then upload to Documenso. + const { ignoreCustomMargins } = { ignoreCustomMargins: false }// Templates[templateObject.name]; + + let reportRequest = { + template: { + name: useShopSpecificTemplate ? `/${bodyshop.imexshopid}/${templateObject.name}` : `/${templateObject.name}`, + + recipe: "chrome-pdf", + ...(!ignoreCustomMargins && { + chrome: { + marginTop: + bodyshop.logo_img_path && + bodyshop.logo_img_path.headerMargin && + bodyshop.logo_img_path.headerMargin > 36 + ? bodyshop.logo_img_path.headerMargin + : "36px", + marginBottom: + bodyshop.logo_img_path && + bodyshop.logo_img_path.footerMargin && + bodyshop.logo_img_path.footerMargin > 50 + ? bodyshop.logo_img_path.footerMargin + : "50px" + } + }), + }, + data: { + ...contextData, + ...templateObject.variables, + ...templateObject.context, + headerpath: shopSpecificFolder ? `/${bodyshop.imexshopid}/header.html` : `/GENERIC/header.html`, + footerpath: shopSpecificFolder ? `/${bodyshop.imexshopid}/footer.html` : `/GENERIC/footer.html`, + bodyshop: bodyshop, + filters: templateObject?.filters, + sorters: templateObject?.sorters, + offset: bodyshop.timezone, //dayjs().utcOffset(), + defaultSorters: templateObject?.defaultSorters + } + }; + const render = await jsreportClient.render(reportRequest); + + //Check render object and download. It should be the PDF? + const pdfBuffer = await render.body() + return { pdf: pdfBuffer, esigFields } +} + +const fetchContextData = async ({ templateObject, jsrAuth, req, }) => { + const { bodyshop } = req.body + const server = "https://reports.test.imex.online"; + //jsreport.headers["FirebaseAuthorization"] = req.headers.authorization; + + + + const folders = await axios.get(`${server}/odata/folders`, { + headers: { Authorization: jsrAuth } + }); + const shopSpecificFolder = folders.data.value.find((f) => f.name === bodyshop.imexshopid); + + const jsReportQueries = await axios.get( + `${server}/odata/assets?$filter=name eq '${templateObject.name}.query'`, + { headers: { Authorization: jsrAuth } } + ); + const jsReportEsig = await axios.get( + `${server}/odata/assets?$filter=name eq '${templateObject.name}.esig'`, + { headers: { Authorization: jsrAuth } } + ); + + let templateQueryToExecute; + let esigFields; + let useShopSpecificTemplate = false; + // let shopSpecificTemplate; + + if (shopSpecificFolder) { + let shopSpecificTemplate = jsReportQueries.data.value.find( + (f) => f?.folder?.shortid === shopSpecificFolder.shortid + ); + if (shopSpecificTemplate) { + useShopSpecificTemplate = true; + templateQueryToExecute = atob(shopSpecificTemplate.content); + } + let shopSpecificEsig = jsReportEsig.data.value.find( + (f) => f?.folder?.shortid === shopSpecificFolder.shortid + ); + if (shopSpecificEsig) { + esigFields = (atob(shopSpecificEsig.content)); + } + } + + if (!templateQueryToExecute) { + const generalTemplate = jsReportQueries.data.value.find((f) => !f.folder); + useShopSpecificTemplate = false; + templateQueryToExecute = atob(generalTemplate.content); + } + if (!esigFields) { + const generalTemplate = jsReportEsig.data.value.find((f) => !f.folder); + useShopSpecificTemplate = false; + if (generalTemplate && generalTemplate.content) { + esigFields = atob(generalTemplate?.content); + } + } + + // Commented out for future revision debugging + // console.log('Template Object'); + // console.dir(templateObject); + // console.log('Unmodified Query'); + // console.dir(templateQueryToExecute); + + // const hasFilters = templateObject?.filters?.length > 0; + // const hasSorters = templateObject?.sorters?.length > 0; + // const hasDefaultSorters = templateObject?.defaultSorters?.length > 0; + const client = req.userGraphQLClient; + + + // In the print center, we will never have sorters or filters. + // We have no template filters or sorters, so we can just execute the query and return the data + // if (!hasFilters && !hasSorters && !hasDefaultSorters) { + let contextData = {}; + if (templateQueryToExecute) { + const data = await client.request( + templateQueryToExecute, + templateObject.variables, + ); + contextData = data; + } + return { + contextData, + useShopSpecificTemplate, + shopSpecificFolder, + esigFields: esigFields ? JSON.parse(esigFields) : [] //TODO: Do the parsing earlier and harden this. Causes a lot of failures on mini format issues. + }; + // } + + // return await generateTemplate(templateQueryToExecute, templateObject, useShopSpecificTemplate, shopSpecificFolder); +}; + +module.exports = { + newEsignDocument, + distributeDocument +} diff --git a/server/esign/webhook.js b/server/esign/webhook.js new file mode 100644 index 000000000..dc3adefd2 --- /dev/null +++ b/server/esign/webhook.js @@ -0,0 +1,150 @@ + +const { Documenso } = require("@documenso/sdk-typescript"); +const fs = require("fs"); +const path = require("path"); + +const documenso = new Documenso({ + apiKey: "api_asojim0czruv13ud",//Done on a by team basis, + serverURL: "https://stg-app.documenso.com/api/v2", +}); + + + +async function esignWebhook(req, res) { + console.log("Esign Webhook Received:", req.body); + try { + + const result = await documenso.documents.download({ + documentId: req.body.payload.id, + }); + result.resultingBuffer = Buffer.from(result.resultingArrayBuffer); + // Save the document to a file for testing purposes + const downloadsDir = path.join(__dirname, '../downloads'); + if (!fs.existsSync(downloadsDir)) { + fs.mkdirSync(downloadsDir, { recursive: true }); + } + const filePath = path.join(downloadsDir, `document_${req.body.payload.id}.pdf`); + fs.writeFileSync(filePath, result.resultingBuffer); + + console.log(result) + + res.sendStatus(200) + } catch (err) { + const downloadsDir = path.join(__dirname, '../downloads'); + if (!fs.existsSync(downloadsDir)) { + fs.mkdirSync(downloadsDir, { recursive: true }); + } + const filePath = path.join(downloadsDir, `document_${req.body.payload.id}.pdf`); + fs.writeFileSync(filePath, Buffer.from(err.body)); + console.error("Error handling esign webhook:", err); + res.sendStatus(500) + } +} + + + +module.exports = { + esignWebhook +} + +// const sampleBody = { +// event: "DOCUMENT_COMPLETED", +// payload: { +// Recipient: [ +// { +// authOptions: { +// accessAuth: [ +// ], +// actionAuth: [ +// ], +// }, +// documentDeletedAt: null, +// documentId: 9827, +// email: "patrick@imexsystems.ca", +// expired: null, +// id: 13311, +// name: "Customer Fullname", +// readStatus: "OPENED", +// rejectionReason: null, +// role: "SIGNER", +// sendStatus: "SENT", +// signedAt: "2026-01-30T18:29:12.648Z", +// signingOrder: null, +// signingStatus: "SIGNED", +// templateId: null, +// token: "uiEWIsXUPTbWHd7QedVgt", +// }, +// ], +// authOptions: { +// globalAccessAuth: [ +// ], +// globalActionAuth: [ +// ], +// }, +// completedAt: "2026-01-30T18:29:16.279Z", +// createdAt: "2026-01-30T18:28:48.861Z", +// deletedAt: null, +// documentMeta: { +// allowDictateNextSigner: false, +// dateFormat: "yyyy-MM-dd hh:mm a", +// distributionMethod: "EMAIL", +// drawSignatureEnabled: true, +// emailSettings: { +// documentCompleted: true, +// documentDeleted: true, +// documentPending: true, +// ownerDocumentCompleted: true, +// recipientRemoved: false, +// recipientSigned: true, +// recipientSigningRequest: true, +// }, +// id: "cml17vfb200qjad1t2spxnc1n", +// language: "en", +// message: "To perform repairs on your vehicle, we must receive digital authorization. Please review and sign the document to proceed with repairs. ", +// redirectUrl: null, +// signingOrder: "PARALLEL", +// subject: "Repair Authorization for ABC Collision", +// timezone: "Etc/UTC", +// typedSignatureEnabled: true, +// uploadSignatureEnabled: true, +// }, +// externalId: null, +// formValues: null, +// id: 9827, +// recipients: [ +// { +// authOptions: { +// accessAuth: [ +// ], +// actionAuth: [ +// ], +// }, +// documentDeletedAt: null, +// documentId: 9827, +// email: "patrick@imexsystems.ca", +// expired: null, +// id: 13311, +// name: "Customer Fullname", +// readStatus: "OPENED", +// rejectionReason: null, +// role: "SIGNER", +// sendStatus: "SENT", +// signedAt: "2026-01-30T18:29:12.648Z", +// signingOrder: null, +// signingStatus: "SIGNED", +// templateId: null, +// token: "uiEWIsXUPTbWHd7QedVgt", +// }, +// ], +// source: "DOCUMENT", +// status: "COMPLETED", +// teamId: 742, +// templateId: null, +// title: "Repair Authorization - 1/30/2026, 6:28:48 PM", +// updatedAt: "2026-01-30T18:29:16.280Z", +// userId: 654, +// visibility: "EVERYONE", +// }, +// createdAt: "2026-01-30T18:29:18.504Z", +// webhookEndpoint: "https://dev.patrickfic.com/esign/webhook", +// } \ No newline at end of file diff --git a/server/routes/esignRoutes.js b/server/routes/esignRoutes.js new file mode 100644 index 000000000..2541d658b --- /dev/null +++ b/server/routes/esignRoutes.js @@ -0,0 +1,16 @@ +const express = require("express"); +const router = express.Router(); + +const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); +const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware"); +const { newEsignDocument, distributeDocument } = require("../esign/esign-new"); +const { esignWebhook } = require("../esign/webhook"); + +//router.use(validateFirebaseIdTokenMiddleware); + +router.post("/new", withUserGraphQLClientMiddleware, newEsignDocument); +router.post("/distribute", withUserGraphQLClientMiddleware, distributeDocument); +router.post("/webhook", withUserGraphQLClientMiddleware, esignWebhook); + + +module.exports = router; diff --git a/server/utils/utils.js b/server/utils/utils.js index 7b7e61fc2..bd78094b3 100644 --- a/server/utils/utils.js +++ b/server/utils/utils.js @@ -2,6 +2,9 @@ exports.servertime = (req, res) => { res.status(200).send(new Date()); }; +exports.jsrAuthString =() => { + return "Basic " + Buffer.from(`${process.env.JSR_USER}:${process.env.JSR_PASSWORD}`).toString("base64") +} exports.jsrAuth = async (req, res) => { - res.send("Basic " + Buffer.from(`${process.env.JSR_USER}:${process.env.JSR_PASSWORD}`).toString("base64")); + res.send(exports.jsrAuthString()); };