From d257221dc86c04be82b173f3d41992f53dea9546 Mon Sep 17 00:00:00 2001 From: Steffen Beyer Date: Mon, 25 May 2026 05:47:49 +0200 Subject: [PATCH] feat: scaffold marmot chat backend Pin marmot-ts 0.5.1, expose Marmot chat routes/configuration, and keep the web UI disabled until transport, storage, and signing adapters are implemented. --- assets/package-lock.json | 465 +++++++++++++++++++++++++++- assets/package.json | 3 + lib/aether/chat.ex | 3 + lib/aether_web/live/chat_live.ex | 89 ++++-- lib/tribes/plugins/aether/plugin.ex | 38 ++- test/aether/chat_page_test.exs | 10 + test/aether/marmot_assets_test.exs | 11 + 7 files changed, 590 insertions(+), 29 deletions(-) create mode 100644 test/aether/marmot_assets_test.exs diff --git a/assets/package-lock.json b/assets/package-lock.json index 98452c0..1a42249 100644 --- a/assets/package-lock.json +++ b/assets/package-lock.json @@ -6,7 +6,470 @@ "packages": { "": { "name": "aether-assets", - "version": "0.1.0" + "version": "0.1.0", + "dependencies": { + "@internet-privacy/marmot-ts": "0.5.1" + } + }, + "node_modules/@hpke/common": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@hpke/common/-/common-1.10.1.tgz", + "integrity": "sha512-moJwhmtLtuxiUzzNp1jpfBfx8yefKoO9D/RCR9dmwrnc7qjJqId1rEtQz+lSlU5cabX8daToMSx/7HayXOiaFw==", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@hpke/core": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@hpke/core/-/core-1.9.0.tgz", + "integrity": "sha512-pFxWl1nNJeQCSUFs7+GAblHvXBCjn9EPN65vdKlYQil2aURaRxfGMO6vBKGqm1YHTKwiAxJQNEI70PbSowMP9Q==", + "license": "MIT", + "dependencies": { + "@hpke/common": "^1.10.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@internet-privacy/marmot-ts": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@internet-privacy/marmot-ts/-/marmot-ts-0.5.1.tgz", + "integrity": "sha512-AZ9924Dz75CHbB+YJPmEGj09xYIYAHpyDCq1xn++ubQjP95sQuV4yNXX5CmPQNXcyEMd50LL5dhJOMuH8gmPsg==", + "license": "MIT", + "dependencies": { + "@hpke/core": "^1.9.0", + "@noble/ciphers": "^2.1.1", + "@noble/curves": "^2.0.1", + "@noble/hashes": "^2.0.1", + "@scure/base": "^2.0.0", + "applesauce-common": "^5.1.0", + "applesauce-core": "^5.1.0", + "debug": "^4.4.3", + "eventemitter3": "^5.0.4", + "ts-mls": "2.0.0-rc.10" + }, + "engines": { + "bun": ">=1.1.0", + "deno": ">=2.0.0", + "node": ">=20.0.0" + } + }, + "node_modules/@noble/ciphers": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.1.1.tgz", + "integrity": "sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.0.1.tgz", + "integrity": "sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "2.0.1" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", + "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", + "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-2.2.0.tgz", + "integrity": "sha512-b8XEupJibegiXV+tDUseI8oLQc8ei3d/4Jkb2RpbHh3MfE054ov3uIz2dhFkB3FI8iwYkEh0gGCApkrYggkPNg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.1.tgz", + "integrity": "sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.1.0", + "@noble/hashes": "~1.3.1", + "@scure/base": "~1.1.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/curves": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz", + "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.1" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", + "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/hashes": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@scure/base": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", + "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz", + "integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.3.0", + "@scure/base": "~1.1.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39/node_modules/@noble/hashes": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39/node_modules/@scure/base": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", + "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/applesauce-common": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/applesauce-common/-/applesauce-common-5.2.0.tgz", + "integrity": "sha512-6Natb0szkj65OBKK5LBHIszAmVd8ha9GkCZcavJnZbNeWBgoDTO0hfkgI4pk2L6L/OWNceo2XCajvDAx0AjlgQ==", + "license": "MIT", + "dependencies": { + "@scure/base": "^1.2.4", + "applesauce-core": "^5.2.0", + "hash-sum": "^2.0.0", + "light-bolt11-decoder": "^3.2.0", + "rxjs": "^7.8.1" + }, + "funding": { + "type": "lightning", + "url": "lightning:nostrudel@geyser.fund" + } + }, + "node_modules/applesauce-common/node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/applesauce-core": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/applesauce-core/-/applesauce-core-5.2.0.tgz", + "integrity": "sha512-aSuM6q6/Gs2FGUqytlHDjKZpSst2xKaT0vMXUQFWUctECNIxvwy6/hTDDInukMuI9mrQdjnO781ZJJgghI7RNw==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "fast-deep-equal": "^3.1.3", + "hash-sum": "^2.0.0", + "nanoid": "^5.0.9", + "nostr-tools": "~2.19", + "rxjs": "^7.8.1" + }, + "funding": { + "type": "lightning", + "url": "lightning:nostrudel@geyser.fund" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "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", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/hash-sum": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz", + "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==", + "license": "MIT" + }, + "node_modules/light-bolt11-decoder": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/light-bolt11-decoder/-/light-bolt11-decoder-3.2.0.tgz", + "integrity": "sha512-3QEofgiBOP4Ehs9BI+RkZdXZNtSys0nsJ6fyGeSiAGCBsMwHGUDS/JQlY/sTnWs91A2Nh0S9XXfA8Sy9g6QpuQ==", + "license": "MIT", + "dependencies": { + "@scure/base": "1.1.1" + } + }, + "node_modules/light-bolt11-decoder/node_modules/@scure/base": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.11.tgz", + "integrity": "sha512-v+KEsUv2ps74PaSKv0gHTxTCgMXOIfBEbaqa6w6ISIGC7ZsvHN4N9oJ8d4cmf0n5oTzQz2SLmThbQWhjd/8eKg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/nostr-tools": { + "version": "2.19.4", + "resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.19.4.tgz", + "integrity": "sha512-qVLfoTpZegNYRJo5j+Oi6RPu0AwLP6jcvzcB3ySMnIT5DrAGNXfs5HNBspB/2HiGfH3GY+v6yXkTtcKSBQZwSg==", + "license": "Unlicense", + "dependencies": { + "@noble/ciphers": "^0.5.1", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.1", + "@scure/base": "1.1.1", + "@scure/bip32": "1.3.1", + "@scure/bip39": "1.2.1", + "nostr-wasm": "0.1.0" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/nostr-tools/node_modules/@noble/ciphers": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.5.3.tgz", + "integrity": "sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/nostr-tools/node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/nostr-tools/node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/nostr-tools/node_modules/@noble/hashes": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", + "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/nostr-tools/node_modules/@scure/base": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, + "node_modules/nostr-wasm": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/nostr-wasm/-/nostr-wasm-0.1.0.tgz", + "integrity": "sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA==", + "license": "MIT" + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/ts-mls": { + "version": "2.0.0-rc.10", + "resolved": "https://registry.npmjs.org/ts-mls/-/ts-mls-2.0.0-rc.10.tgz", + "integrity": "sha512-4FFbkysQkJlVaUv4fs7ZC4wuiwTOCgX/bEMo2fFGngy/QofC/lvWWgsScKuGxEIhEf4e2Q8APfJ7DknP0VRCoA==", + "license": "MIT", + "dependencies": { + "@hpke/core": "1.9.0" + }, + "peerDependencies": { + "@hpke/chacha20poly1305": "1.8.0", + "@hpke/dhkem-x448": "1.8.0", + "@hpke/hybridkem-x-wing": "0.7.0", + "@hpke/ml-kem": "0.3.0", + "@noble/ciphers": "2.1.1", + "@noble/curves": "2.0.1", + "@noble/post-quantum": "0.5.2" + }, + "peerDependenciesMeta": { + "@hpke/chacha20poly1305": { + "optional": true + }, + "@hpke/dhkem-x448": { + "optional": true + }, + "@hpke/hybridkem-x-wing": { + "optional": true + }, + "@hpke/ml-kem": { + "optional": true + }, + "@noble/curves": { + "optional": true + }, + "@noble/post-quantum": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" } } } diff --git a/assets/package.json b/assets/package.json index 5478114..8d360ff 100644 --- a/assets/package.json +++ b/assets/package.json @@ -4,5 +4,8 @@ "version": "0.1.0", "scripts": { "build": "mkdir -p ../priv/static && cp -r css/. ../priv/static && cp -r js/. ../priv/static" + }, + "dependencies": { + "@internet-privacy/marmot-ts": "0.5.1" } } diff --git a/lib/aether/chat.ex b/lib/aether/chat.ex index 4a04d8e..6aa87dd 100644 --- a/lib/aether/chat.ex +++ b/lib/aether/chat.ex @@ -41,6 +41,9 @@ defmodule Aether.Chat do def embed_path(%Channel{slug: slug}), do: embed_path(slug) def embed_path(slug) when is_binary(slug), do: "/aether/chat/embed/" <> slug + def marmot_path(%Channel{slug: slug}), do: marmot_path(slug) + def marmot_path(slug) when is_binary(slug), do: "/aether/chat/marmot/" <> slug + def ensure_context_channel(provider, type, id, attrs \\ %{}, opts \\ []) when is_binary(provider) and is_binary(type) and is_binary(id) and is_map(attrs) do attrs diff --git a/lib/aether_web/live/chat_live.ex b/lib/aether_web/live/chat_live.ex index 83b72fd..d4e3fb5 100644 --- a/lib/aether_web/live/chat_live.ex +++ b/lib/aether_web/live/chat_live.ex @@ -9,9 +9,9 @@ defmodule AetherWeb.ChatLive do @impl true def mount(_params, session, socket) do - {mode, slug} = channel_request(session) + %{mode: mode, slug: slug, backend: backend} = channel_request(session) - case Chat.ensure_channel(%{slug: slug, title: channel_title(slug)}) do + case Chat.ensure_channel(%{slug: slug, title: channel_title(slug), backend: backend}) do {:ok, %Channel{} = channel} -> messages = load_messages(channel) message_ids = messages |> Enum.map(& &1.id) |> MapSet.new() @@ -21,6 +21,7 @@ defmodule AetherWeb.ChatLive do |> assign(:page_title, channel.title) |> assign(:channel, channel) |> assign(:embedded?, mode == :embedded) + |> assign(:backend, channel.backend) |> assign(:message_ids, message_ids) |> assign(:message_count, MapSet.size(message_ids)) |> stream_configure(:messages, dom_id: &("chat-message-" <> &1.id)) @@ -38,6 +39,7 @@ defmodule AetherWeb.ChatLive do |> assign(:page_title, "Chat") |> assign(:channel, nil) |> assign(:embedded?, mode == :embedded) + |> assign(:backend, backend) |> assign(:message_ids, MapSet.new()) |> assign(:message_count, 0) |> assign(:chat_error, inspect(reason)) @@ -82,6 +84,7 @@ defmodule AetherWeb.ChatLive do channel={@channel} current_user={@current_user} embedded?={@embedded?} + backend={@backend} message_count={@message_count} streams={@streams} /> @@ -116,6 +119,7 @@ defmodule AetherWeb.ChatLive do channel={@channel} current_user={@current_user} embedded?={@embedded?} + backend={@backend} message_count={@message_count} streams={@streams} /> @@ -146,6 +150,14 @@ defmodule AetherWeb.ChatLive do {@message_count} +
+ Marmot backend scaffold is enabled for this channel. Browser transport, storage, and signing are not active yet. +
+
- <%= if @current_user do %> -
-
- - - + <%= cond do %> + <% @backend == :marmot -> %> +
+ Marmot chat is not enabled in the web UI yet. +
+ <% @current_user -> %> + +
+ + + +
+ + <% true -> %> +
+ Sign in to join the chat.
- - <% else %> -
- Sign in to join the chat. -
<% end %>
""" @@ -251,15 +268,33 @@ defmodule AetherWeb.ChatLive do defp channel_request(%{"plugin_path" => path}) when is_binary(path) do case String.split(path, "/", trim: true) do - ["aether", "chat", "embed", slug | _rest] -> {:embedded, slug} - ["plugins", "aether", "chat", "embed", slug | _rest] -> {:embedded, slug} - ["aether", "chat", slug | _rest] -> {:standalone, slug} - ["plugins", "aether", "chat", slug | _rest] -> {:standalone, slug} - _other -> {:standalone, Chat.default_channel_slug()} + ["aether", "chat", "embed", slug | _rest] -> + request(:embedded, slug, :public_sync) + + ["plugins", "aether", "chat", "embed", slug | _rest] -> + request(:embedded, slug, :public_sync) + + ["aether", "chat", "marmot", slug | _rest] -> + request(:standalone, slug, :marmot) + + ["plugins", "aether", "chat", "marmot", slug | _rest] -> + request(:standalone, slug, :marmot) + + ["aether", "chat", slug | _rest] -> + request(:standalone, slug, :public_sync) + + ["plugins", "aether", "chat", slug | _rest] -> + request(:standalone, slug, :public_sync) + + _other -> + request(:standalone, Chat.default_channel_slug(), :public_sync) end end - defp channel_request(_session), do: {:standalone, Chat.default_channel_slug()} + defp channel_request(_session), + do: request(:standalone, Chat.default_channel_slug(), :public_sync) + + defp request(mode, slug, backend), do: %{mode: mode, slug: slug, backend: backend} defp channel_title("general"), do: "General Chat" diff --git a/lib/tribes/plugins/aether/plugin.ex b/lib/tribes/plugins/aether/plugin.ex index 4a16653..e8e30d8 100644 --- a/lib/tribes/plugins/aether/plugin.ex +++ b/lib/tribes/plugins/aether/plugin.ex @@ -41,7 +41,43 @@ defmodule Tribes.Plugins.Aether.Plugin do layout: nil } ], - ash_domains: [Aether.Chat] + ash_domains: [Aether.Chat], + config_schema: %{ + title: "Aether", + description: "Social feed and chat defaults.", + groups: [ + %{ + id: "chat", + label: "Chat", + description: "Group chat backend defaults.", + order: 10, + settings: [ + %{ + key: "chat.default_backend", + label: "Default chat backend", + description: + "New standalone channels use public synced chat until Marmot is explicitly selected.", + type: :enum, + default: "public_sync", + options: [ + %{label: "Public synced", value: "public_sync"}, + %{label: "Marmot scaffold", value: "marmot"} + ], + order: 10 + }, + %{ + key: "chat.marmot_enabled", + label: "Enable Marmot chat UI", + description: + "Reserved for the Marmot browser transport once storage, relay, and signing adapters are complete.", + type: :boolean, + default: false, + order: 20 + } + ] + } + ] + } }) end end diff --git a/test/aether/chat_page_test.exs b/test/aether/chat_page_test.exs index f59edd9..1e09e89 100644 --- a/test/aether/chat_page_test.exs +++ b/test/aether/chat_page_test.exs @@ -37,6 +37,15 @@ defmodule Aether.ChatPageTest do refute has_element?(view, "#aether-chat-page") end + test "renders Marmot scaffold without public composer", %{signed_in_conn: conn} do + slug = "marmot-chat-#{System.unique_integer([:positive])}" + {:ok, view, _html} = live(conn, "/aether/chat/marmot/#{slug}") + + assert has_element?(view, "#chat-marmot-notice") + assert has_element?(view, "#chat-marmot-disabled") + refute has_element?(view, "#chat-message-form") + end + test "ensures context group channels with stable slugs" do attrs = %{ context_provider: "sender", @@ -52,6 +61,7 @@ defmodule Aether.ChatPageTest do assert first.slug == "sender-stream-stream-123" assert first.conversation_kind == :context_group assert Chat.embed_path(first) == "/aether/chat/embed/sender-stream-stream-123" + assert Chat.marmot_path(first) == "/aether/chat/marmot/sender-stream-stream-123" end test "ensures context group channels through provider helper" do diff --git a/test/aether/marmot_assets_test.exs b/test/aether/marmot_assets_test.exs new file mode 100644 index 0000000..d0ee40b --- /dev/null +++ b/test/aether/marmot_assets_test.exs @@ -0,0 +1,11 @@ +defmodule Aether.MarmotAssetsTest do + use ExUnit.Case, async: true + + @package_path Path.expand("../../assets/package.json", __DIR__) + + test "pins marmot-ts to the selected client version" do + package = @package_path |> File.read!() |> JSON.decode!() + + assert get_in(package, ["dependencies", "@internet-privacy/marmot-ts"]) == "0.5.1" + end +end