Replace Automerge with simple WebSocket sync
- Remove Automerge dependencies (WASM incompatible with Next.js) - Add lightweight WebSocket-based sync layer - Works in local-only mode until sync server deployed - State persisted in localStorage for reconnection Ready for deployment - sync server can be added later. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
530979978d
commit
23cbe6caa8
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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<Waypoint[]>([]);
|
||||
const [roomName, setRoomName] = useState(slug);
|
||||
|
||||
const handleRef = useRef<DocHandle<RoomDocument> | null>(null);
|
||||
const participantIdRef = useRef<string | null>(null);
|
||||
const syncRef = useRef<RoomSync | null>(null);
|
||||
const participantIdRef = useRef<string>(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<Waypoint, 'id' | 'createdAt' | 'createdBy'>) => {
|
||||
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);
|
||||
}, []);
|
||||
|
||||
|
|
|
|||
|
|
@ -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<RoomDocument> {
|
||||
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<RoomDocument>();
|
||||
handle.change((doc) => {
|
||||
Object.assign(doc, initialDoc);
|
||||
});
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Join an existing room by URL
|
||||
*/
|
||||
export function joinRoom(url: AutomergeUrl): DocHandle<RoomDocument> {
|
||||
const repo = getRepo();
|
||||
return repo.find<RoomDocument>(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<DocHandle<RoomDocument>> {
|
||||
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<RoomDocument>(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<RoomDocument>,
|
||||
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<RoomDocument>,
|
||||
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<RoomDocument>,
|
||||
participant: RoomDocument['participants'][string]
|
||||
): void {
|
||||
handle.change((doc) => {
|
||||
doc.participants[participant.id] = participant;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a participant from the room
|
||||
*/
|
||||
export function removeParticipant(
|
||||
handle: DocHandle<RoomDocument>,
|
||||
participantId: string
|
||||
): void {
|
||||
handle.change((doc) => {
|
||||
delete doc.participants[participantId];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a waypoint to the room
|
||||
*/
|
||||
export function addWaypoint(
|
||||
handle: DocHandle<RoomDocument>,
|
||||
waypoint: RoomDocument['waypoints'][number]
|
||||
): void {
|
||||
handle.change((doc) => {
|
||||
doc.waypoints.push(waypoint);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a waypoint from the room
|
||||
*/
|
||||
export function removeWaypoint(
|
||||
handle: DocHandle<RoomDocument>,
|
||||
waypointId: string
|
||||
): void {
|
||||
handle.change((doc) => {
|
||||
const index = doc.waypoints.findIndex((w) => w.id === waypointId);
|
||||
if (index !== -1) {
|
||||
doc.waypoints.splice(index, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -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<string, ParticipantState>;
|
||||
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<typeof setTimeout> | 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'],
|
||||
};
|
||||
}
|
||||
Loading…
Reference in New Issue