From cf087e637e511a4dde9736b9583ac97c1eb095da Mon Sep 17 00:00:00 2001 From: R3D347HR4Y Date: Tue, 9 Jun 2026 14:29:58 +0200 Subject: [PATCH] hocuspocus lol --- services/hocuspocus/package.json | 16 + services/hocuspocus/pnpm-lock.yaml | 548 +++++++++++++++++++++++++++++ services/hocuspocus/server.mjs | 131 +++++++ 3 files changed, 695 insertions(+) create mode 100644 services/hocuspocus/package.json create mode 100644 services/hocuspocus/pnpm-lock.yaml create mode 100644 services/hocuspocus/server.mjs diff --git a/services/hocuspocus/package.json b/services/hocuspocus/package.json new file mode 100644 index 0000000..0c9887a --- /dev/null +++ b/services/hocuspocus/package.json @@ -0,0 +1,16 @@ +{ + "name": "ulti-hocuspocus", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "start": "node --env-file=.env server.mjs", + "dev": "node --env-file=.env --watch server.mjs" + }, + "dependencies": { + "@hocuspocus/server": "^4.1.0", + "@hocuspocus/transformer": "^4.1.0", + "@tiptap/starter-kit": "^3.23.2", + "yjs": "^13.6.27" + } +} diff --git a/services/hocuspocus/pnpm-lock.yaml b/services/hocuspocus/pnpm-lock.yaml new file mode 100644 index 0000000..96c9f61 --- /dev/null +++ b/services/hocuspocus/pnpm-lock.yaml @@ -0,0 +1,548 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@hocuspocus/server': + specifier: ^4.1.0 + version: 4.1.0(y-protocols@1.0.7(yjs@13.6.31))(yjs@13.6.31) + '@hocuspocus/transformer': + specifier: ^4.1.0 + version: 4.1.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0)(y-prosemirror@1.3.7(prosemirror-model@1.25.7)(prosemirror-state@1.4.4)(prosemirror-view@1.41.8)(y-protocols@1.0.7(yjs@13.6.31))(yjs@13.6.31))(yjs@13.6.31) + '@tiptap/starter-kit': + specifier: ^3.23.2 + version: 3.26.0 + yjs: + specifier: ^13.6.27 + version: 13.6.31 + +packages: + + '@hocuspocus/common@4.1.0': + resolution: {integrity: sha512-SOBbu0GcBMbLo7IYRDZC6gvEcoATbEFIC5KqzvLanC6dZZLkv91pYEBli+Exs/G71ELL3iUjSwnaf+gksxcjFA==} + + '@hocuspocus/server@4.1.0': + resolution: {integrity: sha512-mp0Of76kRkK/u+9pKXPLNV4NI2Cyjql/jVnudVb0z6xAd/i7mBDTK88p1I1sNoX9yvCvhY7lQlrvVx6sMcl8Xw==} + engines: {node: '>=22'} + peerDependencies: + y-protocols: ^1.0.6 + yjs: ^13.6.8 + + '@hocuspocus/transformer@4.1.0': + resolution: {integrity: sha512-zjobRWsSipRM0hQvAZ65zPR0jrDcXgVHSFyuePtaNIZTBBeUbn1y6cLyBVQTIazE96UsuNwsZsBsMyFM7WdMUQ==} + peerDependencies: + '@tiptap/core': ^3.0.1 + '@tiptap/pm': ^3.0.1 + y-prosemirror: ^1.2.1 + yjs: ^13.6.8 + + '@tiptap/core@3.26.0': + resolution: {integrity: sha512-7jTed/RirIVsp+lLdLvGzGqF3EBGpnGHGYKOwz6t28V2BIJLAFdUhfEVdWie7xPxQNWK0TP+fPlsqZS0vxfHBg==} + peerDependencies: + '@tiptap/pm': 3.26.0 + + '@tiptap/extension-blockquote@3.26.0': + resolution: {integrity: sha512-57accpka9affjiJRjP2LMNCDJDTMjTvO23RJCxtP43sp9cTIZ7YZnyDfRxCINTRBNK0X4o4w2+emOLyRwsk3CA==} + peerDependencies: + '@tiptap/core': 3.26.0 + + '@tiptap/extension-bold@3.26.0': + resolution: {integrity: sha512-j6CzTMofcGJ5iMoUgDRQpM0FkG00jBID3aKqs+UBbgtzLgtG/CI/91tMFv0XPC30LeFA895qYgvGZtHdejZhiQ==} + peerDependencies: + '@tiptap/core': 3.26.0 + + '@tiptap/extension-bullet-list@3.26.0': + resolution: {integrity: sha512-Jv7BX+kBB2wUIvO/NhuUjv+T3kAed2Tjr664fgQ2zKT6X69jKIkYuCCedrIHuOyaOQ+SBDuH9h51wYv/E97QgQ==} + peerDependencies: + '@tiptap/extension-list': 3.26.0 + + '@tiptap/extension-code-block@3.26.0': + resolution: {integrity: sha512-WPN9iZ3UjeDD2ckDzSs9tleibXv0cLj7j575NxuvjhwZTehYGNeYDSUTi+6DQUG6bKbhGg9Wcei5H0131vvJHg==} + peerDependencies: + '@tiptap/core': 3.26.0 + '@tiptap/pm': 3.26.0 + + '@tiptap/extension-code@3.26.0': + resolution: {integrity: sha512-VJYcV6rvjnENRTroOi9tDcHWW6G0pmCoRETwatlbgfDzuCmkTOwVwQjeJCXOVMMLNPzNiXZzibsRCUt+Azq/jw==} + peerDependencies: + '@tiptap/core': 3.26.0 + + '@tiptap/extension-document@3.26.0': + resolution: {integrity: sha512-Xhd6DCjaxCN4otQNvV6qra+XuoIjk6Vyjm87E5xn5Y/BMw7UGAG7LTkk3C2IEvxKrVZwJjalfxEqdHOgXQzVfw==} + peerDependencies: + '@tiptap/core': 3.26.0 + + '@tiptap/extension-dropcursor@3.26.0': + resolution: {integrity: sha512-rhAtp5J/YVDUCUIc5T7b0XY9dLeuI72JgOr53w0QQc0VA0uwbfTn7sx0LI9PDCE9uwmDH8H3snVRZRnAvlM8oA==} + peerDependencies: + '@tiptap/extensions': 3.26.0 + + '@tiptap/extension-gapcursor@3.26.0': + resolution: {integrity: sha512-SIe68SDwx2fozt/XKG0FhCwzz/yRN6Bvo4D5TqvfDg6NK3PQb1DS4GN9PilmJqbY+kXryuiWEEJOWi7HpO8SuQ==} + peerDependencies: + '@tiptap/extensions': 3.26.0 + + '@tiptap/extension-hard-break@3.26.0': + resolution: {integrity: sha512-baXvv/rtOTVd2Axjb7Zbb41Y9Qmy3U2fP7EHqLuhViqGxVX8LwQtP0PHUXEZkPokbBpRez10+dmOlvvsYFKAZQ==} + peerDependencies: + '@tiptap/core': 3.26.0 + + '@tiptap/extension-heading@3.26.0': + resolution: {integrity: sha512-qenEQEgzE5FjQay/H6iKOnwIt6DPO27cS+v0mGhXmrL1MjrNER4X0ZkATJbVd0WA6ffsAGaP44NKYDworGeidw==} + peerDependencies: + '@tiptap/core': 3.26.0 + + '@tiptap/extension-horizontal-rule@3.26.0': + resolution: {integrity: sha512-a+N/C4wkQV+/8x4ShdoiC2JdTW3Tw84C5cAloYLFMeaWmRa2me9ACSI+zo0SO9bbH9RJwsoRp7eaxBbk27eF1Q==} + peerDependencies: + '@tiptap/core': 3.26.0 + '@tiptap/pm': 3.26.0 + + '@tiptap/extension-italic@3.26.0': + resolution: {integrity: sha512-s8oFpH+0xmhvY19f452/2dExO3p1tjxh761g6cg4irwEUNUEAJKF2VLcjiaeOhNJ+pmnQYxb+VSkwkXvO+7vHQ==} + peerDependencies: + '@tiptap/core': 3.26.0 + + '@tiptap/extension-link@3.26.0': + resolution: {integrity: sha512-FA/d157aBxyvZFvsdc5eSu46tmHWXebAsqOQSvivOMyw+deBb00VlMsf+iD2J8+sekjbMYwx/hvbsu+xUoX43Q==} + peerDependencies: + '@tiptap/core': 3.26.0 + '@tiptap/pm': 3.26.0 + + '@tiptap/extension-list-item@3.26.0': + resolution: {integrity: sha512-MccGyj9HY4fkl04eIiFoTCkr8067Jku/VVdJNtRWW104Spx43C/7V2zpbxPvpcDhq3dW384fDxYXfpnb186xLg==} + peerDependencies: + '@tiptap/extension-list': 3.26.0 + + '@tiptap/extension-list-keymap@3.26.0': + resolution: {integrity: sha512-oBcj6qaNrRHQ+N0+pDuOVAQa4Nx9r8Cm5ANvyM2lTpoy60sOLOizuVvcvw1andVxbSrsZ1N/Sk+RZWyv1uoWyQ==} + peerDependencies: + '@tiptap/extension-list': 3.26.0 + + '@tiptap/extension-list@3.26.0': + resolution: {integrity: sha512-EM8woyHDNKLEQ+lWUEoDtA4KrwP6fei/mYX1NxseMzKHHo7LFecx7wk6sovAXZrUvdML/yFBihgiMiO5VIsfkg==} + peerDependencies: + '@tiptap/core': 3.26.0 + '@tiptap/pm': 3.26.0 + + '@tiptap/extension-ordered-list@3.26.0': + resolution: {integrity: sha512-ItLdFlcMsJz2vhbs1PcUfcN7nzVqGBOwPeCrrWxjrgscp+K3JoOGD+HhVVpBACOMwivUrlh8Ry5Ohvues2nOeA==} + peerDependencies: + '@tiptap/extension-list': 3.26.0 + + '@tiptap/extension-paragraph@3.26.0': + resolution: {integrity: sha512-h8fYLikg4qN39IghQ1y9g+zzUsgxBpDi5YS3IZbWoxWYYx1YqLL8nAvOiPr7Us14aQ0TjA2/xY7zqmyf29rX1A==} + peerDependencies: + '@tiptap/core': 3.26.0 + + '@tiptap/extension-strike@3.26.0': + resolution: {integrity: sha512-jUll3Pqhq7u1JKvO0B6USW/bmVmUsO6sRcxo/d5tXqLhS0tWAobOGoGU2IgwXnQDSjf+vF73RYD5tRGDLkRC9Q==} + peerDependencies: + '@tiptap/core': 3.26.0 + + '@tiptap/extension-text@3.26.0': + resolution: {integrity: sha512-yZXdevp3/8omGbb40Z52VfvID+tsRNhPQ1GNUToD56XSr2BjdJyAzAb9rWGgDKgVMUPLgJ26yT0O278RFqOKhA==} + peerDependencies: + '@tiptap/core': 3.26.0 + + '@tiptap/extension-underline@3.26.0': + resolution: {integrity: sha512-LlVkivH5cBwov/EMD8BL7ZRcU6YcadiSVIffLW1hyalw9YfhaFzoLxjtWhL7jiU/n2Kg+9dXSZxmV2hTeTwyrQ==} + peerDependencies: + '@tiptap/core': 3.26.0 + + '@tiptap/extensions@3.26.0': + resolution: {integrity: sha512-4wajuqnO2X0+LVvsBjW/xk3/tmdb16bNL939QhicAay4YYqXITeV2v3XJsryzmG4L5GkK1yLxvRGk4aLoxWrnA==} + peerDependencies: + '@tiptap/core': 3.26.0 + '@tiptap/pm': 3.26.0 + + '@tiptap/pm@3.26.0': + resolution: {integrity: sha512-q4RDeWwVrhOL0jJCGRgGxLSdjOYwzQ4h2InURZVhC66433ipcHd6f3bqSOhcXZ4r0sFmMNsuF7aZmUntjWLc7w==} + + '@tiptap/starter-kit@3.26.0': + resolution: {integrity: sha512-o34EtMfqtBaljdmeElZsRG/067oGx9Zcq+j2GWo71KlZe22ga/ALexeTf1c+ETsjCxSTKR6eyQ4RZvz/2JpYfg==} + + async-mutex@0.5.0: + resolution: {integrity: sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==} + + crossws@0.4.5: + resolution: {integrity: sha512-wUR89x/Rw7/8t+vn0CmGDYM9TD6VtARGb0LD5jq2wjtMy1vCP4M+sm6N6TigWeTYvnA8MoW29NqqXD0ep0rfBA==} + peerDependencies: + srvx: '>=0.11.5' + peerDependenciesMeta: + srvx: + optional: true + + isomorphic.js@0.2.5: + resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + lib0@0.2.117: + resolution: {integrity: sha512-DeXj9X5xDCjgKLU/7RR+/HQEVzuuEUiwldwOGsHK/sfAfELGWEyTcf0x+uOvCvK3O2zPmZePXWL85vtia6GyZw==} + engines: {node: '>=16'} + hasBin: true + + linkifyjs@4.3.3: + resolution: {integrity: sha512-P8aEP5U/D1/IlTY2OeYsErdwh9bGuLE30NcXtKEjgdHcahveQoQwM2yZNsioQHsWFz0P7KKudisbrzCgR0sDHg==} + + orderedmap@2.1.1: + resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} + + prosemirror-changeset@2.4.1: + resolution: {integrity: sha512-96WBLhOaYhJ+kPhLg3uW359Tz6I/MfcrQfL4EGv4SrcqKEMC1gmoGrXHecPE8eOwTVCJ4IwgfzM8fFad25wNfw==} + + prosemirror-commands@1.7.1: + resolution: {integrity: sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==} + + prosemirror-dropcursor@1.8.2: + resolution: {integrity: sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==} + + prosemirror-gapcursor@1.4.1: + resolution: {integrity: sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==} + + prosemirror-history@1.5.0: + resolution: {integrity: sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==} + + prosemirror-inputrules@1.5.1: + resolution: {integrity: sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==} + + prosemirror-keymap@1.2.3: + resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==} + + prosemirror-model@1.25.7: + resolution: {integrity: sha512-A79aN8QEFUwI6cax8Yq4Rpcx1TJZ3Kagn+ii7qLo4/V8H3mMiHrhFyhTyHHvpSnOgMPpWiDGSwM3etwrxE50ug==} + + prosemirror-schema-list@1.5.1: + resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==} + + prosemirror-state@1.4.4: + resolution: {integrity: sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==} + + prosemirror-tables@1.8.5: + resolution: {integrity: sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==} + + prosemirror-transform@1.12.0: + resolution: {integrity: sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==} + + prosemirror-view@1.41.8: + resolution: {integrity: sha512-TnKDdohEatgyZNGCDWIdccOHXhYloJwbwU+phw/a23KBvJIR9lWQWW7WHHK3vBdOLDNuF7TaX98GObUZOWkOnA==} + + rope-sequence@1.3.4: + resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + + y-prosemirror@1.3.7: + resolution: {integrity: sha512-NpM99WSdD4Fx4if5xOMDpPtU3oAmTSjlzh5U4353ABbRHl1HtAFUx6HlebLZfyFxXN9jzKMDkVbcRjqOZVkYQg==} + engines: {node: '>=16.0.0', npm: '>=8.0.0'} + peerDependencies: + prosemirror-model: ^1.7.1 + prosemirror-state: ^1.2.3 + prosemirror-view: ^1.9.10 + y-protocols: ^1.0.1 + yjs: ^13.5.38 + + y-protocols@1.0.7: + resolution: {integrity: sha512-YSVsLoXxO67J6eE/nV4AtFtT3QEotZf5sK5BHxFBXso7VDUT3Tx07IfA6hsu5Q5OmBdMkQVmFZ9QOA7fikWvnw==} + engines: {node: '>=16.0.0', npm: '>=8.0.0'} + peerDependencies: + yjs: ^13.0.0 + + yjs@13.6.31: + resolution: {integrity: sha512-Eq+5BRfbeGyqGVrTJL3bEcr8gKkxPuyuoHmAwpk52fDb8kOVMrfVSTRPd6yiGgX5Fskb96qCRjzjbRjrL4YEnw==} + engines: {node: '>=16.0.0', npm: '>=8.0.0'} + +snapshots: + + '@hocuspocus/common@4.1.0': + dependencies: + lib0: 0.2.117 + + '@hocuspocus/server@4.1.0(y-protocols@1.0.7(yjs@13.6.31))(yjs@13.6.31)': + dependencies: + '@hocuspocus/common': 4.1.0 + async-mutex: 0.5.0 + crossws: 0.4.5 + kleur: 4.1.5 + lib0: 0.2.117 + y-protocols: 1.0.7(yjs@13.6.31) + yjs: 13.6.31 + transitivePeerDependencies: + - srvx + + '@hocuspocus/transformer@4.1.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0)(y-prosemirror@1.3.7(prosemirror-model@1.25.7)(prosemirror-state@1.4.4)(prosemirror-view@1.41.8)(y-protocols@1.0.7(yjs@13.6.31))(yjs@13.6.31))(yjs@13.6.31)': + dependencies: + '@tiptap/core': 3.26.0(@tiptap/pm@3.26.0) + '@tiptap/pm': 3.26.0 + '@tiptap/starter-kit': 3.26.0 + y-prosemirror: 1.3.7(prosemirror-model@1.25.7)(prosemirror-state@1.4.4)(prosemirror-view@1.41.8)(y-protocols@1.0.7(yjs@13.6.31))(yjs@13.6.31) + yjs: 13.6.31 + + '@tiptap/core@3.26.0(@tiptap/pm@3.26.0)': + dependencies: + '@tiptap/pm': 3.26.0 + + '@tiptap/extension-blockquote@3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))': + dependencies: + '@tiptap/core': 3.26.0(@tiptap/pm@3.26.0) + + '@tiptap/extension-bold@3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))': + dependencies: + '@tiptap/core': 3.26.0(@tiptap/pm@3.26.0) + + '@tiptap/extension-bullet-list@3.26.0(@tiptap/extension-list@3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0))': + dependencies: + '@tiptap/extension-list': 3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0) + + '@tiptap/extension-code-block@3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0)': + dependencies: + '@tiptap/core': 3.26.0(@tiptap/pm@3.26.0) + '@tiptap/pm': 3.26.0 + + '@tiptap/extension-code@3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))': + dependencies: + '@tiptap/core': 3.26.0(@tiptap/pm@3.26.0) + + '@tiptap/extension-document@3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))': + dependencies: + '@tiptap/core': 3.26.0(@tiptap/pm@3.26.0) + + '@tiptap/extension-dropcursor@3.26.0(@tiptap/extensions@3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0))': + dependencies: + '@tiptap/extensions': 3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0) + + '@tiptap/extension-gapcursor@3.26.0(@tiptap/extensions@3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0))': + dependencies: + '@tiptap/extensions': 3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0) + + '@tiptap/extension-hard-break@3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))': + dependencies: + '@tiptap/core': 3.26.0(@tiptap/pm@3.26.0) + + '@tiptap/extension-heading@3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))': + dependencies: + '@tiptap/core': 3.26.0(@tiptap/pm@3.26.0) + + '@tiptap/extension-horizontal-rule@3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0)': + dependencies: + '@tiptap/core': 3.26.0(@tiptap/pm@3.26.0) + '@tiptap/pm': 3.26.0 + + '@tiptap/extension-italic@3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))': + dependencies: + '@tiptap/core': 3.26.0(@tiptap/pm@3.26.0) + + '@tiptap/extension-link@3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0)': + dependencies: + '@tiptap/core': 3.26.0(@tiptap/pm@3.26.0) + '@tiptap/pm': 3.26.0 + linkifyjs: 4.3.3 + + '@tiptap/extension-list-item@3.26.0(@tiptap/extension-list@3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0))': + dependencies: + '@tiptap/extension-list': 3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0) + + '@tiptap/extension-list-keymap@3.26.0(@tiptap/extension-list@3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0))': + dependencies: + '@tiptap/extension-list': 3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0) + + '@tiptap/extension-list@3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0)': + dependencies: + '@tiptap/core': 3.26.0(@tiptap/pm@3.26.0) + '@tiptap/pm': 3.26.0 + + '@tiptap/extension-ordered-list@3.26.0(@tiptap/extension-list@3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0))': + dependencies: + '@tiptap/extension-list': 3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0) + + '@tiptap/extension-paragraph@3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))': + dependencies: + '@tiptap/core': 3.26.0(@tiptap/pm@3.26.0) + + '@tiptap/extension-strike@3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))': + dependencies: + '@tiptap/core': 3.26.0(@tiptap/pm@3.26.0) + + '@tiptap/extension-text@3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))': + dependencies: + '@tiptap/core': 3.26.0(@tiptap/pm@3.26.0) + + '@tiptap/extension-underline@3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))': + dependencies: + '@tiptap/core': 3.26.0(@tiptap/pm@3.26.0) + + '@tiptap/extensions@3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0)': + dependencies: + '@tiptap/core': 3.26.0(@tiptap/pm@3.26.0) + '@tiptap/pm': 3.26.0 + + '@tiptap/pm@3.26.0': + dependencies: + prosemirror-changeset: 2.4.1 + prosemirror-commands: 1.7.1 + prosemirror-dropcursor: 1.8.2 + prosemirror-gapcursor: 1.4.1 + prosemirror-history: 1.5.0 + prosemirror-inputrules: 1.5.1 + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.7 + prosemirror-schema-list: 1.5.1 + prosemirror-state: 1.4.4 + prosemirror-tables: 1.8.5 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.41.8 + + '@tiptap/starter-kit@3.26.0': + dependencies: + '@tiptap/core': 3.26.0(@tiptap/pm@3.26.0) + '@tiptap/extension-blockquote': 3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0)) + '@tiptap/extension-bold': 3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0)) + '@tiptap/extension-bullet-list': 3.26.0(@tiptap/extension-list@3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0)) + '@tiptap/extension-code': 3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0)) + '@tiptap/extension-code-block': 3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0) + '@tiptap/extension-document': 3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0)) + '@tiptap/extension-dropcursor': 3.26.0(@tiptap/extensions@3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0)) + '@tiptap/extension-gapcursor': 3.26.0(@tiptap/extensions@3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0)) + '@tiptap/extension-hard-break': 3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0)) + '@tiptap/extension-heading': 3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0)) + '@tiptap/extension-horizontal-rule': 3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0) + '@tiptap/extension-italic': 3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0)) + '@tiptap/extension-link': 3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0) + '@tiptap/extension-list': 3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0) + '@tiptap/extension-list-item': 3.26.0(@tiptap/extension-list@3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0)) + '@tiptap/extension-list-keymap': 3.26.0(@tiptap/extension-list@3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0)) + '@tiptap/extension-ordered-list': 3.26.0(@tiptap/extension-list@3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0)) + '@tiptap/extension-paragraph': 3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0)) + '@tiptap/extension-strike': 3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0)) + '@tiptap/extension-text': 3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0)) + '@tiptap/extension-underline': 3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0)) + '@tiptap/extensions': 3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0) + '@tiptap/pm': 3.26.0 + + async-mutex@0.5.0: + dependencies: + tslib: 2.8.1 + + crossws@0.4.5: {} + + isomorphic.js@0.2.5: {} + + kleur@4.1.5: {} + + lib0@0.2.117: + dependencies: + isomorphic.js: 0.2.5 + + linkifyjs@4.3.3: {} + + orderedmap@2.1.1: {} + + prosemirror-changeset@2.4.1: + dependencies: + prosemirror-transform: 1.12.0 + + prosemirror-commands@1.7.1: + dependencies: + prosemirror-model: 1.25.7 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + + prosemirror-dropcursor@1.8.2: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.41.8 + + prosemirror-gapcursor@1.4.1: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.7 + prosemirror-state: 1.4.4 + prosemirror-view: 1.41.8 + + prosemirror-history@1.5.0: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.41.8 + rope-sequence: 1.3.4 + + prosemirror-inputrules@1.5.1: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + + prosemirror-keymap@1.2.3: + dependencies: + prosemirror-state: 1.4.4 + w3c-keyname: 2.2.8 + + prosemirror-model@1.25.7: + dependencies: + orderedmap: 2.1.1 + + prosemirror-schema-list@1.5.1: + dependencies: + prosemirror-model: 1.25.7 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + + prosemirror-state@1.4.4: + dependencies: + prosemirror-model: 1.25.7 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.41.8 + + prosemirror-tables@1.8.5: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.7 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.41.8 + + prosemirror-transform@1.12.0: + dependencies: + prosemirror-model: 1.25.7 + + prosemirror-view@1.41.8: + dependencies: + prosemirror-model: 1.25.7 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + + rope-sequence@1.3.4: {} + + tslib@2.8.1: {} + + w3c-keyname@2.2.8: {} + + y-prosemirror@1.3.7(prosemirror-model@1.25.7)(prosemirror-state@1.4.4)(prosemirror-view@1.41.8)(y-protocols@1.0.7(yjs@13.6.31))(yjs@13.6.31): + dependencies: + lib0: 0.2.117 + prosemirror-model: 1.25.7 + prosemirror-state: 1.4.4 + prosemirror-view: 1.41.8 + y-protocols: 1.0.7(yjs@13.6.31) + yjs: 13.6.31 + + y-protocols@1.0.7(yjs@13.6.31): + dependencies: + lib0: 0.2.117 + yjs: 13.6.31 + + yjs@13.6.31: + dependencies: + lib0: 0.2.117 diff --git a/services/hocuspocus/server.mjs b/services/hocuspocus/server.mjs new file mode 100644 index 0000000..ea0ea74 --- /dev/null +++ b/services/hocuspocus/server.mjs @@ -0,0 +1,131 @@ +import { Server } from "@hocuspocus/server" +import { TiptapTransformer } from "@hocuspocus/transformer" +import * as Y from "yjs" +import { createRequire } from "node:module" + +const require = createRequire(import.meta.url) + +const StarterKit = require("@tiptap/starter-kit").default + +const PORT = Number(process.env.HOCUSPOCUS_PORT || 1234) +const SECRET = process.env.HOCUSPOCUS_SECRET || "" +const ULTID_URL = (process.env.ULTID_INTERNAL_URL || "http://127.0.0.1").replace(/\/$/, "") + +function decodeJwtPayload(token) { + const parts = token.split(".") + if (parts.length < 2) return null + try { + const json = Buffer.from(parts[1], "base64url").toString("utf8") + return JSON.parse(json) + } catch { + return null + } +} + +function hookContext(payload) { + return payload?.lastContext ?? payload?.context ?? {} +} + +async function loadFromUltid(context) { + if (!context?.path || !context?.user) return null + const params = new URLSearchParams({ user: context.user, path: context.path }) + const res = await fetch(`${ULTID_URL}/api/v1/richtext/internal/document?${params}`, { + headers: SECRET ? { "X-Hocuspocus-Secret": SECRET } : {}, + }) + if (res.status === 404) return null + if (!res.ok) throw new Error(`load failed: ${res.status}`) + const raw = await res.text() + if (!raw.trim()) return null + try { + const doc = JSON.parse(raw) + if (doc.yjsState) { + return Buffer.from(doc.yjsState, "base64") + } + if (doc.content) { + const ydoc = TiptapTransformer.toYdoc(doc.content, "default", [StarterKit]) + return Buffer.from(Y.encodeStateAsUpdate(ydoc)) + } + } catch (err) { + console.error("[onLoadDocument] parse", err) + } + return null +} + +async function storeToUltid(context, document) { + if (!context?.path || !context?.user) { + throw new Error("store missing path or user in context") + } + const state = Buffer.from(Y.encodeStateAsUpdate(document)).toString("base64") + let tiptapJson = null + try { + tiptapJson = TiptapTransformer.fromYdoc(document, "default") + } catch { + /* optional */ + } + const body = { + room: context.room ?? context.path, + path: context.path, + user: context.user, + sub: context.sub ?? "", + yjsState: state, + document: tiptapJson, + } + const res = await fetch(`${ULTID_URL}/api/v1/richtext/hooks/store`, { + method: "POST", + headers: { + "Content-Type": "application/json", + ...(SECRET ? { "X-Hocuspocus-Secret": SECRET } : {}), + }, + body: JSON.stringify(body), + }) + if (!res.ok) { + const detail = await res.text().catch(() => "") + throw new Error(`store failed: ${res.status}${detail ? ` ${detail}` : ""}`) + } +} + +const server = new Server({ + port: PORT, + debounce: 3000, + maxDebounce: 10000, + + async onAuthenticate(data) { + const { token, documentName } = data + if (!SECRET) return {} + const payload = decodeJwtPayload(token) + if (!payload?.room || payload.room !== documentName) { + console.error("[onAuthenticate] forbidden", { documentName, room: payload?.room }) + throw new Error("forbidden") + } + if (payload.exp && payload.exp * 1000 < Date.now()) { + console.error("[onAuthenticate] token expired", { documentName }) + throw new Error("token expired") + } + return { + room: payload.room, + path: payload.path, + user: payload.user, + sub: payload.sub, + name: payload.name, + mode: payload.mode, + } + }, + + async onLoadDocument(data) { + const ctx = hookContext(data) + return await loadFromUltid(ctx) + }, + + async onStoreDocument(data) { + const ctx = hookContext(data) + if (ctx.mode === "view") return + try { + await storeToUltid(ctx, data.document) + } catch (err) { + console.error("[onStoreDocument]", err) + } + }, +}) + +server.listen() +console.log(`Hocuspocus listening on :${PORT}`)