diff --git a/package-lock.json b/package-lock.json index 2fa7d78..bef5f47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,11 +8,6 @@ "name": "rmaps-online", "version": "0.1.0", "dependencies": { - "@automerge/automerge": "^2.2.8", - "@automerge/automerge-repo": "^1.2.1", - "@automerge/automerge-repo-network-websocket": "^1.2.1", - "@automerge/automerge-repo-react-hooks": "^1.2.1", - "@automerge/automerge-repo-storage-indexeddb": "^1.2.1", "maplibre-gl": "^5.0.0", "nanoid": "^5.0.9", "next": "^14.2.28", @@ -45,174 +40,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@automerge/automerge": { - "version": "2.2.9", - "resolved": "https://registry.npmjs.org/@automerge/automerge/-/automerge-2.2.9.tgz", - "integrity": "sha512-6HM52Ops79hAQBWMg/t0MNfGOdEiXyenQjO9F1hKZq0RWDsMLpPa1SzRy/C4/4UyX67sTHuA5CwBpH34SpfZlA==", - "license": "MIT", - "dependencies": { - "uuid": "^9.0.0" - } - }, - "node_modules/@automerge/automerge-repo": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@automerge/automerge-repo/-/automerge-repo-1.2.1.tgz", - "integrity": "sha512-uEBr4bM01aSWkEt2tDKQxfW0Pahz2zbTTn4sRJfeKJlAg2SLr4QepFJ+3Tp4CNEkkU485olfnKYf6gt7uilMZQ==", - "license": "MIT", - "dependencies": { - "@automerge/automerge": "^2.2.5", - "bs58check": "^3.0.1", - "cbor-x": "^1.3.0", - "debug": "^4.3.4", - "eventemitter3": "^5.0.1", - "fast-sha256": "^1.3.0", - "tiny-typed-emitter": "^2.1.0", - "ts-node": "^10.9.1", - "uuid": "^9.0.0", - "xstate": "^5.9.1" - } - }, - "node_modules/@automerge/automerge-repo-network-websocket": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@automerge/automerge-repo-network-websocket/-/automerge-repo-network-websocket-1.2.1.tgz", - "integrity": "sha512-n4sI6l51iBf0edWtX+vhk+sc8wJL72IVZl682SvzRHakA5CzyNZiC8sVzc142zRT3RDN12jCWGpwA2Voq6EXsQ==", - "license": "MIT", - "dependencies": { - "@automerge/automerge-repo": "1.2.1", - "cbor-x": "^1.3.0", - "debug": "^4.3.4", - "eventemitter3": "^5.0.1", - "isomorphic-ws": "^5.0.0", - "ws": "^8.7.0" - } - }, - "node_modules/@automerge/automerge-repo-react-hooks": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@automerge/automerge-repo-react-hooks/-/automerge-repo-react-hooks-1.2.1.tgz", - "integrity": "sha512-xRRPFRp7dMLZZoIPuczI6ll8U6Qo4NkLAstIHZloGcVefSEOLXRRObMwkITbVaRCEM2zyRLG0MfA9T4dA1JYFQ==", - "license": "MIT", - "dependencies": { - "@automerge/automerge": "^2.2.5", - "@automerge/automerge-repo": "1.2.1", - "eventemitter3": "^5.0.1", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-usestateref": "^1.0.8" - }, - "peerDependencies": { - "react": ">16.8.0", - "react-dom": ">16.8.0" - } - }, - "node_modules/@automerge/automerge-repo-storage-indexeddb": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@automerge/automerge-repo-storage-indexeddb/-/automerge-repo-storage-indexeddb-1.2.1.tgz", - "integrity": "sha512-u+9eZZJK7DAr541buF4ut1ipkuiKRoaAtoFYo/ilq7zOLO7JX+GQOFx/8eKKRDlGt/AHTcDaFktkcaX0vKahQQ==", - "license": "MIT", - "dependencies": { - "@automerge/automerge-repo": "1.2.1" - } - }, - "node_modules/@cbor-extract/cbor-extract-darwin-arm64": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.2.0.tgz", - "integrity": "sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@cbor-extract/cbor-extract-darwin-x64": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-x64/-/cbor-extract-darwin-x64-2.2.0.tgz", - "integrity": "sha512-1liF6fgowph0JxBbYnAS7ZlqNYLf000Qnj4KjqPNW4GViKrEql2MgZnAsExhY9LSy8dnvA4C0qHEBgPrll0z0w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@cbor-extract/cbor-extract-linux-arm": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm/-/cbor-extract-linux-arm-2.2.0.tgz", - "integrity": "sha512-QeBcBXk964zOytiedMPQNZr7sg0TNavZeuUCD6ON4vEOU/25+pLhNN6EDIKJ9VLTKaZ7K7EaAriyYQ1NQ05s/Q==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@cbor-extract/cbor-extract-linux-arm64": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm64/-/cbor-extract-linux-arm64-2.2.0.tgz", - "integrity": "sha512-rQvhNmDuhjTVXSPFLolmQ47/ydGOFXtbR7+wgkSY0bdOxCFept1hvg59uiLPT2fVDuJFuEy16EImo5tE2x3RsQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@cbor-extract/cbor-extract-linux-x64": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-x64/-/cbor-extract-linux-x64-2.2.0.tgz", - "integrity": "sha512-cWLAWtT3kNLHSvP4RKDzSTX9o0wvQEEAj4SKvhWuOVZxiDAeQazr9A+PSiRILK1VYMLeDml89ohxCnUNQNQNCw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@cbor-extract/cbor-extract-win32-x64": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-win32-x64/-/cbor-extract-win32-x64-2.2.0.tgz", - "integrity": "sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, "node_modules/@emnapi/core": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", @@ -410,6 +237,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -419,6 +247,7 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { @@ -549,15 +378,15 @@ } }, "node_modules/@next/env": { - "version": "14.2.28", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.28.tgz", - "integrity": "sha512-PAmWhJfJQlP+kxZwCjrVd9QnR5x0R3u0mTXTiZDgSd4h5LdXmjxCCWbN9kq6hkZBOax8Rm3xDW5HagWyJuT37g==", + "version": "14.2.35", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.35.tgz", + "integrity": "sha512-DuhvCtj4t9Gwrx80dmz2F4t/zKQ4ktN8WrMwOuVzkJfBilwAwGr6v16M5eI8yCuZ63H9TTuEU09Iu2HqkzFPVQ==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { - "version": "14.2.28", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.28.tgz", - "integrity": "sha512-GQUPA1bTZy5qZdPV5MOHB18465azzhg8xm5o2SqxMF+h1rWNjB43y6xmIPHG5OV2OiU3WxuINpusXom49DdaIQ==", + "version": "14.2.35", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.35.tgz", + "integrity": "sha512-Jw9A3ICz2183qSsqwi7fgq4SBPiNfmOLmTPXKvlnzstUwyvBrtySiY+8RXJweNAs9KThb1+bYhZh9XWcNOr2zQ==", "dev": true, "license": "MIT", "dependencies": { @@ -565,9 +394,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "14.2.28", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.28.tgz", - "integrity": "sha512-kzGChl9setxYWpk3H6fTZXXPFFjg7urptLq5o5ZgYezCrqlemKttwMT5iFyx/p1e/JeglTwDFRtb923gTJ3R1w==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.33.tgz", + "integrity": "sha512-HqYnb6pxlsshoSTubdXKu15g3iivcbsMXg4bYpjL2iS/V6aQot+iyF4BUc2qA/J/n55YtvE4PHMKWBKGCF/+wA==", "cpu": [ "arm64" ], @@ -581,9 +410,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "14.2.28", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.28.tgz", - "integrity": "sha512-z6FXYHDJlFOzVEOiiJ/4NG8aLCeayZdcRSMjPDysW297Up6r22xw6Ea9AOwQqbNsth8JNgIK8EkWz2IDwaLQcw==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.33.tgz", + "integrity": "sha512-8HGBeAE5rX3jzKvF593XTTFg3gxeU4f+UWnswa6JPhzaR6+zblO5+fjltJWIZc4aUalqTclvN2QtTC37LxvZAA==", "cpu": [ "x64" ], @@ -597,9 +426,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.2.28", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.28.tgz", - "integrity": "sha512-9ARHLEQXhAilNJ7rgQX8xs9aH3yJSj888ssSjJLeldiZKR4D7N08MfMqljk77fAwZsWwsrp8ohHsMvurvv9liQ==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.33.tgz", + "integrity": "sha512-JXMBka6lNNmqbkvcTtaX8Gu5by9547bukHQvPoLe9VRBx1gHwzf5tdt4AaezW85HAB3pikcvyqBToRTDA4DeLw==", "cpu": [ "arm64" ], @@ -613,9 +442,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.2.28", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.28.tgz", - "integrity": "sha512-p6gvatI1nX41KCizEe6JkF0FS/cEEF0u23vKDpl+WhPe/fCTBeGkEBh7iW2cUM0rvquPVwPWdiUR6Ebr/kQWxQ==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.33.tgz", + "integrity": "sha512-Bm+QulsAItD/x6Ih8wGIMfRJy4G73tu1HJsrccPW6AfqdZd0Sfm5Imhgkgq2+kly065rYMnCOxTBvmvFY1BKfg==", "cpu": [ "arm64" ], @@ -629,9 +458,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.2.28", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.28.tgz", - "integrity": "sha512-nsiSnz2wO6GwMAX2o0iucONlVL7dNgKUqt/mDTATGO2NY59EO/ZKnKEr80BJFhuA5UC1KZOMblJHWZoqIJddpA==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.33.tgz", + "integrity": "sha512-FnFn+ZBgsVMbGDsTqo8zsnRzydvsGV8vfiWwUo1LD8FTmPTdV+otGSWKc4LJec0oSexFnCYVO4hX8P8qQKaSlg==", "cpu": [ "x64" ], @@ -645,9 +474,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.28", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.28.tgz", - "integrity": "sha512-+IuGQKoI3abrXFqx7GtlvNOpeExUH1mTIqCrh1LGFf8DnlUcTmOOCApEnPJUSLrSbzOdsF2ho2KhnQoO0I1RDw==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.33.tgz", + "integrity": "sha512-345tsIWMzoXaQndUTDv1qypDRiebFxGYx9pYkhwY4hBRaOLt8UGfiWKr9FSSHs25dFIf8ZqIFaPdy5MljdoawA==", "cpu": [ "x64" ], @@ -661,9 +490,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.28", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.28.tgz", - "integrity": "sha512-l61WZ3nevt4BAnGksUVFKy2uJP5DPz2E0Ma/Oklvo3sGj9sw3q7vBWONFRgz+ICiHpW5mV+mBrkB3XEubMrKaA==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.33.tgz", + "integrity": "sha512-nscpt0G6UCTkrT2ppnJnFsYbPDQwmum4GNXYTeoTIdsmMydSKFz9Iny2jpaRupTb+Wl298+Rh82WKzt9LCcqSQ==", "cpu": [ "arm64" ], @@ -677,9 +506,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.28", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.28.tgz", - "integrity": "sha512-+Kcp1T3jHZnJ9v9VTJ/yf1t/xmtFAc/Sge4v7mVc1z+NYfYzisi8kJ9AsY8itbgq+WgEwMtOpiLLJsUy2qnXZw==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.33.tgz", + "integrity": "sha512-pc9LpGNKhJ0dXQhZ5QMmYxtARwwmWLpeocFmVG5Z0DzWq5Uf0izcI8tLc+qOpqxO1PWqZ5A7J1blrUIKrIFc7Q==", "cpu": [ "ia32" ], @@ -693,9 +522,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.28", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.28.tgz", - "integrity": "sha512-1gCmpvyhz7DkB1srRItJTnmR2UwQPAUXXIg9r0/56g3O8etGmwlX68skKXJOp9EejW3hhv7nSQUJ2raFiz4MoA==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.33.tgz", + "integrity": "sha512-nOjfZMy8B94MdisuzZo9/57xuFVLHJaDj5e/xrduJp9CV2/HrfxTRH2fbyLe+K9QT41WBLUd4iXX3R7jBp0EUg==", "cpu": [ "x64" ], @@ -708,18 +537,6 @@ "node": ">= 10" } }, - "node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -809,30 +626,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", - "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "license": "MIT" - }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", @@ -870,6 +663,7 @@ "version": "22.19.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", + "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -1461,6 +1255,7 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -1479,18 +1274,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1836,12 +1619,6 @@ "dev": true, "license": "MIT" }, - "node_modules/base-x": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.1.tgz", - "integrity": "sha512-uAZ8x6r6S3aUM9rbHGVOIsR15U/ZSc82b3ymnCPsT45Gk1DDvhDPdIgB5MrhirZWt+5K0EEPQH985kNqZgNPFw==", - "license": "MIT" - }, "node_modules/baseline-browser-mapping": { "version": "2.9.7", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.7.tgz", @@ -1923,25 +1700,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/bs58": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", - "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", - "license": "MIT", - "dependencies": { - "base-x": "^4.0.0" - } - }, - "node_modules/bs58check": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-3.0.1.tgz", - "integrity": "sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "^1.2.0", - "bs58": "^5.0.0" - } - }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -2043,37 +1801,6 @@ ], "license": "CC-BY-4.0" }, - "node_modules/cbor-extract": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cbor-extract/-/cbor-extract-2.2.0.tgz", - "integrity": "sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "node-gyp-build-optional-packages": "5.1.1" - }, - "bin": { - "download-cbor-prebuilds": "bin/download-prebuilds.js" - }, - "optionalDependencies": { - "@cbor-extract/cbor-extract-darwin-arm64": "2.2.0", - "@cbor-extract/cbor-extract-darwin-x64": "2.2.0", - "@cbor-extract/cbor-extract-linux-arm": "2.2.0", - "@cbor-extract/cbor-extract-linux-arm64": "2.2.0", - "@cbor-extract/cbor-extract-linux-x64": "2.2.0", - "@cbor-extract/cbor-extract-win32-x64": "2.2.0" - } - }, - "node_modules/cbor-x": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/cbor-x/-/cbor-x-1.6.0.tgz", - "integrity": "sha512-0kareyRwHSkL6ws5VXHEf8uY1liitysCVJjlmhaLG+IXLqhSaOO+t63coaso7yjwEzWZzLy8fJo06gZDVQM9Qg==", - "license": "MIT", - "optionalDependencies": { - "cbor-extract": "^2.2.0" - } - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2172,12 +1899,6 @@ "dev": true, "license": "MIT" }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "license": "MIT" - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2278,6 +1999,7 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2334,16 +2056,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "license": "Apache-2.0", - "optional": true, - "engines": { - "node": ">=8" - } - }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -2351,15 +2063,6 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -2680,13 +2383,13 @@ } }, "node_modules/eslint-config-next": { - "version": "14.2.28", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.28.tgz", - "integrity": "sha512-UxJMRQ4uaEdLp3mVQoIbRIlEF0S2rTlyZhI/2yEMVdAWmgFfPY4iJZ68jCbhLvXMnKeHMkmqTGjEhFH5Vm9h+A==", + "version": "14.2.35", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.35.tgz", + "integrity": "sha512-BpLsv01UisH193WyT/1lpHqq5iJ/Orfz9h/NOOlAmTUq4GY349PextQ62K4XpnaM9supeiEn3TaOTeQO07gURg==", "dev": true, "license": "MIT", "dependencies": { - "@next/eslint-plugin-next": "14.2.28", + "@next/eslint-plugin-next": "14.2.35", "@rushstack/eslint-patch": "^1.3.3", "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", @@ -3070,12 +2773,6 @@ "node": ">=0.10.0" } }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "license": "MIT" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3127,12 +2824,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-sha256": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", - "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", - "license": "Unlicense" - }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -4157,15 +3848,6 @@ "dev": true, "license": "ISC" }, - "node_modules/isomorphic-ws": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", - "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", - "license": "MIT", - "peerDependencies": { - "ws": "*" - } - }, "node_modules/iterator.prototype": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", @@ -4400,12 +4082,6 @@ "dev": true, "license": "ISC" }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "license": "ISC" - }, "node_modules/maplibre-gl": { "version": "5.14.0", "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.14.0.tgz", @@ -4514,6 +4190,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, "license": "MIT" }, "node_modules/murmurhash-js": { @@ -4576,13 +4253,12 @@ "license": "MIT" }, "node_modules/next": { - "version": "14.2.28", - "resolved": "https://registry.npmjs.org/next/-/next-14.2.28.tgz", - "integrity": "sha512-QLEIP/kYXynIxtcKB6vNjtWLVs3Y4Sb+EClTC/CSVzdLD1gIuItccpu/n1lhmduffI32iPGEK2cLLxxt28qgYA==", - "deprecated": "This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/security-update-2025-12-11 for more details.", + "version": "14.2.35", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.35.tgz", + "integrity": "sha512-KhYd2Hjt/O1/1aZVX3dCwGXM1QmOV4eNM2UTacK5gipDdPN/oHHK/4oVGy7X8GMfPMsUTUEmGlsy0EY1YGAkig==", "license": "MIT", "dependencies": { - "@next/env": "14.2.28", + "@next/env": "14.2.35", "@swc/helpers": "0.5.5", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", @@ -4597,15 +4273,15 @@ "node": ">=18.17.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "14.2.28", - "@next/swc-darwin-x64": "14.2.28", - "@next/swc-linux-arm64-gnu": "14.2.28", - "@next/swc-linux-arm64-musl": "14.2.28", - "@next/swc-linux-x64-gnu": "14.2.28", - "@next/swc-linux-x64-musl": "14.2.28", - "@next/swc-win32-arm64-msvc": "14.2.28", - "@next/swc-win32-ia32-msvc": "14.2.28", - "@next/swc-win32-x64-msvc": "14.2.28" + "@next/swc-darwin-arm64": "14.2.33", + "@next/swc-darwin-x64": "14.2.33", + "@next/swc-linux-arm64-gnu": "14.2.33", + "@next/swc-linux-arm64-musl": "14.2.33", + "@next/swc-linux-x64-gnu": "14.2.33", + "@next/swc-linux-x64-musl": "14.2.33", + "@next/swc-win32-arm64-msvc": "14.2.33", + "@next/swc-win32-ia32-msvc": "14.2.33", + "@next/swc-win32-x64-msvc": "14.2.33" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -4672,21 +4348,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/node-gyp-build-optional-packages": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz", - "integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==", - "license": "MIT", - "optional": true, - "dependencies": { - "detect-libc": "^2.0.1" - }, - "bin": { - "node-gyp-build-optional-packages": "bin.js", - "node-gyp-build-optional-packages-optional": "optional.js", - "node-gyp-build-optional-packages-test": "build-test.js" - } - }, "node_modules/node-releases": { "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", @@ -5328,15 +4989,6 @@ "dev": true, "license": "MIT" }, - "node_modules/react-usestateref": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/react-usestateref/-/react-usestateref-1.0.9.tgz", - "integrity": "sha512-t8KLsI7oje0HzfzGhxFXzuwbf1z9vhBM1ptHLUIHhYqZDKFuI5tzdhEVxSNzUkYxwF8XdpOErzHlKxvP7sTERw==", - "license": "ISC", - "peerDependencies": { - "react": ">16.0.0" - } - }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -6192,12 +5844,6 @@ "node": ">=0.8" } }, - "node_modules/tiny-typed-emitter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz", - "integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==", - "license": "MIT" - }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -6285,55 +5931,6 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/ts-node/node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "license": "MIT" - }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -6461,6 +6058,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -6493,6 +6091,7 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, "license": "MIT" }, "node_modules/unrs-resolver": { @@ -6578,25 +6177,6 @@ "dev": true, "license": "MIT" }, - "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "license": "MIT" - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6820,46 +6400,6 @@ "dev": true, "license": "ISC" }, - "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xstate": { - "version": "5.25.0", - "resolved": "https://registry.npmjs.org/xstate/-/xstate-5.25.0.tgz", - "integrity": "sha512-yyWzfhVRoTHNLjLoMmdwZGagAYfmnzpm9gPjlX2MhJZsDojXGqRxODDOi4BsgGRKD46NZRAdcLp6CKOyvQS0Bw==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/xstate" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index fbb5f9a..56ef54f 100644 --- a/package.json +++ b/package.json @@ -11,11 +11,6 @@ "type-check": "tsc --noEmit" }, "dependencies": { - "@automerge/automerge": "^2.2.8", - "@automerge/automerge-repo": "^1.2.1", - "@automerge/automerge-repo-network-websocket": "^1.2.1", - "@automerge/automerge-repo-react-hooks": "^1.2.1", - "@automerge/automerge-repo-storage-indexeddb": "^1.2.1", "maplibre-gl": "^5.0.0", "nanoid": "^5.0.9", "next": "^14.2.28", diff --git a/src/hooks/useRoom.ts b/src/hooks/useRoom.ts index 9de8b45..231f633 100644 --- a/src/hooks/useRoom.ts +++ b/src/hooks/useRoom.ts @@ -1,18 +1,16 @@ 'use client'; import { useState, useEffect, useCallback, useRef } from 'react'; -import { DocHandle } from '@automerge/automerge-repo'; import { nanoid } from 'nanoid'; import { - findOrCreateRoom, - addParticipant, - removeParticipant, - updateParticipantLocation, - updateParticipantStatus, - addWaypoint as addWaypointToDoc, - removeWaypoint as removeWaypointFromDoc, - type RoomDocument, -} from '@/lib/automerge'; + RoomSync, + stateToParticipant, + stateToWaypoint, + type ParticipantState, + type LocationState, + type WaypointState, + type RoomState, +} from '@/lib/sync'; import type { Participant, ParticipantLocation, Waypoint } from '@/types'; // Color palette for participants @@ -58,138 +56,72 @@ export function useRoom({ slug, userName, userEmoji }: UseRoomOptions): UseRoomR const [waypoints, setWaypoints] = useState([]); const [roomName, setRoomName] = useState(slug); - const handleRef = useRef | null>(null); - const participantIdRef = useRef(null); + const syncRef = useRef(null); + const participantIdRef = useRef(nanoid()); - // Convert document participants to typed Participant array - const docToParticipants = useCallback((doc: RoomDocument): Participant[] => { - return Object.values(doc.participants).map((p) => ({ - id: p.id, - name: p.name, - emoji: p.emoji, - color: p.color, - joinedAt: new Date(p.joinedAt), - lastSeen: new Date(p.lastSeen), - status: p.status as Participant['status'], - location: p.location - ? { - ...p.location, - timestamp: new Date(p.location.timestamp), - source: p.location.source as ParticipantLocation['source'], - } - : undefined, - privacySettings: { - ...p.privacySettings, - defaultPrecision: p.privacySettings.defaultPrecision as Participant['privacySettings']['defaultPrecision'], - }, - })); - }, []); + // Handle state updates from sync + const handleStateChange = useCallback((state: RoomState) => { + setParticipants(Object.values(state.participants).map(stateToParticipant)); + setWaypoints(state.waypoints.map(stateToWaypoint)); + setRoomName(state.name || slug); + }, [slug]); - // Convert document waypoints to typed Waypoint array - const docToWaypoints = useCallback((doc: RoomDocument): Waypoint[] => { - return doc.waypoints.map((w) => ({ - id: w.id, - name: w.name, - emoji: w.emoji, - location: { - latitude: w.location.latitude, - longitude: w.location.longitude, - indoor: w.location.indoor, - }, - createdBy: w.createdBy, - createdAt: new Date(w.createdAt), - type: w.type as Waypoint['type'], - })); + // Handle connection changes + const handleConnectionChange = useCallback((connected: boolean) => { + setIsConnected(connected); }, []); // Initialize room connection useEffect(() => { - let mounted = true; + if (!userName) return; - async function init() { - try { - setIsLoading(true); - setError(null); + setIsLoading(true); + setError(null); - const participantId = nanoid(); - participantIdRef.current = participantId; + const participantId = participantIdRef.current; + const color = COLORS[Math.floor(Math.random() * COLORS.length)]; - const color = COLORS[Math.floor(Math.random() * COLORS.length)]; + // Create sync instance + const sync = new RoomSync( + slug, + participantId, + handleStateChange, + handleConnectionChange + ); + syncRef.current = sync; - const handle = await findOrCreateRoom( - slug, - participantId, - userName, - userEmoji, - color - ); + // Create participant state + const participant: ParticipantState = { + id: participantId, + name: userName, + emoji: userEmoji, + color, + joinedAt: new Date().toISOString(), + lastSeen: new Date().toISOString(), + status: 'online', + }; - if (!mounted) return; + // Join room + sync.join(participant); - handleRef.current = handle; + // Connect to sync server (if available) + // For now, runs in local-only mode + const syncUrl = process.env.NEXT_PUBLIC_SYNC_URL; + sync.connect(syncUrl); - // Add this participant if not already in the room - const doc = handle.docSync(); - if (doc && !doc.participants[participantId]) { - addParticipant(handle, { - id: participantId, - name: userName, - emoji: userEmoji, - color, - joinedAt: new Date().toISOString(), - lastSeen: new Date().toISOString(), - status: 'online', - privacySettings: { - sharingEnabled: true, - defaultPrecision: 'exact', - showIndoorFloor: true, - ghostMode: false, - }, - }); - } - - // Subscribe to changes - handle.on('change', ({ doc }) => { - if (!mounted || !doc) return; - setParticipants(docToParticipants(doc)); - setWaypoints(docToWaypoints(doc)); - setRoomName(doc.name || slug); - }); - - // Initial state - const initialDoc = handle.docSync(); - if (initialDoc) { - setParticipants(docToParticipants(initialDoc)); - setWaypoints(docToWaypoints(initialDoc)); - setRoomName(initialDoc.name || slug); - } - - setIsConnected(true); - setIsLoading(false); - } catch (e) { - if (!mounted) return; - console.error('Failed to connect to room:', e); - setError('Failed to connect to room'); - setIsLoading(false); - } - } - - init(); + setIsLoading(false); return () => { - mounted = false; - // Leave room on unmount - if (handleRef.current && participantIdRef.current) { - updateParticipantStatus(handleRef.current, participantIdRef.current, 'offline'); - } + sync.leave(); + syncRef.current = null; }; - }, [slug, userName, userEmoji, docToParticipants, docToWaypoints]); + }, [slug, userName, userEmoji, handleStateChange, handleConnectionChange]); // Update location const updateLocation = useCallback((location: ParticipantLocation) => { - if (!handleRef.current || !participantIdRef.current) return; + if (!syncRef.current) return; - updateParticipantLocation(handleRef.current, participantIdRef.current, { + const locationState: LocationState = { latitude: location.latitude, longitude: location.longitude, accuracy: location.accuracy, @@ -199,49 +131,50 @@ export function useRoom({ slug, userName, userEmoji }: UseRoomOptions): UseRoomR timestamp: location.timestamp.toISOString(), source: location.source, indoor: location.indoor, - }); + }; + + syncRef.current.updateLocation(locationState); }, []); // Set status const setStatus = useCallback((status: Participant['status']) => { - if (!handleRef.current || !participantIdRef.current) return; - updateParticipantStatus(handleRef.current, participantIdRef.current, status); + if (!syncRef.current) return; + syncRef.current.updateStatus(status); }, []); // Add waypoint const addWaypoint = useCallback( (waypoint: Omit) => { - if (!handleRef.current || !participantIdRef.current) return; + if (!syncRef.current) return; - addWaypointToDoc(handleRef.current, { + const waypointState: WaypointState = { id: nanoid(), name: waypoint.name, emoji: waypoint.emoji, - location: { - latitude: waypoint.location.latitude, - longitude: waypoint.location.longitude, - indoor: waypoint.location.indoor, - }, + latitude: waypoint.location.latitude, + longitude: waypoint.location.longitude, + indoor: waypoint.location.indoor, createdBy: participantIdRef.current, createdAt: new Date().toISOString(), type: waypoint.type, - }); + }; + + syncRef.current.addWaypoint(waypointState); }, [] ); // Remove waypoint const removeWaypoint = useCallback((waypointId: string) => { - if (!handleRef.current) return; - removeWaypointFromDoc(handleRef.current, waypointId); + if (!syncRef.current) return; + syncRef.current.removeWaypoint(waypointId); }, []); // Leave room const leave = useCallback(() => { - if (!handleRef.current || !participantIdRef.current) return; - removeParticipant(handleRef.current, participantIdRef.current); - handleRef.current = null; - participantIdRef.current = null; + if (!syncRef.current) return; + syncRef.current.leave(); + syncRef.current = null; setIsConnected(false); }, []); diff --git a/src/lib/automerge.ts b/src/lib/automerge.ts deleted file mode 100644 index 2d584ad..0000000 --- a/src/lib/automerge.ts +++ /dev/null @@ -1,298 +0,0 @@ -/** - * Automerge sync setup for real-time room collaboration - * - * Each room is an Automerge document containing: - * - Room metadata (name, settings) - * - Participants map (id -> participant data) - * - Waypoints array - * - * Documents sync via WebSocket to a relay server or P2P - */ - -import { Repo, DocHandle } from '@automerge/automerge-repo'; -import { BrowserWebSocketClientAdapter } from '@automerge/automerge-repo-network-websocket'; -import { IndexedDBStorageAdapter } from '@automerge/automerge-repo-storage-indexeddb'; -import type { AutomergeUrl } from '@automerge/automerge-repo'; - -// Room document schema (Automerge-compatible) -export interface RoomDocument { - id: string; - slug: string; - name: string; - createdAt: string; - createdBy: string; - settings: { - maxParticipants: number; - defaultPrecision: string; - allowGuestJoin: boolean; - showC3NavIndoor: boolean; - eventId?: string; - }; - participants: { - [id: string]: { - id: string; - name: string; - emoji: string; - color: string; - joinedAt: string; - lastSeen: string; - status: string; - location?: { - latitude: number; - longitude: number; - accuracy: number; - altitude?: number; - heading?: number; - speed?: number; - timestamp: string; - source: string; - indoor?: { - level: number; - x: number; - y: number; - spaceName?: string; - }; - }; - privacySettings: { - sharingEnabled: boolean; - defaultPrecision: string; - showIndoorFloor: boolean; - ghostMode: boolean; - }; - }; - }; - waypoints: Array<{ - id: string; - name: string; - emoji?: string; - location: { - latitude: number; - longitude: number; - indoor?: { - level: number; - x: number; - y: number; - }; - }; - createdBy: string; - createdAt: string; - type: string; - }>; -} - -// Singleton repo instance -let repoInstance: Repo | null = null; - -// Default sync server URL (can be overridden) -const DEFAULT_SYNC_URL = - process.env.NEXT_PUBLIC_AUTOMERGE_SYNC_URL || 'wss://sync.automerge.org'; - -/** - * Get or create the Automerge repo instance - */ -export function getRepo(): Repo { - if (repoInstance) { - return repoInstance; - } - - // Create network adapter (WebSocket) - const network = new BrowserWebSocketClientAdapter(DEFAULT_SYNC_URL); - - // Create storage adapter (IndexedDB for persistence) - const storage = new IndexedDBStorageAdapter('rmaps-automerge'); - - // Create repo - repoInstance = new Repo({ - network: [network], - storage, - }); - - return repoInstance; -} - -/** - * Create a new room document - */ -export function createRoom( - slug: string, - creatorId: string, - creatorName: string, - creatorEmoji: string, - creatorColor: string -): DocHandle { - const repo = getRepo(); - - const initialDoc: RoomDocument = { - id: crypto.randomUUID(), - slug, - name: slug, - createdAt: new Date().toISOString(), - createdBy: creatorId, - settings: { - maxParticipants: 10, - defaultPrecision: 'exact', - allowGuestJoin: true, - showC3NavIndoor: true, - }, - participants: { - [creatorId]: { - id: creatorId, - name: creatorName, - emoji: creatorEmoji, - color: creatorColor, - joinedAt: new Date().toISOString(), - lastSeen: new Date().toISOString(), - status: 'online', - privacySettings: { - sharingEnabled: true, - defaultPrecision: 'exact', - showIndoorFloor: true, - ghostMode: false, - }, - }, - }, - waypoints: [], - }; - - const handle = repo.create(); - handle.change((doc) => { - Object.assign(doc, initialDoc); - }); - - return handle; -} - -/** - * Join an existing room by URL - */ -export function joinRoom(url: AutomergeUrl): DocHandle { - const repo = getRepo(); - return repo.find(url); -} - -/** - * Get a room document handle by slug - * Uses a deterministic URL based on slug for discoverability - */ -export async function findOrCreateRoom( - slug: string, - creatorId: string, - creatorName: string, - creatorEmoji: string, - creatorColor: string -): Promise> { - const repo = getRepo(); - - // For now, create a new document each time - // In production, you'd use a discovery service or deterministic URLs - // based on the slug to find existing rooms - - // Store room URL mapping in localStorage for reconnection - const storedUrl = localStorage.getItem(`rmaps_room_${slug}`); - - if (storedUrl) { - try { - const handle = repo.find(storedUrl as AutomergeUrl); - // Wait for initial sync - await handle.whenReady(); - const doc = handle.docSync(); - if (doc) { - // Room exists, join it - return handle; - } - } catch (e) { - console.warn('Failed to load stored room, creating new:', e); - } - } - - // Create new room - const handle = createRoom(slug, creatorId, creatorName, creatorEmoji, creatorColor); - - // Store URL for future reconnection - localStorage.setItem(`rmaps_room_${slug}`, handle.url); - - return handle; -} - -/** - * Update participant location in a room document - */ -export function updateParticipantLocation( - handle: DocHandle, - participantId: string, - location: RoomDocument['participants'][string]['location'] -): void { - handle.change((doc) => { - if (doc.participants[participantId]) { - doc.participants[participantId].location = location; - doc.participants[participantId].lastSeen = new Date().toISOString(); - } - }); -} - -/** - * Update participant status - */ -export function updateParticipantStatus( - handle: DocHandle, - participantId: string, - status: string -): void { - handle.change((doc) => { - if (doc.participants[participantId]) { - doc.participants[participantId].status = status; - doc.participants[participantId].lastSeen = new Date().toISOString(); - } - }); -} - -/** - * Add a participant to the room - */ -export function addParticipant( - handle: DocHandle, - participant: RoomDocument['participants'][string] -): void { - handle.change((doc) => { - doc.participants[participant.id] = participant; - }); -} - -/** - * Remove a participant from the room - */ -export function removeParticipant( - handle: DocHandle, - participantId: string -): void { - handle.change((doc) => { - delete doc.participants[participantId]; - }); -} - -/** - * Add a waypoint to the room - */ -export function addWaypoint( - handle: DocHandle, - waypoint: RoomDocument['waypoints'][number] -): void { - handle.change((doc) => { - doc.waypoints.push(waypoint); - }); -} - -/** - * Remove a waypoint from the room - */ -export function removeWaypoint( - handle: DocHandle, - waypointId: string -): void { - handle.change((doc) => { - const index = doc.waypoints.findIndex((w) => w.id === waypointId); - if (index !== -1) { - doc.waypoints.splice(index, 1); - } - }); -} diff --git a/src/lib/sync.ts b/src/lib/sync.ts new file mode 100644 index 0000000..e6b1c6b --- /dev/null +++ b/src/lib/sync.ts @@ -0,0 +1,353 @@ +/** + * Simple WebSocket-based room sync + * + * This is a lightweight sync layer that works without WASM dependencies. + * Can be replaced with Automerge later when we have a proper sync server. + * + * Architecture: + * - Each room is a shared state object + * - Changes are broadcast via WebSocket to all participants + * - State is stored in localStorage for reconnection + * - Falls back to local-only mode if WebSocket unavailable + */ + +import type { Participant, ParticipantLocation, Waypoint } from '@/types'; + +// Room state that gets synced +export interface RoomState { + id: string; + slug: string; + name: string; + createdAt: string; + participants: Record; + waypoints: WaypointState[]; +} + +export interface ParticipantState { + id: string; + name: string; + emoji: string; + color: string; + joinedAt: string; + lastSeen: string; + status: string; + location?: LocationState; +} + +export interface LocationState { + latitude: number; + longitude: number; + accuracy: number; + altitude?: number; + heading?: number; + speed?: number; + timestamp: string; + source: string; + indoor?: { + level: number; + x: number; + y: number; + spaceName?: string; + }; +} + +export interface WaypointState { + id: string; + name: string; + emoji?: string; + latitude: number; + longitude: number; + indoor?: { + level: number; + x: number; + y: number; + }; + createdBy: string; + createdAt: string; + type: string; +} + +// Message types for sync +export type SyncMessage = + | { type: 'join'; participant: ParticipantState } + | { type: 'leave'; participantId: string } + | { type: 'location'; participantId: string; location: LocationState } + | { type: 'status'; participantId: string; status: string } + | { type: 'waypoint_add'; waypoint: WaypointState } + | { type: 'waypoint_remove'; waypointId: string } + | { type: 'full_state'; state: RoomState } + | { type: 'request_state' }; + +type SyncCallback = (state: RoomState) => void; +type ConnectionCallback = (connected: boolean) => void; + +export class RoomSync { + private slug: string; + private state: RoomState; + private ws: WebSocket | null = null; + private reconnectTimer: ReturnType | null = null; + private onStateChange: SyncCallback; + private onConnectionChange: ConnectionCallback; + private participantId: string; + + constructor( + slug: string, + participantId: string, + onStateChange: SyncCallback, + onConnectionChange: ConnectionCallback + ) { + this.slug = slug; + this.participantId = participantId; + this.onStateChange = onStateChange; + this.onConnectionChange = onConnectionChange; + + // Initialize or load state + this.state = this.loadState() || this.createInitialState(); + } + + private createInitialState(): RoomState { + return { + id: crypto.randomUUID(), + slug: this.slug, + name: this.slug, + createdAt: new Date().toISOString(), + participants: {}, + waypoints: [], + }; + } + + private loadState(): RoomState | null { + try { + const stored = localStorage.getItem(`rmaps_room_${this.slug}`); + if (stored) { + return JSON.parse(stored); + } + } catch (e) { + console.warn('Failed to load room state:', e); + } + return null; + } + + private saveState(): void { + try { + localStorage.setItem(`rmaps_room_${this.slug}`, JSON.stringify(this.state)); + } catch (e) { + console.warn('Failed to save room state:', e); + } + } + + private notifyStateChange(): void { + this.saveState(); + this.onStateChange({ ...this.state }); + } + + // Connect to sync server (when available) + connect(syncUrl?: string): void { + if (!syncUrl) { + // No sync server - local only mode + console.log('Running in local-only mode (no sync server)'); + this.onConnectionChange(true); + return; + } + + try { + this.ws = new WebSocket(`${syncUrl}/room/${this.slug}`); + + this.ws.onopen = () => { + console.log('Connected to sync server'); + this.onConnectionChange(true); + // Request current state from server + this.send({ type: 'request_state' }); + }; + + this.ws.onmessage = (event) => { + try { + const message: SyncMessage = JSON.parse(event.data); + this.handleMessage(message); + } catch (e) { + console.warn('Invalid sync message:', e); + } + }; + + this.ws.onclose = () => { + console.log('Disconnected from sync server'); + this.onConnectionChange(false); + this.scheduleReconnect(syncUrl); + }; + + this.ws.onerror = (error) => { + console.error('WebSocket error:', error); + }; + } catch (e) { + console.error('Failed to connect to sync server:', e); + this.onConnectionChange(false); + } + } + + private scheduleReconnect(syncUrl: string): void { + if (this.reconnectTimer) { + clearTimeout(this.reconnectTimer); + } + this.reconnectTimer = setTimeout(() => { + console.log('Attempting to reconnect...'); + this.connect(syncUrl); + }, 5000); + } + + private send(message: SyncMessage): void { + if (this.ws?.readyState === WebSocket.OPEN) { + this.ws.send(JSON.stringify(message)); + } + } + + private handleMessage(message: SyncMessage): void { + switch (message.type) { + case 'full_state': + // Merge with local state, keeping our participant + const myParticipant = this.state.participants[this.participantId]; + this.state = message.state; + if (myParticipant) { + this.state.participants[this.participantId] = myParticipant; + } + break; + + case 'join': + this.state.participants[message.participant.id] = message.participant; + break; + + case 'leave': + delete this.state.participants[message.participantId]; + break; + + case 'location': + if (this.state.participants[message.participantId]) { + this.state.participants[message.participantId].location = message.location; + this.state.participants[message.participantId].lastSeen = new Date().toISOString(); + } + break; + + case 'status': + if (this.state.participants[message.participantId]) { + this.state.participants[message.participantId].status = message.status; + this.state.participants[message.participantId].lastSeen = new Date().toISOString(); + } + break; + + case 'waypoint_add': + this.state.waypoints.push(message.waypoint); + break; + + case 'waypoint_remove': + this.state.waypoints = this.state.waypoints.filter( + (w) => w.id !== message.waypointId + ); + break; + } + + this.notifyStateChange(); + } + + // Public methods for updating state + join(participant: ParticipantState): void { + this.state.participants[participant.id] = participant; + this.send({ type: 'join', participant }); + this.notifyStateChange(); + } + + leave(): void { + delete this.state.participants[this.participantId]; + this.send({ type: 'leave', participantId: this.participantId }); + this.notifyStateChange(); + + if (this.ws) { + this.ws.close(); + this.ws = null; + } + if (this.reconnectTimer) { + clearTimeout(this.reconnectTimer); + } + } + + updateLocation(location: LocationState): void { + if (this.state.participants[this.participantId]) { + this.state.participants[this.participantId].location = location; + this.state.participants[this.participantId].lastSeen = new Date().toISOString(); + this.send({ type: 'location', participantId: this.participantId, location }); + this.notifyStateChange(); + } + } + + updateStatus(status: string): void { + if (this.state.participants[this.participantId]) { + this.state.participants[this.participantId].status = status; + this.state.participants[this.participantId].lastSeen = new Date().toISOString(); + this.send({ type: 'status', participantId: this.participantId, status }); + this.notifyStateChange(); + } + } + + addWaypoint(waypoint: WaypointState): void { + this.state.waypoints.push(waypoint); + this.send({ type: 'waypoint_add', waypoint }); + this.notifyStateChange(); + } + + removeWaypoint(waypointId: string): void { + this.state.waypoints = this.state.waypoints.filter((w) => w.id !== waypointId); + this.send({ type: 'waypoint_remove', waypointId }); + this.notifyStateChange(); + } + + getState(): RoomState { + return { ...this.state }; + } +} + +// Convert sync state to typed Participant +export function stateToParticipant(state: ParticipantState): Participant { + return { + id: state.id, + name: state.name, + emoji: state.emoji, + color: state.color, + joinedAt: new Date(state.joinedAt), + lastSeen: new Date(state.lastSeen), + status: state.status as Participant['status'], + location: state.location + ? { + latitude: state.location.latitude, + longitude: state.location.longitude, + accuracy: state.location.accuracy, + altitude: state.location.altitude, + heading: state.location.heading, + speed: state.location.speed, + timestamp: new Date(state.location.timestamp), + source: state.location.source as ParticipantLocation['source'], + indoor: state.location.indoor, + } + : undefined, + privacySettings: { + sharingEnabled: true, + defaultPrecision: 'exact', + showIndoorFloor: true, + ghostMode: false, + }, + }; +} + +// Convert sync state to typed Waypoint +export function stateToWaypoint(state: WaypointState): Waypoint { + return { + id: state.id, + name: state.name, + emoji: state.emoji, + location: { + latitude: state.latitude, + longitude: state.longitude, + indoor: state.indoor, + }, + createdBy: state.createdBy, + createdAt: new Date(state.createdAt), + type: state.type as Waypoint['type'], + }; +}