@swal/edge-mesh 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +128 -0
- package/NOTICE +37 -0
- package/README.md +470 -2
- package/dist/adapters/gpu-agent/index.d.ts +61 -0
- package/dist/adapters/gpu-agent/index.d.ts.map +1 -0
- package/dist/adapters/gpu-agent/index.js +364 -0
- package/dist/adapters/gpu-agent/index.js.map +1 -0
- package/dist/adapters/maloca-salud/citas-distribuidas.d.ts +19 -0
- package/dist/adapters/maloca-salud/citas-distribuidas.d.ts.map +1 -0
- package/dist/adapters/maloca-salud/citas-distribuidas.js +51 -0
- package/dist/adapters/maloca-salud/citas-distribuidas.js.map +1 -0
- package/dist/adapters/maloca-salud/eps-bridge.d.ts +15 -0
- package/dist/adapters/maloca-salud/eps-bridge.d.ts.map +1 -0
- package/dist/adapters/maloca-salud/eps-bridge.js +27 -0
- package/dist/adapters/maloca-salud/eps-bridge.js.map +1 -0
- package/dist/adapters/maloca-salud/patient-profile.d.ts +20 -0
- package/dist/adapters/maloca-salud/patient-profile.d.ts.map +1 -0
- package/dist/adapters/maloca-salud/patient-profile.js +36 -0
- package/dist/adapters/maloca-salud/patient-profile.js.map +1 -0
- package/dist/adapters/maloca-salud/types.d.ts +40 -0
- package/dist/adapters/maloca-salud/types.d.ts.map +1 -0
- package/dist/adapters/maloca-salud/types.js +2 -0
- package/dist/adapters/maloca-salud/types.js.map +1 -0
- package/dist/adapters/maloca-veeduria/contract-bridge.d.ts +31 -0
- package/dist/adapters/maloca-veeduria/contract-bridge.d.ts.map +1 -0
- package/dist/adapters/maloca-veeduria/contract-bridge.js +103 -0
- package/dist/adapters/maloca-veeduria/contract-bridge.js.map +1 -0
- package/dist/adapters/maloca-veeduria/evidentia-veeduria.d.ts +14 -0
- package/dist/adapters/maloca-veeduria/evidentia-veeduria.d.ts.map +1 -0
- package/dist/adapters/maloca-veeduria/evidentia-veeduria.js +40 -0
- package/dist/adapters/maloca-veeduria/evidentia-veeduria.js.map +1 -0
- package/dist/adapters/maloca-veeduria/licitante-profile.d.ts +21 -0
- package/dist/adapters/maloca-veeduria/licitante-profile.d.ts.map +1 -0
- package/dist/adapters/maloca-veeduria/licitante-profile.js +58 -0
- package/dist/adapters/maloca-veeduria/licitante-profile.js.map +1 -0
- package/dist/adapters/maloca-veeduria/types.d.ts +30 -0
- package/dist/adapters/maloca-veeduria/types.d.ts.map +1 -0
- package/dist/adapters/maloca-veeduria/types.js +2 -0
- package/dist/adapters/maloca-veeduria/types.js.map +1 -0
- package/dist/adapters/maloca-xavier/agent-profile.d.ts +32 -0
- package/dist/adapters/maloca-xavier/agent-profile.d.ts.map +1 -0
- package/dist/adapters/maloca-xavier/agent-profile.js +56 -0
- package/dist/adapters/maloca-xavier/agent-profile.js.map +1 -0
- package/dist/adapters/maloca-xavier/codegraph-bridge.d.ts +27 -0
- package/dist/adapters/maloca-xavier/codegraph-bridge.d.ts.map +1 -0
- package/dist/adapters/maloca-xavier/codegraph-bridge.js +58 -0
- package/dist/adapters/maloca-xavier/codegraph-bridge.js.map +1 -0
- package/dist/adapters/maloca-xavier/llm-router.d.ts +25 -0
- package/dist/adapters/maloca-xavier/llm-router.d.ts.map +1 -0
- package/dist/adapters/maloca-xavier/llm-router.js +58 -0
- package/dist/adapters/maloca-xavier/llm-router.js.map +1 -0
- package/dist/authz/index.d.ts +81 -0
- package/dist/authz/index.d.ts.map +1 -0
- package/dist/authz/index.js +271 -0
- package/dist/authz/index.js.map +1 -0
- package/dist/chat/index.d.ts +138 -0
- package/dist/chat/index.d.ts.map +1 -0
- package/dist/chat/index.js +394 -0
- package/dist/chat/index.js.map +1 -0
- package/dist/chat/offline-queue.d.ts +32 -0
- package/dist/chat/offline-queue.d.ts.map +1 -0
- package/dist/chat/offline-queue.js +93 -0
- package/dist/chat/offline-queue.js.map +1 -0
- package/dist/core/DefaultEdgeMeshNode.d.ts +17 -0
- package/dist/core/DefaultEdgeMeshNode.d.ts.map +1 -0
- package/dist/core/DefaultEdgeMeshNode.js +57 -0
- package/dist/core/DefaultEdgeMeshNode.js.map +1 -0
- package/dist/core/node.d.ts +26 -0
- package/dist/core/node.d.ts.map +1 -0
- package/dist/core/node.js +16 -0
- package/dist/core/node.js.map +1 -0
- package/dist/edge-mesh.d.ts +139 -0
- package/dist/edge-mesh.d.ts.map +1 -0
- package/dist/edge-mesh.js +1146 -0
- package/dist/edge-mesh.js.map +1 -0
- package/dist/gos/GosBridge.d.ts +49 -0
- package/dist/gos/GosBridge.d.ts.map +1 -0
- package/dist/gos/GosBridge.js +133 -0
- package/dist/gos/GosBridge.js.map +1 -0
- package/dist/governance/authority.d.ts +44 -0
- package/dist/governance/authority.d.ts.map +1 -0
- package/dist/governance/authority.js +123 -0
- package/dist/governance/authority.js.map +1 -0
- package/dist/governance/index.d.ts +66 -0
- package/dist/governance/index.d.ts.map +1 -0
- package/dist/governance/index.js +210 -0
- package/dist/governance/index.js.map +1 -0
- package/dist/governance/merge.d.ts +31 -0
- package/dist/governance/merge.d.ts.map +1 -0
- package/dist/governance/merge.js +167 -0
- package/dist/governance/merge.js.map +1 -0
- package/dist/identity/index.d.ts +199 -0
- package/dist/identity/index.d.ts.map +1 -0
- package/dist/identity/index.js +415 -0
- package/dist/identity/index.js.map +1 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/dist/maloca/backoffice.d.ts +31 -0
- package/dist/maloca/backoffice.d.ts.map +1 -0
- package/dist/maloca/backoffice.js +40 -0
- package/dist/maloca/backoffice.js.map +1 -0
- package/dist/maloca/event-bus.d.ts +60 -0
- package/dist/maloca/event-bus.d.ts.map +1 -0
- package/dist/maloca/event-bus.js +175 -0
- package/dist/maloca/event-bus.js.map +1 -0
- package/dist/maloca/evidentia.d.ts +136 -0
- package/dist/maloca/evidentia.d.ts.map +1 -0
- package/dist/maloca/evidentia.js +447 -0
- package/dist/maloca/evidentia.js.map +1 -0
- package/dist/maloca/gateway/api.d.ts +146 -0
- package/dist/maloca/gateway/api.d.ts.map +1 -0
- package/dist/maloca/gateway/api.js +228 -0
- package/dist/maloca/gateway/api.js.map +1 -0
- package/dist/maloca/gateway/auth.d.ts +20 -0
- package/dist/maloca/gateway/auth.d.ts.map +1 -0
- package/dist/maloca/gateway/auth.js +115 -0
- package/dist/maloca/gateway/auth.js.map +1 -0
- package/dist/maloca/gateway/dashboard.d.ts +42 -0
- package/dist/maloca/gateway/dashboard.d.ts.map +1 -0
- package/dist/maloca/gateway/dashboard.js +67 -0
- package/dist/maloca/gateway/dashboard.js.map +1 -0
- package/dist/maloca/gateway/websocket.d.ts +34 -0
- package/dist/maloca/gateway/websocket.d.ts.map +1 -0
- package/dist/maloca/gateway/websocket.js +71 -0
- package/dist/maloca/gateway/websocket.js.map +1 -0
- package/dist/maloca/karma.d.ts +165 -0
- package/dist/maloca/karma.d.ts.map +1 -0
- package/dist/maloca/karma.js +307 -0
- package/dist/maloca/karma.js.map +1 -0
- package/dist/maloca/kernel.d.ts +20 -0
- package/dist/maloca/kernel.d.ts.map +1 -0
- package/dist/maloca/kernel.js +76 -0
- package/dist/maloca/kernel.js.map +1 -0
- package/dist/maloca/metadata.d.ts +30 -0
- package/dist/maloca/metadata.d.ts.map +1 -0
- package/dist/maloca/metadata.js +80 -0
- package/dist/maloca/metadata.js.map +1 -0
- package/dist/maloca/perfil.d.ts +57 -0
- package/dist/maloca/perfil.d.ts.map +1 -0
- package/dist/maloca/perfil.js +113 -0
- package/dist/maloca/perfil.js.map +1 -0
- package/dist/maloca/plugin-registry.d.ts +27 -0
- package/dist/maloca/plugin-registry.d.ts.map +1 -0
- package/dist/maloca/plugin-registry.js +76 -0
- package/dist/maloca/plugin-registry.js.map +1 -0
- package/dist/maloca/polygon-bridge.d.ts +63 -0
- package/dist/maloca/polygon-bridge.d.ts.map +1 -0
- package/dist/maloca/polygon-bridge.js +198 -0
- package/dist/maloca/polygon-bridge.js.map +1 -0
- package/dist/maloca/types.d.ts +44 -0
- package/dist/maloca/types.d.ts.map +1 -0
- package/dist/maloca/types.js +2 -0
- package/dist/maloca/types.js.map +1 -0
- package/dist/medico/aval.d.ts +149 -0
- package/dist/medico/aval.d.ts.map +1 -0
- package/dist/medico/aval.js +281 -0
- package/dist/medico/aval.js.map +1 -0
- package/dist/medico/canonica.d.ts +96 -0
- package/dist/medico/canonica.d.ts.map +1 -0
- package/dist/medico/canonica.js +148 -0
- package/dist/medico/canonica.js.map +1 -0
- package/dist/medico/credencial.d.ts +160 -0
- package/dist/medico/credencial.d.ts.map +1 -0
- package/dist/medico/credencial.js +240 -0
- package/dist/medico/credencial.js.map +1 -0
- package/dist/medico/index.d.ts +36 -0
- package/dist/medico/index.d.ts.map +1 -0
- package/dist/medico/index.js +39 -0
- package/dist/medico/index.js.map +1 -0
- package/dist/medico/malla.d.ts +218 -0
- package/dist/medico/malla.d.ts.map +1 -0
- package/dist/medico/malla.js +347 -0
- package/dist/medico/malla.js.map +1 -0
- package/dist/medico/registro-profesional.d.ts +84 -0
- package/dist/medico/registro-profesional.d.ts.map +1 -0
- package/dist/medico/registro-profesional.js +135 -0
- package/dist/medico/registro-profesional.js.map +1 -0
- package/dist/medico/registro.d.ts +149 -0
- package/dist/medico/registro.d.ts.map +1 -0
- package/dist/medico/registro.js +173 -0
- package/dist/medico/registro.js.map +1 -0
- package/dist/medico/types.d.ts +287 -0
- package/dist/medico/types.d.ts.map +1 -0
- package/dist/medico/types.js +135 -0
- package/dist/medico/types.js.map +1 -0
- package/dist/medico/verificacion-registro.d.ts +300 -0
- package/dist/medico/verificacion-registro.d.ts.map +1 -0
- package/dist/medico/verificacion-registro.js +319 -0
- package/dist/medico/verificacion-registro.js.map +1 -0
- package/dist/mesh/index.d.ts +457 -0
- package/dist/mesh/index.d.ts.map +1 -0
- package/dist/mesh/index.js +929 -0
- package/dist/mesh/index.js.map +1 -0
- package/dist/namespaces/encrypted-plugin.d.ts +56 -0
- package/dist/namespaces/encrypted-plugin.d.ts.map +1 -0
- package/dist/namespaces/encrypted-plugin.js +173 -0
- package/dist/namespaces/encrypted-plugin.js.map +1 -0
- package/dist/namespaces/index.d.ts +52 -0
- package/dist/namespaces/index.d.ts.map +1 -0
- package/dist/namespaces/index.js +134 -0
- package/dist/namespaces/index.js.map +1 -0
- package/dist/namespaces/ivn-proofs.d.ts +54 -0
- package/dist/namespaces/ivn-proofs.d.ts.map +1 -0
- package/dist/namespaces/ivn-proofs.js +162 -0
- package/dist/namespaces/ivn-proofs.js.map +1 -0
- package/dist/namespaces/offers-gossip.d.ts +95 -0
- package/dist/namespaces/offers-gossip.d.ts.map +1 -0
- package/dist/namespaces/offers-gossip.js +213 -0
- package/dist/namespaces/offers-gossip.js.map +1 -0
- package/dist/node-memory/idb-store.d.ts +22 -0
- package/dist/node-memory/idb-store.d.ts.map +1 -0
- package/dist/node-memory/idb-store.js +154 -0
- package/dist/node-memory/idb-store.js.map +1 -0
- package/dist/node-memory/index.d.ts +14 -0
- package/dist/node-memory/index.d.ts.map +1 -0
- package/dist/node-memory/index.js +159 -0
- package/dist/node-memory/index.js.map +1 -0
- package/dist/node-memory/sync-flush.d.ts +11 -0
- package/dist/node-memory/sync-flush.d.ts.map +1 -0
- package/dist/node-memory/sync-flush.js +37 -0
- package/dist/node-memory/sync-flush.js.map +1 -0
- package/dist/node-memory/types.d.ts +34 -0
- package/dist/node-memory/types.d.ts.map +1 -0
- package/dist/node-memory/types.js +2 -0
- package/dist/node-memory/types.js.map +1 -0
- package/dist/node-memory/xavier-store.d.ts +12 -0
- package/dist/node-memory/xavier-store.d.ts.map +1 -0
- package/dist/node-memory/xavier-store.js +75 -0
- package/dist/node-memory/xavier-store.js.map +1 -0
- package/dist/op-log/index.d.ts +49 -0
- package/dist/op-log/index.d.ts.map +1 -0
- package/dist/op-log/index.js +190 -0
- package/dist/op-log/index.js.map +1 -0
- package/dist/presence/health.d.ts +54 -0
- package/dist/presence/health.d.ts.map +1 -0
- package/dist/presence/health.js +139 -0
- package/dist/presence/health.js.map +1 -0
- package/dist/presence/index.d.ts +85 -0
- package/dist/presence/index.d.ts.map +1 -0
- package/dist/presence/index.js +255 -0
- package/dist/presence/index.js.map +1 -0
- package/dist/presence/peer-health.d.ts +40 -0
- package/dist/presence/peer-health.d.ts.map +1 -0
- package/dist/presence/peer-health.js +118 -0
- package/dist/presence/peer-health.js.map +1 -0
- package/dist/protocol/canonical.d.ts +10 -0
- package/dist/protocol/canonical.d.ts.map +1 -0
- package/dist/protocol/canonical.js +32 -0
- package/dist/protocol/canonical.js.map +1 -0
- package/dist/protocol/crypto.d.ts +299 -0
- package/dist/protocol/crypto.d.ts.map +1 -0
- package/dist/protocol/crypto.js +739 -0
- package/dist/protocol/crypto.js.map +1 -0
- package/dist/protocol/index.d.ts +30 -0
- package/dist/protocol/index.d.ts.map +1 -0
- package/dist/protocol/index.js +116 -0
- package/dist/protocol/index.js.map +1 -0
- package/dist/protocol/utils.d.ts +5 -0
- package/dist/protocol/utils.d.ts.map +1 -0
- package/dist/protocol/utils.js +27 -0
- package/dist/protocol/utils.js.map +1 -0
- package/dist/salones/manager.d.ts +100 -0
- package/dist/salones/manager.d.ts.map +1 -0
- package/dist/salones/manager.js +240 -0
- package/dist/salones/manager.js.map +1 -0
- package/dist/security/rate-limiter.d.ts +13 -0
- package/dist/security/rate-limiter.d.ts.map +1 -0
- package/dist/security/rate-limiter.js +32 -0
- package/dist/security/rate-limiter.js.map +1 -0
- package/dist/snapshot/index.d.ts +90 -0
- package/dist/snapshot/index.d.ts.map +1 -0
- package/dist/snapshot/index.js +189 -0
- package/dist/snapshot/index.js.map +1 -0
- package/dist/storage/index.d.ts +49 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +176 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/optimizer.d.ts +28 -0
- package/dist/storage/optimizer.d.ts.map +1 -0
- package/dist/storage/optimizer.js +50 -0
- package/dist/storage/optimizer.js.map +1 -0
- package/dist/storage/ydoc-persistence.d.ts +27 -0
- package/dist/storage/ydoc-persistence.d.ts.map +1 -0
- package/dist/storage/ydoc-persistence.js +128 -0
- package/dist/storage/ydoc-persistence.js.map +1 -0
- package/dist/sync/ChunkSyncManager.d.ts +41 -0
- package/dist/sync/ChunkSyncManager.d.ts.map +1 -0
- package/dist/sync/ChunkSyncManager.js +115 -0
- package/dist/sync/ChunkSyncManager.js.map +1 -0
- package/dist/sync/conflict-resolver.d.ts +33 -0
- package/dist/sync/conflict-resolver.d.ts.map +1 -0
- package/dist/sync/conflict-resolver.js +54 -0
- package/dist/sync/conflict-resolver.js.map +1 -0
- package/dist/sync/engine.d.ts +168 -0
- package/dist/sync/engine.d.ts.map +1 -0
- package/dist/sync/engine.js +315 -0
- package/dist/sync/engine.js.map +1 -0
- package/dist/sync/index.d.ts +3 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +3 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/transport/TorTransportAdapter.d.ts +70 -0
- package/dist/transport/TorTransportAdapter.d.ts.map +1 -0
- package/dist/transport/TorTransportAdapter.js +192 -0
- package/dist/transport/TorTransportAdapter.js.map +1 -0
- package/dist/transport/memory.d.ts +31 -0
- package/dist/transport/memory.d.ts.map +1 -0
- package/dist/transport/memory.js +114 -0
- package/dist/transport/memory.js.map +1 -0
- package/dist/transport/peerjs.d.ts +45 -0
- package/dist/transport/peerjs.d.ts.map +1 -0
- package/dist/transport/peerjs.js +301 -0
- package/dist/transport/peerjs.js.map +1 -0
- package/dist/transport/pqc-handshake.d.ts +72 -0
- package/dist/transport/pqc-handshake.d.ts.map +1 -0
- package/dist/transport/pqc-handshake.js +129 -0
- package/dist/transport/pqc-handshake.js.map +1 -0
- package/dist/transport/relay-config.d.ts +11 -0
- package/dist/transport/relay-config.d.ts.map +1 -0
- package/dist/transport/relay-config.js +48 -0
- package/dist/transport/relay-config.js.map +1 -0
- package/dist/transport/relay-server.d.ts +37 -0
- package/dist/transport/relay-server.d.ts.map +1 -0
- package/dist/transport/relay-server.js +230 -0
- package/dist/transport/relay-server.js.map +1 -0
- package/dist/transport/tor.d.ts +47 -0
- package/dist/transport/tor.d.ts.map +1 -0
- package/dist/transport/tor.js +158 -0
- package/dist/transport/tor.js.map +1 -0
- package/dist/transport/types.d.ts +34 -0
- package/dist/transport/types.d.ts.map +1 -0
- package/dist/transport/types.js +2 -0
- package/dist/transport/types.js.map +1 -0
- package/dist/types/index.d.ts +293 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +73 -0
- package/dist/types/index.js.map +1 -0
- package/dist/web/admission.d.ts +24 -0
- package/dist/web/admission.d.ts.map +1 -0
- package/dist/web/admission.js +41 -0
- package/dist/web/admission.js.map +1 -0
- package/dist/web/crypto.d.ts +18 -0
- package/dist/web/crypto.d.ts.map +1 -0
- package/dist/web/crypto.js +78 -0
- package/dist/web/crypto.js.map +1 -0
- package/dist/web/exchange.d.ts +23 -0
- package/dist/web/exchange.d.ts.map +1 -0
- package/dist/web/exchange.js +54 -0
- package/dist/web/exchange.js.map +1 -0
- package/dist/web/fragment.d.ts +58 -0
- package/dist/web/fragment.d.ts.map +1 -0
- package/dist/web/fragment.js +198 -0
- package/dist/web/fragment.js.map +1 -0
- package/dist/web/index.d.ts +25 -0
- package/dist/web/index.d.ts.map +1 -0
- package/dist/web/index.js +14 -0
- package/dist/web/index.js.map +1 -0
- package/dist/web/merge/index.d.ts +104 -0
- package/dist/web/merge/index.d.ts.map +1 -0
- package/dist/web/merge/index.js +236 -0
- package/dist/web/merge/index.js.map +1 -0
- package/dist/web/oplog/hlc.d.ts +19 -0
- package/dist/web/oplog/hlc.d.ts.map +1 -0
- package/dist/web/oplog/hlc.js +39 -0
- package/dist/web/oplog/hlc.js.map +1 -0
- package/dist/web/oplog/index.d.ts +10 -0
- package/dist/web/oplog/index.d.ts.map +1 -0
- package/dist/web/oplog/index.js +6 -0
- package/dist/web/oplog/index.js.map +1 -0
- package/dist/web/oplog/log.d.ts +182 -0
- package/dist/web/oplog/log.d.ts.map +1 -0
- package/dist/web/oplog/log.js +693 -0
- package/dist/web/oplog/log.js.map +1 -0
- package/dist/web/oplog/store.d.ts +40 -0
- package/dist/web/oplog/store.d.ts.map +1 -0
- package/dist/web/oplog/store.js +85 -0
- package/dist/web/oplog/store.js.map +1 -0
- package/dist/web/oplog/sync.d.ts +54 -0
- package/dist/web/oplog/sync.d.ts.map +1 -0
- package/dist/web/oplog/sync.js +162 -0
- package/dist/web/oplog/sync.js.map +1 -0
- package/dist/web/oplog/types.d.ts +119 -0
- package/dist/web/oplog/types.d.ts.map +1 -0
- package/dist/web/oplog/types.js +2 -0
- package/dist/web/oplog/types.js.map +1 -0
- package/dist/web/pairing.d.ts +186 -0
- package/dist/web/pairing.d.ts.map +1 -0
- package/dist/web/pairing.js +395 -0
- package/dist/web/pairing.js.map +1 -0
- package/dist/web/pq.d.ts +36 -0
- package/dist/web/pq.d.ts.map +1 -0
- package/dist/web/pq.js +59 -0
- package/dist/web/pq.js.map +1 -0
- package/dist/web/provider.d.ts +188 -0
- package/dist/web/provider.d.ts.map +1 -0
- package/dist/web/provider.js +2042 -0
- package/dist/web/provider.js.map +1 -0
- package/dist/web/rooms.d.ts +20 -0
- package/dist/web/rooms.d.ts.map +1 -0
- package/dist/web/rooms.js +51 -0
- package/dist/web/rooms.js.map +1 -0
- package/dist/web/rotation.d.ts +73 -0
- package/dist/web/rotation.d.ts.map +1 -0
- package/dist/web/rotation.js +130 -0
- package/dist/web/rotation.js.map +1 -0
- package/dist/web/secstate.d.ts +213 -0
- package/dist/web/secstate.d.ts.map +1 -0
- package/dist/web/secstate.js +789 -0
- package/dist/web/secstate.js.map +1 -0
- package/dist/web/store.d.ts +9 -0
- package/dist/web/store.d.ts.map +1 -0
- package/dist/web/store.js +32 -0
- package/dist/web/store.js.map +1 -0
- package/dist/web/transports/loopback.d.ts +11 -0
- package/dist/web/transports/loopback.d.ts.map +1 -0
- package/dist/web/transports/loopback.js +97 -0
- package/dist/web/transports/loopback.js.map +1 -0
- package/dist/web/transports/qr-sdp.d.ts +27 -0
- package/dist/web/transports/qr-sdp.d.ts.map +1 -0
- package/dist/web/transports/qr-sdp.js +131 -0
- package/dist/web/transports/qr-sdp.js.map +1 -0
- package/dist/web/transports/ws.d.ts +12 -0
- package/dist/web/transports/ws.d.ts.map +1 -0
- package/dist/web/transports/ws.js +139 -0
- package/dist/web/transports/ws.js.map +1 -0
- package/dist/web/trust/canonical.d.ts +8 -0
- package/dist/web/trust/canonical.d.ts.map +1 -0
- package/dist/web/trust/canonical.js +27 -0
- package/dist/web/trust/canonical.js.map +1 -0
- package/dist/web/trust/docs.d.ts +51 -0
- package/dist/web/trust/docs.d.ts.map +1 -0
- package/dist/web/trust/docs.js +170 -0
- package/dist/web/trust/docs.js.map +1 -0
- package/dist/web/trust/index.d.ts +10 -0
- package/dist/web/trust/index.d.ts.map +1 -0
- package/dist/web/trust/index.js +7 -0
- package/dist/web/trust/index.js.map +1 -0
- package/dist/web/trust/keys.d.ts +37 -0
- package/dist/web/trust/keys.d.ts.map +1 -0
- package/dist/web/trust/keys.js +75 -0
- package/dist/web/trust/keys.js.map +1 -0
- package/dist/web/trust/store.d.ts +150 -0
- package/dist/web/trust/store.d.ts.map +1 -0
- package/dist/web/trust/store.js +587 -0
- package/dist/web/trust/store.js.map +1 -0
- package/dist/web/trust/types.d.ts +134 -0
- package/dist/web/trust/types.d.ts.map +1 -0
- package/dist/web/trust/types.js +4 -0
- package/dist/web/trust/types.js.map +1 -0
- package/dist/web/types.d.ts +115 -0
- package/dist/web/types.d.ts.map +1 -0
- package/dist/web/types.js +2 -0
- package/dist/web/types.js.map +1 -0
- package/dist/web/util.d.ts +12 -0
- package/dist/web/util.d.ts.map +1 -0
- package/dist/web/util.js +54 -0
- package/dist/web/util.js.map +1 -0
- package/dist/web/webrtc.d.ts +35 -0
- package/dist/web/webrtc.d.ts.map +1 -0
- package/dist/web/webrtc.js +304 -0
- package/dist/web/webrtc.js.map +1 -0
- package/package.json +138 -4
|
@@ -0,0 +1,1146 @@
|
|
|
1
|
+
import { ml_dsa65 } from "@noble/post-quantum/ml-dsa.js";
|
|
2
|
+
import * as Y from "yjs";
|
|
3
|
+
import { CAPACIDAD_ESTANDAR, createNamespaceAuthorizer, } from "./authz/index.js";
|
|
4
|
+
import { PersistentOfflineQueue } from "./chat/offline-queue.js";
|
|
5
|
+
import { createEdgeMeshNode } from "./core/node.js";
|
|
6
|
+
import { createAuthorityManager, createGovernanceManager, } from "./governance/index.js";
|
|
7
|
+
import { createPostQuantumIdentity, generateKeypair, identityFromSecret, } from "./identity/index.js";
|
|
8
|
+
import { MerkleTree } from "./maloca/evidentia.js";
|
|
9
|
+
import { MeshGossip } from "./mesh/index.js";
|
|
10
|
+
import { NamespaceManager } from "./namespaces/index.js";
|
|
11
|
+
import { OpLog } from "./op-log/index.js";
|
|
12
|
+
import { PresenceManager } from "./presence/index.js";
|
|
13
|
+
import { canonicalStringify } from "./protocol/canonical.js";
|
|
14
|
+
import { createEnvelope, MessageDeduplicator, signEnvelope, validateEnvelope, verifyEnvelopeSignature, } from "./protocol/index.js";
|
|
15
|
+
import { bytesAHex, hexABytes } from "./protocol/utils.js";
|
|
16
|
+
import { createSnapshotManager, } from "./snapshot/index.js";
|
|
17
|
+
import { InMemoryStorage, StorageManager } from "./storage/index.js";
|
|
18
|
+
import { SyncEngine } from "./sync/engine.js";
|
|
19
|
+
import { MemoryTransport } from "./transport/memory.js";
|
|
20
|
+
import { PeerJSTransport, } from "./transport/peerjs.js";
|
|
21
|
+
import { PqcHandshake, } from "./transport/pqc-handshake.js";
|
|
22
|
+
import { TIPO_MENSAJE } from "./types/index.js";
|
|
23
|
+
// ─── YJS ADAPTER ───────────────────────────────────────────────────────────
|
|
24
|
+
/**
|
|
25
|
+
* Reserved transaction origin used when the mutation guard reverts unauthorized changes (self-healing).
|
|
26
|
+
*/
|
|
27
|
+
export const MUTATION_REVERT_ORIGIN = "mutation-guard-revert";
|
|
28
|
+
/**
|
|
29
|
+
* Utility helper to trace a nested or top-level shared type back to its top-level map collection name.
|
|
30
|
+
*/
|
|
31
|
+
function findTopLevelName(doc, type) {
|
|
32
|
+
let current = type;
|
|
33
|
+
while (current && current._item) {
|
|
34
|
+
current = current.parent;
|
|
35
|
+
}
|
|
36
|
+
if (!current)
|
|
37
|
+
return null;
|
|
38
|
+
for (const [name, sharedType] of doc.share.entries()) {
|
|
39
|
+
if (sharedType === current) {
|
|
40
|
+
return name;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
export class YjsAdapter {
|
|
46
|
+
doc;
|
|
47
|
+
/** When false, destroy() only detaches listeners (shared host doc). */
|
|
48
|
+
ownsDoc;
|
|
49
|
+
listeners;
|
|
50
|
+
mutationGuards;
|
|
51
|
+
afterTransactionHandler;
|
|
52
|
+
constructor(existingDoc, ownsDoc = !existingDoc) {
|
|
53
|
+
this.doc = existingDoc ?? new Y.Doc();
|
|
54
|
+
this.ownsDoc = ownsDoc;
|
|
55
|
+
this.listeners = new Map();
|
|
56
|
+
this.mutationGuards = new Set();
|
|
57
|
+
this.afterTransactionHandler = (tr) => {
|
|
58
|
+
if (tr.origin === MUTATION_REVERT_ORIGIN) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (this.mutationGuards.size === 0) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
// Collect all touched keys in Y.Map instances under their top-level collection names
|
|
65
|
+
const touched = new Map();
|
|
66
|
+
tr.changedParentTypes.forEach((events, type) => {
|
|
67
|
+
if (type instanceof Y.Map) {
|
|
68
|
+
const mapName = findTopLevelName(this.doc, type);
|
|
69
|
+
if (!mapName)
|
|
70
|
+
return;
|
|
71
|
+
if (!touched.has(mapName)) {
|
|
72
|
+
touched.set(mapName, new Set());
|
|
73
|
+
}
|
|
74
|
+
const keySet = touched.get(mapName);
|
|
75
|
+
for (const event of events) {
|
|
76
|
+
if (event instanceof Y.YMapEvent) {
|
|
77
|
+
for (const key of event.keys.keys()) {
|
|
78
|
+
keySet.add(key);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
if (touched.size === 0) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
let rejectAll = false;
|
|
88
|
+
const rejectedKeys = new Map();
|
|
89
|
+
for (const guard of this.mutationGuards) {
|
|
90
|
+
try {
|
|
91
|
+
const result = guard(tr.origin, touched);
|
|
92
|
+
if (result === false) {
|
|
93
|
+
rejectAll = true;
|
|
94
|
+
}
|
|
95
|
+
else if (result instanceof Map) {
|
|
96
|
+
for (const [mapName, keys] of result.entries()) {
|
|
97
|
+
if (!rejectedKeys.has(mapName)) {
|
|
98
|
+
rejectedKeys.set(mapName, new Set());
|
|
99
|
+
}
|
|
100
|
+
const set = rejectedKeys.get(mapName);
|
|
101
|
+
for (const k of keys) {
|
|
102
|
+
set.add(k);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
rejectAll = true;
|
|
109
|
+
console.error("Mutation guard threw an error, rejecting all changes:", error);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (rejectAll) {
|
|
113
|
+
for (const [mapName, keys] of touched.entries()) {
|
|
114
|
+
if (!rejectedKeys.has(mapName)) {
|
|
115
|
+
rejectedKeys.set(mapName, new Set());
|
|
116
|
+
}
|
|
117
|
+
const set = rejectedKeys.get(mapName);
|
|
118
|
+
for (const k of keys) {
|
|
119
|
+
set.add(k);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Apply surgical self-healing/reversion if any keys were rejected
|
|
124
|
+
if (rejectedKeys.size > 0) {
|
|
125
|
+
this.doc.transact(() => {
|
|
126
|
+
tr.changedParentTypes.forEach((events, type) => {
|
|
127
|
+
if (type instanceof Y.Map) {
|
|
128
|
+
const mapName = findTopLevelName(this.doc, type);
|
|
129
|
+
if (!mapName)
|
|
130
|
+
return;
|
|
131
|
+
const keysToReject = rejectedKeys.get(mapName);
|
|
132
|
+
if (!keysToReject)
|
|
133
|
+
return;
|
|
134
|
+
for (const event of events) {
|
|
135
|
+
if (event instanceof Y.YMapEvent) {
|
|
136
|
+
event.keys.forEach((change, key) => {
|
|
137
|
+
if (keysToReject.has(key)) {
|
|
138
|
+
if (change.action === "update" ||
|
|
139
|
+
change.action === "delete") {
|
|
140
|
+
event.target.set(key, change.oldValue);
|
|
141
|
+
}
|
|
142
|
+
else if (change.action === "add") {
|
|
143
|
+
event.target.delete(key);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}, MUTATION_REVERT_ORIGIN);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
this.doc.on("afterTransaction", this.afterTransactionHandler);
|
|
155
|
+
}
|
|
156
|
+
registerMutationGuard(fn) {
|
|
157
|
+
this.mutationGuards.add(fn);
|
|
158
|
+
return () => {
|
|
159
|
+
this.mutationGuards.delete(fn);
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
onUpdate(handler) {
|
|
163
|
+
const id = "global";
|
|
164
|
+
const handlers = this.listeners.get(id) ?? new Set();
|
|
165
|
+
handlers.add(handler);
|
|
166
|
+
this.listeners.set(id, handlers);
|
|
167
|
+
this.doc.on("update", handler);
|
|
168
|
+
return () => {
|
|
169
|
+
this.doc.off("update", handler);
|
|
170
|
+
handlers.delete(handler);
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
applyUpdate(update, origin = null) {
|
|
174
|
+
Y.applyUpdate(this.doc, update, origin);
|
|
175
|
+
}
|
|
176
|
+
getState() {
|
|
177
|
+
return Y.encodeStateAsUpdate(this.doc);
|
|
178
|
+
}
|
|
179
|
+
getStateVector() {
|
|
180
|
+
return Y.encodeStateVector(this.doc);
|
|
181
|
+
}
|
|
182
|
+
merge(remoteState) {
|
|
183
|
+
Y.applyUpdate(this.doc, remoteState);
|
|
184
|
+
}
|
|
185
|
+
getMap(name) {
|
|
186
|
+
return this.doc.getMap(name);
|
|
187
|
+
}
|
|
188
|
+
getArray(name) {
|
|
189
|
+
return this.doc.getArray(name);
|
|
190
|
+
}
|
|
191
|
+
getText(name) {
|
|
192
|
+
return this.doc.getText(name);
|
|
193
|
+
}
|
|
194
|
+
destroy() {
|
|
195
|
+
// Detach update handlers first
|
|
196
|
+
for (const handlers of this.listeners.values()) {
|
|
197
|
+
for (const handler of handlers) {
|
|
198
|
+
this.doc.off("update", handler);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
this.listeners.clear();
|
|
202
|
+
this.mutationGuards.clear();
|
|
203
|
+
this.doc.off("afterTransaction", this.afterTransactionHandler);
|
|
204
|
+
if (this.ownsDoc) {
|
|
205
|
+
this.doc.destroy();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// ─── EDGE MESH ─────────────────────────────────────────────────────────────
|
|
210
|
+
export class EdgeMesh {
|
|
211
|
+
config;
|
|
212
|
+
nodo;
|
|
213
|
+
eventTarget;
|
|
214
|
+
deduplicator;
|
|
215
|
+
storage;
|
|
216
|
+
governance;
|
|
217
|
+
authority;
|
|
218
|
+
identity;
|
|
219
|
+
presence;
|
|
220
|
+
authorizer;
|
|
221
|
+
namespaces;
|
|
222
|
+
yjsAdapter;
|
|
223
|
+
offlineQueue;
|
|
224
|
+
peerSecureChannels;
|
|
225
|
+
pqcHandshake;
|
|
226
|
+
meshGossip;
|
|
227
|
+
transport = null;
|
|
228
|
+
logsDoc;
|
|
229
|
+
syncs;
|
|
230
|
+
snapshots;
|
|
231
|
+
subscriptions;
|
|
232
|
+
merkleTree;
|
|
233
|
+
snapshotConfig;
|
|
234
|
+
snapshotTimer = null;
|
|
235
|
+
snapshotRestored = false;
|
|
236
|
+
/** Registered peer public keys for envelope verification. */
|
|
237
|
+
peerPublicKeys;
|
|
238
|
+
sybilRegistry;
|
|
239
|
+
requireAuthz;
|
|
240
|
+
requireSignedEnvelopes;
|
|
241
|
+
defaultSyncNamespace;
|
|
242
|
+
unsubYjs = null;
|
|
243
|
+
transportMessageHandler = null;
|
|
244
|
+
iniciado = false;
|
|
245
|
+
/** True when this mesh created the Y.Doc (false when host injects yDoc). */
|
|
246
|
+
sharesExternalDoc;
|
|
247
|
+
relayLocalYjs;
|
|
248
|
+
constructor(config) {
|
|
249
|
+
this.config = config;
|
|
250
|
+
this.logsDoc = new Map();
|
|
251
|
+
this.syncs = new Map();
|
|
252
|
+
this.snapshots = new Map();
|
|
253
|
+
this.peerPublicKeys = new Map();
|
|
254
|
+
this.sybilRegistry = new Map();
|
|
255
|
+
this.requireAuthz = config.requireAuthz !== false;
|
|
256
|
+
// Secure by default: SYNC/AUTHZ envelopes must be signed by a registered peer key (opt out explicitly).
|
|
257
|
+
this.requireSignedEnvelopes = config.requireSignedEnvelopes !== false;
|
|
258
|
+
this.defaultSyncNamespace = config.defaultSyncNamespace ?? "global";
|
|
259
|
+
this.sharesExternalDoc = config.yDoc !== undefined;
|
|
260
|
+
// Host-owned doc (dbSync) already broadcasts via p2pManager YJS_UPDATE — avoid double relay.
|
|
261
|
+
this.relayLocalYjs = config.relayLocalYjs ?? config.yDoc === undefined;
|
|
262
|
+
// Core — mesh uses its own EventTarget so emit("error") does not re-enter nodo handlers
|
|
263
|
+
this.nodo = createEdgeMeshNode(config.nodoId);
|
|
264
|
+
this.eventTarget = new EventTarget();
|
|
265
|
+
this.deduplicator = new MessageDeduplicator();
|
|
266
|
+
this.peerSecureChannels = new Map();
|
|
267
|
+
// Storage
|
|
268
|
+
this.storage =
|
|
269
|
+
config.storageBackend === "mem"
|
|
270
|
+
? new InMemoryStorage()
|
|
271
|
+
: new StorageManager({
|
|
272
|
+
dbName: `edge-mesh-${config.nodoId}`,
|
|
273
|
+
});
|
|
274
|
+
// Identity — never pair a custom private key with an empty public key
|
|
275
|
+
if (config.identitySecret && config.identitySecret.length > 0) {
|
|
276
|
+
this.identity = identityFromSecret(config.nodoId, config.identitySecret, "maestra");
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
this.identity = createPostQuantumIdentity(config.nodoId, generateKeypair("maestra"));
|
|
280
|
+
}
|
|
281
|
+
this.registrarClavePublica(config.nodoId, this.identity.exportarPublico());
|
|
282
|
+
this.pqcHandshake = new PqcHandshake(this.identity, (peerId) => this.obtenerClavePublica(peerId));
|
|
283
|
+
// Yjs — Phase B: optional shared host document (e.g. dbSync.doc)
|
|
284
|
+
const externalDoc = config.yDoc;
|
|
285
|
+
this.yjsAdapter = new YjsAdapter(externalDoc, !externalDoc);
|
|
286
|
+
// Governance with crypto verifier
|
|
287
|
+
this.governance = createGovernanceManager(config.governancePolicy, this, {
|
|
288
|
+
requireSignedVotes: config.requireSignedVotes,
|
|
289
|
+
});
|
|
290
|
+
// Mesh Gossip
|
|
291
|
+
this.meshGossip = new MeshGossip({
|
|
292
|
+
nodoId: config.nodoId,
|
|
293
|
+
fanOut: config.gossipFanOut ?? 3,
|
|
294
|
+
gossipTTL: config.gossipTTL ?? 5,
|
|
295
|
+
}, this);
|
|
296
|
+
this.meshGossip.addEventListener("gossipRecibido", (ev) => {
|
|
297
|
+
this.emit("gossipRecibido", ev.detail);
|
|
298
|
+
});
|
|
299
|
+
// Presence
|
|
300
|
+
this.presence = new PresenceManager({
|
|
301
|
+
heartbeatIntervalMs: config.heartbeatIntervalMs ?? 5_000,
|
|
302
|
+
timeoutMs: config.heartbeatTimeoutMs ?? 15_000,
|
|
303
|
+
});
|
|
304
|
+
// Authority
|
|
305
|
+
this.authority = createAuthorityManager(config.nodoId, this.presence, {
|
|
306
|
+
initialMaster: config.initialMaster,
|
|
307
|
+
});
|
|
308
|
+
// Forward authority events
|
|
309
|
+
this.authority.on("failover", (ev) => {
|
|
310
|
+
this.emit("failover", ev.detail);
|
|
311
|
+
});
|
|
312
|
+
this.presence.registrarClavePublica(config.nodoId, this.identity.exportarPublico());
|
|
313
|
+
// Authz
|
|
314
|
+
this.authorizer = createNamespaceAuthorizer(this.storage);
|
|
315
|
+
this.subscriptions = new Map();
|
|
316
|
+
this.merkleTree = new MerkleTree();
|
|
317
|
+
const defaultSnapConfig = {
|
|
318
|
+
intervalMs: 5 * 60 * 1000, // 5 min
|
|
319
|
+
maxSnapshots: 3,
|
|
320
|
+
include: [],
|
|
321
|
+
};
|
|
322
|
+
this.snapshotConfig = { ...defaultSnapConfig, ...config.snapshotConfig };
|
|
323
|
+
// Namespaces
|
|
324
|
+
this.namespaces = new NamespaceManager();
|
|
325
|
+
// Offline Queue
|
|
326
|
+
this.offlineQueue = new PersistentOfflineQueue(this.storage);
|
|
327
|
+
this.presence.addOnlineListener((peerId) => {
|
|
328
|
+
void this.offlineQueue.handlePeerReconnect(peerId);
|
|
329
|
+
if (this.config.enablePqcEncryption !== false &&
|
|
330
|
+
this.config.nodoId < peerId) {
|
|
331
|
+
void this.iniciarPqcHandshake(peerId);
|
|
332
|
+
}
|
|
333
|
+
void this.solicitarSyncYjs(peerId);
|
|
334
|
+
});
|
|
335
|
+
// Re-encolar eventos del nodo (forward to mesh EventTarget without looping)
|
|
336
|
+
this.nodo.on("nodoConectado", (ev) => {
|
|
337
|
+
this.onNodoConectado(ev.detail.nodoId);
|
|
338
|
+
this.emit("nodoConectado", ev.detail);
|
|
339
|
+
});
|
|
340
|
+
this.nodo.on("nodoDesconectado", (ev) => {
|
|
341
|
+
this.onNodoDesconectado(ev.detail.nodoId);
|
|
342
|
+
this.emit("nodoDesconectado", ev.detail);
|
|
343
|
+
});
|
|
344
|
+
this.nodo.on("estadoCambiado", (ev) => {
|
|
345
|
+
this.emit("estadoCambiado", ev.detail);
|
|
346
|
+
});
|
|
347
|
+
// Note: do NOT re-emit nodo "error" onto the same shared target pattern —
|
|
348
|
+
// mesh-level errors use this.emit("error") on a dedicated EventTarget.
|
|
349
|
+
}
|
|
350
|
+
// ─── PUBLIC KEY REGISTRY ─────────────────────────────────────────────
|
|
351
|
+
registrarClavePublica(nodoId, parPublico, origenIp = "127.0.0.1") {
|
|
352
|
+
const threshold = this.config.sybilThreshold ?? 10;
|
|
353
|
+
let registered = this.sybilRegistry.get(origenIp);
|
|
354
|
+
if (!registered) {
|
|
355
|
+
registered = new Set();
|
|
356
|
+
this.sybilRegistry.set(origenIp, registered);
|
|
357
|
+
}
|
|
358
|
+
if (registered.size >= threshold && !registered.has(nodoId)) {
|
|
359
|
+
throw new Error(`Sybil attack detected: threshold exceeded for IP ${origenIp}`);
|
|
360
|
+
}
|
|
361
|
+
registered.add(nodoId);
|
|
362
|
+
this.peerPublicKeys.set(nodoId, new Uint8Array(parPublico));
|
|
363
|
+
if (this.presence) {
|
|
364
|
+
this.presence.registrarClavePublica(nodoId, new Uint8Array(parPublico));
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
obtenerClavePublica(nodoId) {
|
|
368
|
+
return this.peerPublicKeys.get(nodoId);
|
|
369
|
+
}
|
|
370
|
+
verificarFirma(mensaje, firma, clave) {
|
|
371
|
+
return this.verificarFirmaVoto(mensaje, firma, clave);
|
|
372
|
+
}
|
|
373
|
+
verificarFirmaVoto(mensaje, firma, clave) {
|
|
374
|
+
try {
|
|
375
|
+
return ml_dsa65.verify(firma, mensaje, clave);
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
// ─── INICIALIZACION ──────────────────────────────────────────────────
|
|
382
|
+
/**
|
|
383
|
+
* Attach an external transport (MemoryTransport, adapter over host PeerJS, etc.).
|
|
384
|
+
* Prefer this over `peerId` when the host app already owns a Peer connection.
|
|
385
|
+
* Safe to call after `iniciar()` (Phase C late-bind).
|
|
386
|
+
*/
|
|
387
|
+
usarTransport(transport) {
|
|
388
|
+
if (this.transport && this.transportMessageHandler) {
|
|
389
|
+
this.transport.off("mensaje", this.transportMessageHandler);
|
|
390
|
+
}
|
|
391
|
+
this.transport = transport;
|
|
392
|
+
this.transportMessageHandler = (ev) => {
|
|
393
|
+
void this.procesarMensaje(ev.detail.envolvente);
|
|
394
|
+
};
|
|
395
|
+
this.transport.on("mensaje", this.transportMessageHandler);
|
|
396
|
+
// Late-bind Yjs relay if mesh already started
|
|
397
|
+
if (this.iniciado) {
|
|
398
|
+
this.ensureYjsRelay();
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
/** Detach transport without destroying host PeerJS (adapter.cerrar only unsubscribes). */
|
|
402
|
+
detachTransport() {
|
|
403
|
+
if (this.transport && this.transportMessageHandler) {
|
|
404
|
+
this.transport.off("mensaje", this.transportMessageHandler);
|
|
405
|
+
}
|
|
406
|
+
this.transportMessageHandler = null;
|
|
407
|
+
this.transport = null;
|
|
408
|
+
if (this.unsubYjs) {
|
|
409
|
+
this.unsubYjs();
|
|
410
|
+
this.unsubYjs = null;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
ensureYjsRelay() {
|
|
414
|
+
if (this.unsubYjs || this.transport === null || !this.relayLocalYjs)
|
|
415
|
+
return;
|
|
416
|
+
this.unsubYjs = this.yjsAdapter.onUpdate((update, origin) => {
|
|
417
|
+
if (origin === "remote" || origin === this.config.nodoId)
|
|
418
|
+
return;
|
|
419
|
+
// Local writes: string "local" or object { origin: "local", branchId }
|
|
420
|
+
const isLocalObject = typeof origin === "object" &&
|
|
421
|
+
origin !== null &&
|
|
422
|
+
origin.origin === "local";
|
|
423
|
+
if (origin !== "local" && !isLocalObject && origin !== null)
|
|
424
|
+
return;
|
|
425
|
+
void this.broadcastYjsUpdate(update);
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
async iniciar() {
|
|
429
|
+
// Cargar persistencia de authz
|
|
430
|
+
await this.authorizer.loadGrants();
|
|
431
|
+
await this.authorizer.loadRoleAssignments();
|
|
432
|
+
await this.authorizer.loadCapabilities();
|
|
433
|
+
// Intentar restaurar desde snapshot al iniciar
|
|
434
|
+
await this.restaurarDesdeSnapshot();
|
|
435
|
+
// Optional built-in PeerJS — skip when host provides transport or omits peerId
|
|
436
|
+
if (this.transport === null && this.config.peerId !== undefined) {
|
|
437
|
+
const opts = {
|
|
438
|
+
peerId: this.config.peerId,
|
|
439
|
+
...this.config.transportConfig,
|
|
440
|
+
};
|
|
441
|
+
const peerTransport = new PeerJSTransport(this.config.nodoId, opts);
|
|
442
|
+
this.usarTransport(peerTransport);
|
|
443
|
+
}
|
|
444
|
+
// Connect memory transport if already attached
|
|
445
|
+
if (this.transport instanceof MemoryTransport) {
|
|
446
|
+
await this.transport.conectar();
|
|
447
|
+
}
|
|
448
|
+
this.ensureYjsRelay();
|
|
449
|
+
// Iniciar presencia
|
|
450
|
+
await this.presence.iniciar(this.config.nodoId, async (payload) => {
|
|
451
|
+
await this.transmitir(payload, TIPO_MENSAJE.HEARTBEAT);
|
|
452
|
+
}, this.identity);
|
|
453
|
+
// Iniciar autoridad
|
|
454
|
+
this.authority.iniciar();
|
|
455
|
+
// Conectar nodo
|
|
456
|
+
await this.nodo.conectar();
|
|
457
|
+
this.iniciado = true;
|
|
458
|
+
// Programar snapshot automático
|
|
459
|
+
if (this.snapshotTimer === null) {
|
|
460
|
+
this.snapshotTimer = setInterval(() => {
|
|
461
|
+
void this.generarSnapshotAutomatico();
|
|
462
|
+
}, this.snapshotConfig.intervalMs);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
async detener() {
|
|
466
|
+
this.iniciado = false;
|
|
467
|
+
if (this.snapshotTimer !== null) {
|
|
468
|
+
clearInterval(this.snapshotTimer);
|
|
469
|
+
this.snapshotTimer = null;
|
|
470
|
+
}
|
|
471
|
+
this.presence.detener();
|
|
472
|
+
this.authority.detener();
|
|
473
|
+
if (this.unsubYjs) {
|
|
474
|
+
this.unsubYjs();
|
|
475
|
+
this.unsubYjs = null;
|
|
476
|
+
}
|
|
477
|
+
if (this.transport !== null) {
|
|
478
|
+
if (this.transportMessageHandler) {
|
|
479
|
+
this.transport.off("mensaje", this.transportMessageHandler);
|
|
480
|
+
this.transportMessageHandler = null;
|
|
481
|
+
}
|
|
482
|
+
await this.transport.cerrar();
|
|
483
|
+
this.transport = null;
|
|
484
|
+
}
|
|
485
|
+
await this.nodo.desconectar();
|
|
486
|
+
// Shared host docs are only detached (listeners cleared), never destroyed.
|
|
487
|
+
this.yjsAdapter.destroy();
|
|
488
|
+
this.governance.destruir();
|
|
489
|
+
this.meshGossip.destruir();
|
|
490
|
+
}
|
|
491
|
+
/** Current attached transport (if any). */
|
|
492
|
+
obtenerTransport() {
|
|
493
|
+
return this.transport;
|
|
494
|
+
}
|
|
495
|
+
/** Whether yjsAdapter.doc is an externally owned document. */
|
|
496
|
+
isSharedYDoc() {
|
|
497
|
+
return this.sharesExternalDoc;
|
|
498
|
+
}
|
|
499
|
+
// ─── TRANSPORTE ──────────────────────────────────────────────────────
|
|
500
|
+
async enviar(destino, payload, tipoMensaje = TIPO_MENSAJE.SYNC) {
|
|
501
|
+
if (this.transport !== null) {
|
|
502
|
+
await this.transport.enviar(destino, payload, tipoMensaje);
|
|
503
|
+
}
|
|
504
|
+
await this.nodo.enviar(destino, payload);
|
|
505
|
+
}
|
|
506
|
+
async transmitir(payload, tipoMensaje = TIPO_MENSAJE.SYNC) {
|
|
507
|
+
if (tipoMensaje === TIPO_MENSAJE.SYNC) {
|
|
508
|
+
const connections = this.transport
|
|
509
|
+
? this.transport.obtenerConexiones()
|
|
510
|
+
: [];
|
|
511
|
+
if (connections.length > 0) {
|
|
512
|
+
const promesas = connections.map((peerId) => this.enviarSyncEnvelope(peerId, payload));
|
|
513
|
+
await Promise.all(promesas);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
if (this.transport !== null) {
|
|
518
|
+
await this.transport.transmitir(payload, tipoMensaje);
|
|
519
|
+
}
|
|
520
|
+
await this.nodo.transmitir(payload);
|
|
521
|
+
}
|
|
522
|
+
async enviarSyncEnvelope(destino, payload) {
|
|
523
|
+
const rawPayload = esEnvolvente(payload) ? payload.payload : payload;
|
|
524
|
+
let finalPayload = rawPayload;
|
|
525
|
+
const secureChannel = this.peerSecureChannels.get(destino);
|
|
526
|
+
if (secureChannel &&
|
|
527
|
+
secureChannel.status === "ready" &&
|
|
528
|
+
secureChannel.channel) {
|
|
529
|
+
const plaintext = new TextEncoder().encode(JSON.stringify(rawPayload));
|
|
530
|
+
const encrypted = secureChannel.channel.encrypt(plaintext);
|
|
531
|
+
finalPayload = {
|
|
532
|
+
encrypted: true,
|
|
533
|
+
ciphertext: bytesAHex(encrypted.ciphertext),
|
|
534
|
+
iv: bytesAHex(encrypted.iv),
|
|
535
|
+
tag: bytesAHex(encrypted.tag),
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
let env = createEnvelope(TIPO_MENSAJE.SYNC, this.config.nodoId, destino, finalPayload);
|
|
539
|
+
if (this.requireSignedEnvelopes) {
|
|
540
|
+
env = await signEnvelope(env, this.identity);
|
|
541
|
+
}
|
|
542
|
+
await this.enviar(destino, env, TIPO_MENSAJE.SYNC);
|
|
543
|
+
}
|
|
544
|
+
async iniciarPqcHandshake(destino) {
|
|
545
|
+
if (this.config.enablePqcEncryption === false)
|
|
546
|
+
return;
|
|
547
|
+
const existing = this.peerSecureChannels.get(destino);
|
|
548
|
+
if (existing &&
|
|
549
|
+
(existing.status === "initiating" || existing.status === "ready")) {
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
// Set status synchronously to prevent concurrent triggers!
|
|
553
|
+
this.peerSecureChannels.set(destino, { status: "initiating" });
|
|
554
|
+
try {
|
|
555
|
+
const { payload, keysA, challengeA } = await this.pqcHandshake.initiate(destino);
|
|
556
|
+
this.peerSecureChannels.set(destino, {
|
|
557
|
+
status: "initiating",
|
|
558
|
+
keysA,
|
|
559
|
+
challengeA,
|
|
560
|
+
});
|
|
561
|
+
await this.enviar(destino, payload, TIPO_MENSAJE.PQC_HANDSHAKE);
|
|
562
|
+
}
|
|
563
|
+
catch (err) {
|
|
564
|
+
this.peerSecureChannels.delete(destino);
|
|
565
|
+
this.emit("error", {
|
|
566
|
+
mensaje: `Error al iniciar PQC handshake con ${destino}: ${err.message}`,
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
async solicitarSyncYjs(destino, docId = "default") {
|
|
571
|
+
const stateVector = this.yjsAdapter.getStateVector();
|
|
572
|
+
const payload = {
|
|
573
|
+
tipoSync: "solicitud",
|
|
574
|
+
docId,
|
|
575
|
+
datos: Array.from(stateVector),
|
|
576
|
+
clock: Date.now(),
|
|
577
|
+
namespace: this.defaultSyncNamespace,
|
|
578
|
+
};
|
|
579
|
+
await this.enviarSyncEnvelope(destino, payload);
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Publish a CRDT update to peers (optionally signed).
|
|
583
|
+
*/
|
|
584
|
+
async broadcastYjsUpdate(update, docId = "default") {
|
|
585
|
+
const payload = {
|
|
586
|
+
tipoSync: "delta",
|
|
587
|
+
docId,
|
|
588
|
+
// JSON-safe encoding for transports that serialize to JSON
|
|
589
|
+
datos: Array.from(update),
|
|
590
|
+
clock: Date.now(),
|
|
591
|
+
};
|
|
592
|
+
let env = createEnvelope(TIPO_MENSAJE.SYNC, this.config.nodoId, "*", payload);
|
|
593
|
+
if (this.requireSignedEnvelopes) {
|
|
594
|
+
env = await signEnvelope(env, this.identity);
|
|
595
|
+
}
|
|
596
|
+
if (this.transport !== null) {
|
|
597
|
+
await this.transmitir(env, TIPO_MENSAJE.SYNC);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
// ─── PROCESAMIENTO DE MENSAJES ───────────────────────────────────────
|
|
601
|
+
/** Exposed for tests / external transports feeding envelopes. */
|
|
602
|
+
async recibirEnvelope(env) {
|
|
603
|
+
await this.procesarMensaje(env);
|
|
604
|
+
}
|
|
605
|
+
async procesarMensaje(env) {
|
|
606
|
+
if (!validateEnvelope(env))
|
|
607
|
+
return;
|
|
608
|
+
const envolvente = env;
|
|
609
|
+
if (this.deduplicator.esDuplicado(envolvente))
|
|
610
|
+
return;
|
|
611
|
+
// Handshake trigger
|
|
612
|
+
if (this.config.enablePqcEncryption !== false &&
|
|
613
|
+
this.config.nodoId < envolvente.origen &&
|
|
614
|
+
!this.peerSecureChannels.has(envolvente.origen) &&
|
|
615
|
+
envolvente.tipo !== TIPO_MENSAJE.PQC_HANDSHAKE &&
|
|
616
|
+
envolvente.tipo !== TIPO_MENSAJE.KEM_REPLY &&
|
|
617
|
+
envolvente.tipo !== TIPO_MENSAJE.PQC_ACK) {
|
|
618
|
+
void this.iniciarPqcHandshake(envolvente.origen);
|
|
619
|
+
}
|
|
620
|
+
// Signature gate for sensitive message types. The sender signs the envelope as sent (with the encrypted
|
|
621
|
+
// payload when a PQC channel is up), so verify it as received, BEFORE decrypting.
|
|
622
|
+
if (this.requireSignedEnvelopes &&
|
|
623
|
+
(envolvente.tipo === TIPO_MENSAJE.SYNC ||
|
|
624
|
+
envolvente.tipo === TIPO_MENSAJE.AUTHZ)) {
|
|
625
|
+
const ok = await this.verificarFirmaEnvelope(envolvente);
|
|
626
|
+
if (!ok) {
|
|
627
|
+
this.emit("error", {
|
|
628
|
+
mensaje: `Firma invalida o ausente de ${envolvente.origen} (${envolvente.tipo})`,
|
|
629
|
+
});
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
let processedEnvelope = envolvente;
|
|
634
|
+
if (envolvente.tipo === TIPO_MENSAJE.SYNC &&
|
|
635
|
+
envolvente.payload &&
|
|
636
|
+
typeof envolvente.payload === "object" &&
|
|
637
|
+
envolvente.payload.encrypted === true) {
|
|
638
|
+
const secureChannel = this.peerSecureChannels.get(envolvente.origen);
|
|
639
|
+
if (secureChannel &&
|
|
640
|
+
(secureChannel.status === "ready" ||
|
|
641
|
+
secureChannel.status === "responding") &&
|
|
642
|
+
secureChannel.channel) {
|
|
643
|
+
try {
|
|
644
|
+
const encPayload = envolvente.payload;
|
|
645
|
+
const decryptedBytes = secureChannel.channel.decrypt(hexABytes(encPayload.ciphertext), hexABytes(encPayload.iv), hexABytes(encPayload.tag));
|
|
646
|
+
const decryptedPayload = JSON.parse(new TextDecoder().decode(decryptedBytes));
|
|
647
|
+
processedEnvelope = {
|
|
648
|
+
...envolvente,
|
|
649
|
+
payload: decryptedPayload,
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
catch (err) {
|
|
653
|
+
this.emit("error", {
|
|
654
|
+
mensaje: `Error descifrando SYNC de ${envolvente.origen}: ${err.message}`,
|
|
655
|
+
});
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
else {
|
|
660
|
+
this.emit("error", {
|
|
661
|
+
mensaje: `SYNC cifrado recibido de ${envolvente.origen} pero no hay canal seguro listo`,
|
|
662
|
+
});
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
this.emit("mensajeRecibido", { envolvente: processedEnvelope });
|
|
667
|
+
switch (processedEnvelope.tipo) {
|
|
668
|
+
case TIPO_MENSAJE.HEARTBEAT:
|
|
669
|
+
await this.presence.procesarHeartbeat(processedEnvelope.payload);
|
|
670
|
+
break;
|
|
671
|
+
case TIPO_MENSAJE.SYNC:
|
|
672
|
+
await this.procesarSync(processedEnvelope);
|
|
673
|
+
break;
|
|
674
|
+
case TIPO_MENSAJE.SNAPSHOT:
|
|
675
|
+
await this.procesarSnapshot(processedEnvelope);
|
|
676
|
+
break;
|
|
677
|
+
case TIPO_MENSAJE.GOVERNANCE:
|
|
678
|
+
await this.procesarGovernance(processedEnvelope);
|
|
679
|
+
break;
|
|
680
|
+
case TIPO_MENSAJE.AUTHZ:
|
|
681
|
+
await this.procesarAuthz(processedEnvelope);
|
|
682
|
+
break;
|
|
683
|
+
case TIPO_MENSAJE.NAMESPACE:
|
|
684
|
+
await this.procesarNamespace(processedEnvelope);
|
|
685
|
+
break;
|
|
686
|
+
case TIPO_MENSAJE.PQC_HANDSHAKE:
|
|
687
|
+
await this.procesarPqcHandshake(processedEnvelope);
|
|
688
|
+
break;
|
|
689
|
+
case TIPO_MENSAJE.KEM_REPLY:
|
|
690
|
+
await this.procesarKemReply(processedEnvelope);
|
|
691
|
+
break;
|
|
692
|
+
case TIPO_MENSAJE.PQC_ACK:
|
|
693
|
+
await this.procesarPqcAck(processedEnvelope);
|
|
694
|
+
break;
|
|
695
|
+
case TIPO_MENSAJE.GOSSIP:
|
|
696
|
+
await this.procesarGossip(processedEnvelope);
|
|
697
|
+
break;
|
|
698
|
+
default:
|
|
699
|
+
break;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
async procesarGossip(env) {
|
|
703
|
+
const payload = env.payload;
|
|
704
|
+
const mensaje = (payload?.mensaje ?? payload);
|
|
705
|
+
this.meshGossip.recibirGossip(env.origen, mensaje);
|
|
706
|
+
}
|
|
707
|
+
async publicarGossip(payload, namespace = "global", ttl) {
|
|
708
|
+
const mensaje = {
|
|
709
|
+
id: crypto.randomUUID(),
|
|
710
|
+
namespace,
|
|
711
|
+
ttl: ttl ?? this.config.gossipTTL ?? 5,
|
|
712
|
+
payload,
|
|
713
|
+
origen: this.config.nodoId,
|
|
714
|
+
timestamp: Date.now(),
|
|
715
|
+
ruta: [this.config.nodoId],
|
|
716
|
+
};
|
|
717
|
+
await this.meshGossip.propagarGossip(mensaje);
|
|
718
|
+
}
|
|
719
|
+
async procesarPqcHandshake(env) {
|
|
720
|
+
if (this.config.enablePqcEncryption === false)
|
|
721
|
+
return;
|
|
722
|
+
const payload = env.payload;
|
|
723
|
+
const existing = this.peerSecureChannels.get(env.origen);
|
|
724
|
+
if (existing &&
|
|
725
|
+
(existing.status === "responding" || existing.status === "ready")) {
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
// Synchronous reservation
|
|
729
|
+
this.peerSecureChannels.set(env.origen, { status: "responding" });
|
|
730
|
+
try {
|
|
731
|
+
const { payload: replyPayload, channel, challengeB, } = await this.pqcHandshake.respond(env.origen, payload);
|
|
732
|
+
this.peerSecureChannels.set(env.origen, {
|
|
733
|
+
status: "responding",
|
|
734
|
+
challengeB,
|
|
735
|
+
channel,
|
|
736
|
+
});
|
|
737
|
+
await this.enviar(env.origen, replyPayload, TIPO_MENSAJE.KEM_REPLY);
|
|
738
|
+
}
|
|
739
|
+
catch (err) {
|
|
740
|
+
this.peerSecureChannels.delete(env.origen);
|
|
741
|
+
this.emit("error", {
|
|
742
|
+
mensaje: `Error procesando PQC_HANDSHAKE de ${env.origen}: ${err.message}`,
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
async procesarKemReply(env) {
|
|
747
|
+
if (this.config.enablePqcEncryption === false)
|
|
748
|
+
return;
|
|
749
|
+
const payload = env.payload;
|
|
750
|
+
const state = this.peerSecureChannels.get(env.origen);
|
|
751
|
+
if (!state || state.status !== "initiating")
|
|
752
|
+
return;
|
|
753
|
+
// Move state synchronously to ready
|
|
754
|
+
this.peerSecureChannels.set(env.origen, { status: "ready" });
|
|
755
|
+
try {
|
|
756
|
+
const { payload: ackPayload, channel } = await this.pqcHandshake.finalize(env.origen, state, payload);
|
|
757
|
+
this.peerSecureChannels.set(env.origen, {
|
|
758
|
+
status: "ready",
|
|
759
|
+
channel,
|
|
760
|
+
});
|
|
761
|
+
this.emit("handshakeCompletado", { peerId: env.origen });
|
|
762
|
+
await this.enviar(env.origen, ackPayload, TIPO_MENSAJE.PQC_ACK);
|
|
763
|
+
}
|
|
764
|
+
catch (err) {
|
|
765
|
+
this.peerSecureChannels.set(env.origen, state); // revert state
|
|
766
|
+
this.emit("error", {
|
|
767
|
+
mensaje: `Error procesando KEM_REPLY de ${env.origen}: ${err.message}`,
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
async procesarPqcAck(env) {
|
|
772
|
+
if (this.config.enablePqcEncryption === false)
|
|
773
|
+
return;
|
|
774
|
+
const payload = env.payload;
|
|
775
|
+
const state = this.peerSecureChannels.get(env.origen);
|
|
776
|
+
if (!state || state.status !== "responding")
|
|
777
|
+
return;
|
|
778
|
+
// Move state synchronously to ready
|
|
779
|
+
this.peerSecureChannels.set(env.origen, { status: "ready" });
|
|
780
|
+
try {
|
|
781
|
+
await this.pqcHandshake.verifyAck(env.origen, state, payload);
|
|
782
|
+
this.peerSecureChannels.set(env.origen, {
|
|
783
|
+
status: "ready",
|
|
784
|
+
channel: state.channel,
|
|
785
|
+
});
|
|
786
|
+
this.emit("handshakeCompletado", { peerId: env.origen });
|
|
787
|
+
}
|
|
788
|
+
catch (err) {
|
|
789
|
+
this.peerSecureChannels.set(env.origen, state); // revert state
|
|
790
|
+
this.emit("error", {
|
|
791
|
+
mensaje: `Error procesando PQC_ACK de ${env.origen}: ${err.message}`,
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
async verificarFirmaEnvelope(env) {
|
|
796
|
+
const pub = this.peerPublicKeys.get(env.origen);
|
|
797
|
+
if (!pub)
|
|
798
|
+
return false;
|
|
799
|
+
return verifyEnvelopeSignature(env, pub, this.identity);
|
|
800
|
+
}
|
|
801
|
+
decodeSyncBytes(datos) {
|
|
802
|
+
if (datos instanceof Uint8Array)
|
|
803
|
+
return datos;
|
|
804
|
+
if (Array.isArray(datos))
|
|
805
|
+
return new Uint8Array(datos);
|
|
806
|
+
if (typeof datos === "object" && datos !== null && "data" in datos) {
|
|
807
|
+
const arr = datos.data;
|
|
808
|
+
if (Array.isArray(arr))
|
|
809
|
+
return new Uint8Array(arr);
|
|
810
|
+
}
|
|
811
|
+
return null;
|
|
812
|
+
}
|
|
813
|
+
async procesarSync(env) {
|
|
814
|
+
const payload = env.payload;
|
|
815
|
+
if (payload === undefined || typeof payload !== "object")
|
|
816
|
+
return;
|
|
817
|
+
const docId = payload.docId;
|
|
818
|
+
if (docId === undefined)
|
|
819
|
+
return;
|
|
820
|
+
const namespace = payload.namespace ?? this.defaultSyncNamespace;
|
|
821
|
+
if (this.requireAuthz) {
|
|
822
|
+
const allowed = this.authorizer.verificarCapacidad(namespace, env.origen, CAPACIDAD_ESTANDAR.ESCRIBIR) ||
|
|
823
|
+
this.authorizer.verificarCapacidad(namespace, env.origen, CAPACIDAD_ESTANDAR.SINC) ||
|
|
824
|
+
this.authorizer.verificarCapacidad(namespace, env.origen, CAPACIDAD_ESTANDAR.ADMIN);
|
|
825
|
+
if (!allowed) {
|
|
826
|
+
this.emit("error", {
|
|
827
|
+
mensaje: `SYNC denegado: ${env.origen} sin write/sync en ${namespace}`,
|
|
828
|
+
});
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
const bytes = this.decodeSyncBytes(payload.datos);
|
|
833
|
+
if (!bytes)
|
|
834
|
+
return;
|
|
835
|
+
const tipoSync = payload.tipoSync ?? "delta";
|
|
836
|
+
if (tipoSync === "solicitud") {
|
|
837
|
+
const diff = Y.encodeStateAsUpdate(this.yjsAdapter.doc, bytes);
|
|
838
|
+
const responsePayload = {
|
|
839
|
+
tipoSync: "delta",
|
|
840
|
+
docId,
|
|
841
|
+
datos: Array.from(diff),
|
|
842
|
+
clock: Date.now(),
|
|
843
|
+
namespace,
|
|
844
|
+
};
|
|
845
|
+
await this.enviarSyncEnvelope(env.origen, responsePayload);
|
|
846
|
+
}
|
|
847
|
+
else {
|
|
848
|
+
if (bytes.length > 0) {
|
|
849
|
+
this.yjsAdapter.applyUpdate(bytes, env.origen);
|
|
850
|
+
}
|
|
851
|
+
this.emit("syncCompletado", {
|
|
852
|
+
docId,
|
|
853
|
+
clock: payload.clock ?? 0,
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
async procesarSnapshot(env) {
|
|
858
|
+
const snapshot = env.payload;
|
|
859
|
+
const snapManager = this.snapshots.get(snapshot.docId);
|
|
860
|
+
if (snapManager !== undefined) {
|
|
861
|
+
await snapManager.recibirSnapshot({
|
|
862
|
+
docId: snapshot.docId,
|
|
863
|
+
version: snapshot.version,
|
|
864
|
+
datos: snapshot.datos,
|
|
865
|
+
nodosConfirmados: snapshot.nodosConfirmados,
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
async procesarGovernance(env) {
|
|
870
|
+
const payload = env.payload;
|
|
871
|
+
if (payload.accion === "votar" && payload.voto !== undefined) {
|
|
872
|
+
this.governance.votar(payload.propuesta, payload.voto);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
async procesarAuthz(env) {
|
|
876
|
+
const payload = env.payload;
|
|
877
|
+
// Never accept unauthenticated remote capability grants
|
|
878
|
+
if (this.requireAuthz) {
|
|
879
|
+
const isAdmin = this.authorizer.verificarCapacidad(payload.espacio, env.origen, CAPACIDAD_ESTANDAR.ADMIN);
|
|
880
|
+
const signedOk = !this.requireSignedEnvelopes ||
|
|
881
|
+
(await this.verificarFirmaEnvelope(env));
|
|
882
|
+
if (!isAdmin || !signedOk) {
|
|
883
|
+
// Even without requireSignedEnvelopes, still require admin capability
|
|
884
|
+
// for remote grants when requireAuthz is on.
|
|
885
|
+
if (!isAdmin) {
|
|
886
|
+
this.emit("error", {
|
|
887
|
+
mensaje: `AUTHZ denegado: ${env.origen} no es admin de ${payload.espacio}`,
|
|
888
|
+
});
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
if (payload.accion === "conceder") {
|
|
894
|
+
this.authorizer.concederCapacidad(payload.espacio, payload.sujeto, payload.capacidad);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
async procesarNamespace(env) {
|
|
898
|
+
const payload = env.payload;
|
|
899
|
+
if (payload.accion === "unir") {
|
|
900
|
+
const espacio = this.namespaces.obtenerEspacioPorNombre(payload.espacio);
|
|
901
|
+
if (espacio !== null) {
|
|
902
|
+
this.namespaces.unirNodo(espacio.id, payload.nodoId);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
// ─── EVENTOS DE NODO ────────────────────────────────────────────────
|
|
907
|
+
onNodoConectado(nodoId) {
|
|
908
|
+
this.namespaces.unirNodo(this.namespaces.obtenerEspacioPorNombre("global")?.id ?? "", nodoId);
|
|
909
|
+
}
|
|
910
|
+
onNodoDesconectado(nodoId) {
|
|
911
|
+
const espacios = this.namespaces.obtenerEspaciosDeNodo(nodoId);
|
|
912
|
+
for (const espacio of espacios) {
|
|
913
|
+
this.namespaces.abandonarNodo(espacio.id, nodoId);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
// ─── OP LOG ──────────────────────────────────────────────────────────
|
|
917
|
+
obtenerOLog(docId) {
|
|
918
|
+
const existente = this.logsDoc.get(docId);
|
|
919
|
+
if (existente !== undefined)
|
|
920
|
+
return existente;
|
|
921
|
+
const opLog = new OpLog({
|
|
922
|
+
docId,
|
|
923
|
+
storage: this.storage,
|
|
924
|
+
});
|
|
925
|
+
this.logsDoc.set(docId, opLog);
|
|
926
|
+
return opLog;
|
|
927
|
+
}
|
|
928
|
+
// ─── SYNC ────────────────────────────────────────────────────────────
|
|
929
|
+
obtenerSyncEngine(docId) {
|
|
930
|
+
const existente = this.syncs.get(docId);
|
|
931
|
+
if (existente !== undefined)
|
|
932
|
+
return existente;
|
|
933
|
+
const opLog = this.obtenerOLog(docId);
|
|
934
|
+
const sync = new SyncEngine({
|
|
935
|
+
docId,
|
|
936
|
+
opLog,
|
|
937
|
+
});
|
|
938
|
+
this.syncs.set(docId, sync);
|
|
939
|
+
return sync;
|
|
940
|
+
}
|
|
941
|
+
// ─── SNAPSHOT ────────────────────────────────────────────────────────
|
|
942
|
+
obtenerSnapshotManager(docId) {
|
|
943
|
+
const existente = this.snapshots.get(docId);
|
|
944
|
+
if (existente !== undefined)
|
|
945
|
+
return existente;
|
|
946
|
+
const snap = createSnapshotManager({
|
|
947
|
+
docId,
|
|
948
|
+
storage: this.storage,
|
|
949
|
+
interval: this.config.snapshotInterval,
|
|
950
|
+
});
|
|
951
|
+
this.snapshots.set(docId, snap);
|
|
952
|
+
return snap;
|
|
953
|
+
}
|
|
954
|
+
// ─── SNAPSHOT RECOVERY & COMPACTION ──────────────────────────────────
|
|
955
|
+
async generarSnapshotAutomatico() {
|
|
956
|
+
try {
|
|
957
|
+
const latestSnapshotEntry = await this.storage.get("storage:snapshot:latest");
|
|
958
|
+
const prevSnapshotId = latestSnapshotEntry
|
|
959
|
+
? latestSnapshotEntry.valor.id
|
|
960
|
+
: undefined;
|
|
961
|
+
const grants = Array.from(this.authorizer.obtenerGrantsMap().entries());
|
|
962
|
+
const roleAssignments = Array.from(this.authorizer.obtenerRoleAssignmentsMap().entries());
|
|
963
|
+
let profiles = [];
|
|
964
|
+
if (this.profiles) {
|
|
965
|
+
profiles = this.profiles.exportCache();
|
|
966
|
+
}
|
|
967
|
+
const merkleTree = this.merkleTree;
|
|
968
|
+
const propuestas = this.governance.obtenerPropuestas();
|
|
969
|
+
const governance = {
|
|
970
|
+
propuestas: [...propuestas],
|
|
971
|
+
timestamp: Date.now(),
|
|
972
|
+
};
|
|
973
|
+
const subscriptions = Array.from(this.subscriptions.entries());
|
|
974
|
+
let lastOpSequence = 0;
|
|
975
|
+
for (const opLog of this.logsDoc.values()) {
|
|
976
|
+
lastOpSequence = Math.max(lastOpSequence, opLog.obtenerUltimaSecuencia());
|
|
977
|
+
}
|
|
978
|
+
const id = `snapshot-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`;
|
|
979
|
+
const state = {
|
|
980
|
+
grants,
|
|
981
|
+
roleAssignments,
|
|
982
|
+
profiles,
|
|
983
|
+
merkleTree,
|
|
984
|
+
governance,
|
|
985
|
+
subscriptions,
|
|
986
|
+
lastOpSequence,
|
|
987
|
+
};
|
|
988
|
+
const snapshot = {
|
|
989
|
+
id,
|
|
990
|
+
timestamp: Date.now(),
|
|
991
|
+
state,
|
|
992
|
+
prevSnapshotId,
|
|
993
|
+
};
|
|
994
|
+
if (this.identity) {
|
|
995
|
+
const serialized = canonicalStringify(JSON.parse(JSON.stringify(state)));
|
|
996
|
+
const signatureBytes = await this.identity.firmar(new TextEncoder().encode(serialized));
|
|
997
|
+
snapshot.signature = bytesAHex(signatureBytes);
|
|
998
|
+
}
|
|
999
|
+
await this.storage.set("storage:snapshot:latest", snapshot);
|
|
1000
|
+
const historyEntry = await this.storage.get("storage:snapshot:history");
|
|
1001
|
+
let history = historyEntry ? historyEntry.valor : [];
|
|
1002
|
+
if (!Array.isArray(history)) {
|
|
1003
|
+
history = [];
|
|
1004
|
+
}
|
|
1005
|
+
history.push(snapshot);
|
|
1006
|
+
const maxSnaps = this.snapshotConfig.maxSnapshots || 3;
|
|
1007
|
+
if (history.length > maxSnaps) {
|
|
1008
|
+
history = history.slice(-maxSnaps);
|
|
1009
|
+
}
|
|
1010
|
+
await this.storage.set("storage:snapshot:history", history);
|
|
1011
|
+
if (lastOpSequence > 0) {
|
|
1012
|
+
for (const opLog of this.logsDoc.values()) {
|
|
1013
|
+
await opLog.compactar(lastOpSequence);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
return snapshot;
|
|
1017
|
+
}
|
|
1018
|
+
catch (err) {
|
|
1019
|
+
this.emit("error", {
|
|
1020
|
+
mensaje: `Error al generar snapshot automático: ${err.message}`,
|
|
1021
|
+
});
|
|
1022
|
+
return null;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
async restaurarDesdeSnapshot() {
|
|
1026
|
+
try {
|
|
1027
|
+
const latestEntry = await this.storage.get("storage:snapshot:latest");
|
|
1028
|
+
if (!latestEntry) {
|
|
1029
|
+
await this.reconstruirDesdeOpLogCompleto();
|
|
1030
|
+
return false;
|
|
1031
|
+
}
|
|
1032
|
+
const snapshot = latestEntry.valor;
|
|
1033
|
+
if (snapshot.signature) {
|
|
1034
|
+
const isValid = await this.verificarFirmaSnapshot(snapshot);
|
|
1035
|
+
if (!isValid) {
|
|
1036
|
+
throw new Error("Firma del snapshot invalida o corrupta");
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
await this.aplicarEstadoSnapshot(snapshot);
|
|
1040
|
+
this.snapshotRestored = true;
|
|
1041
|
+
const lastSeq = snapshot.state.lastOpSequence ?? 0;
|
|
1042
|
+
for (const [docId, opLog] of this.logsDoc.entries()) {
|
|
1043
|
+
if (docId === "maloca_profiles" && this.profiles) {
|
|
1044
|
+
await this.profiles.loadProfiles(true);
|
|
1045
|
+
}
|
|
1046
|
+
else if (docId === "maloca_karma" && this.karma) {
|
|
1047
|
+
await this.karma.loadFromOpLog(true);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
return true;
|
|
1051
|
+
}
|
|
1052
|
+
catch (err) {
|
|
1053
|
+
this.emit("error", {
|
|
1054
|
+
mensaje: `Recuperacion desde snapshot fallida, cayendo a OpLog completo: ${err.message}`,
|
|
1055
|
+
});
|
|
1056
|
+
await this.reconstruirDesdeOpLogCompleto();
|
|
1057
|
+
return false;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
async verificarFirmaSnapshot(snapshot) {
|
|
1061
|
+
if (!snapshot.signature)
|
|
1062
|
+
return false;
|
|
1063
|
+
try {
|
|
1064
|
+
const serialized = canonicalStringify(JSON.parse(JSON.stringify(snapshot.state)));
|
|
1065
|
+
const signatureBytes = hexABytes(snapshot.signature);
|
|
1066
|
+
const pubKey = this.identity.exportarPublico();
|
|
1067
|
+
return await this.identity.verificar(new TextEncoder().encode(serialized), signatureBytes, pubKey);
|
|
1068
|
+
}
|
|
1069
|
+
catch {
|
|
1070
|
+
return false;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
async aplicarEstadoSnapshot(snapshot) {
|
|
1074
|
+
if (snapshot.state.grants) {
|
|
1075
|
+
this.authorizer.cargarGrantsMap(snapshot.state.grants);
|
|
1076
|
+
}
|
|
1077
|
+
if (snapshot.state.roleAssignments) {
|
|
1078
|
+
this.authorizer.cargarRoleAssignmentsMap(snapshot.state.roleAssignments);
|
|
1079
|
+
}
|
|
1080
|
+
if (snapshot.state.profiles && this.profiles) {
|
|
1081
|
+
this.profiles.importCache(snapshot.state.profiles);
|
|
1082
|
+
}
|
|
1083
|
+
if (snapshot.state.merkleTree) {
|
|
1084
|
+
const mtData = snapshot.state.merkleTree;
|
|
1085
|
+
if (Array.isArray(mtData.leaves)) {
|
|
1086
|
+
const mt = new MerkleTree(mtData.leaves);
|
|
1087
|
+
if (mtData.signature) {
|
|
1088
|
+
mt.signature = mtData.signature;
|
|
1089
|
+
}
|
|
1090
|
+
this.merkleTree = mt;
|
|
1091
|
+
}
|
|
1092
|
+
else if (mtData instanceof MerkleTree) {
|
|
1093
|
+
this.merkleTree = mtData;
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
if (snapshot.state.governance && snapshot.state.governance.propuestas) {
|
|
1097
|
+
this.governance.importarPropuestas(snapshot.state.governance.propuestas);
|
|
1098
|
+
}
|
|
1099
|
+
if (snapshot.state.subscriptions) {
|
|
1100
|
+
this.subscriptions.clear();
|
|
1101
|
+
for (const [k, v] of snapshot.state.subscriptions) {
|
|
1102
|
+
this.subscriptions.set(k, v);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
async reconstruirDesdeOpLogCompleto() {
|
|
1107
|
+
this.snapshotRestored = false;
|
|
1108
|
+
this.authorizer.cargarGrantsMap([]);
|
|
1109
|
+
this.authorizer.cargarRoleAssignmentsMap([]);
|
|
1110
|
+
if (this.profiles) {
|
|
1111
|
+
this.profiles.importCache([]);
|
|
1112
|
+
}
|
|
1113
|
+
this.merkleTree = new MerkleTree();
|
|
1114
|
+
this.governance.destruir();
|
|
1115
|
+
this.subscriptions.clear();
|
|
1116
|
+
if (this.profiles) {
|
|
1117
|
+
await this.profiles.loadProfiles(false);
|
|
1118
|
+
}
|
|
1119
|
+
if (this.karma) {
|
|
1120
|
+
await this.karma.loadFromOpLog(false);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
// ─── EVENTOS ─────────────────────────────────────────────────────────
|
|
1124
|
+
on(tipo, handler) {
|
|
1125
|
+
this.eventTarget.addEventListener(tipo, handler);
|
|
1126
|
+
}
|
|
1127
|
+
off(tipo, handler) {
|
|
1128
|
+
this.eventTarget.removeEventListener(tipo, handler);
|
|
1129
|
+
}
|
|
1130
|
+
emit(tipo, detalle) {
|
|
1131
|
+
const evento = new CustomEvent(tipo, { detail: detalle });
|
|
1132
|
+
this.eventTarget.dispatchEvent(evento);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
function esEnvolvente(valor) {
|
|
1136
|
+
if (typeof valor !== "object" || valor === null)
|
|
1137
|
+
return false;
|
|
1138
|
+
const candidate = valor;
|
|
1139
|
+
return (typeof candidate.id === "string" &&
|
|
1140
|
+
typeof candidate.tipo === "string" &&
|
|
1141
|
+
typeof candidate.origen === "string" &&
|
|
1142
|
+
typeof candidate.destino === "string" &&
|
|
1143
|
+
typeof candidate.timestamp === "number" &&
|
|
1144
|
+
candidate.payload !== undefined);
|
|
1145
|
+
}
|
|
1146
|
+
//# sourceMappingURL=edge-mesh.js.map
|