fleetmesh-node 0.2.0__py3-none-any.whl
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.
- fleetmesh_node-0.2.0.dist-info/METADATA +57 -0
- fleetmesh_node-0.2.0.dist-info/RECORD +338 -0
- fleetmesh_node-0.2.0.dist-info/WHEEL +5 -0
- fleetmesh_node-0.2.0.dist-info/entry_points.txt +2 -0
- fleetmesh_node-0.2.0.dist-info/licenses/LICENSE +121 -0
- fleetmesh_node-0.2.0.dist-info/top_level.txt +2 -0
- node/RELEASE +1 -0
- node/__init__.py +35 -0
- node/app/__init__.py +49 -0
- node/app/commands.py +265 -0
- node/app/config.py +131 -0
- node/app/control.py +670 -0
- node/app/core.py +867 -0
- node/app/plugins.py +76 -0
- node/app/router.py +83 -0
- node/app/socket.py +431 -0
- node/app/systemd.py +36 -0
- node/app/views.py +204 -0
- node/channels/__init__.py +378 -0
- node/channels/bus.py +331 -0
- node/channels/journal.py +46 -0
- node/commands/__init__.py +140 -0
- node/commands/adapter.py +210 -0
- node/commands/base.py +278 -0
- node/commands/cluster_cmds.py +183 -0
- node/commands/compute_cmds.py +401 -0
- node/commands/core.py +474 -0
- node/commands/gossip_cmds.py +106 -0
- node/commands/nsite_cmds.py +141 -0
- node/commands/peering.py +402 -0
- node/commands/plugin_cmds.py +135 -0
- node/commands/sidecar_cmds.py +120 -0
- node/commands/sync.py +388 -0
- node/commands/transport_cmds.py +451 -0
- node/commands/video_cmds.py +99 -0
- node/compartment/__init__.py +124 -0
- node/compartment/base.py +868 -0
- node/compartment/command.py +458 -0
- node/compartment/context.py +163 -0
- node/compartment/every.py +108 -0
- node/compartment/handler.py +131 -0
- node/compartment/lifecycle.py +460 -0
- node/compartment/lint.py +388 -0
- node/compartment/need.py +158 -0
- node/compartment/problem.py +521 -0
- node/compartment/refusals.py +120 -0
- node/compartment/registry.py +790 -0
- node/compartment/schema.py +290 -0
- node/compartment/sidecar.py +499 -0
- node/compartment/stores.py +662 -0
- node/compartment/wire.py +225 -0
- node/compartment/worker.py +61 -0
- node/config/__init__.py +43 -0
- node/config/base.py +116 -0
- node/config/change.py +441 -0
- node/config/changestore.py +201 -0
- node/config/compartments.py +1144 -0
- node/config/node.py +318 -0
- node/config/parameter.py +563 -0
- node/config/protocols.py +274 -0
- node/config/records.py +171 -0
- node/examples/__init__.py +1 -0
- node/examples/demo.py +226 -0
- node/examples/follower.py +46 -0
- node/interfaces.py +235 -0
- node/keystore/__init__.py +27 -0
- node/keystore/core.py +103 -0
- node/keystore/enclave.py +688 -0
- node/meshnode.py +111 -0
- node/metrics/__init__.py +396 -0
- node/metrics/adapters.py +102 -0
- node/metrics/face.py +110 -0
- node/planes/__init__.py +21 -0
- node/planes/identity/__init__.py +20 -0
- node/planes/identity/announce/__init__.py +9 -0
- node/planes/identity/announce/compartment.py +447 -0
- node/planes/identity/announce/types.py +26 -0
- node/planes/identity/discovery/__init__.py +9 -0
- node/planes/identity/discovery/compartment.py +522 -0
- node/planes/identity/discovery/types.py +13 -0
- node/planes/identity/linked/__init__.py +1 -0
- node/planes/identity/linked/compartment.py +270 -0
- node/planes/identity/linked/types.py +25 -0
- node/planes/identity/presence/__init__.py +7 -0
- node/planes/identity/presence/compartment.py +409 -0
- node/planes/identity/presence/types.py +14 -0
- node/planes/operations/__init__.py +13 -0
- node/planes/operations/scheduler/__init__.py +9 -0
- node/planes/operations/scheduler/compartment.py +353 -0
- node/planes/operations/scheduler/types.py +21 -0
- node/planes/operations/updates/__init__.py +17 -0
- node/planes/operations/updates/core.py +310 -0
- node/planes/operations/updates/store.py +79 -0
- node/planes/operations/updates/types.py +20 -0
- node/planes/peering/__init__.py +28 -0
- node/planes/peering/budget/__init__.py +43 -0
- node/planes/peering/budget/chain.py +43 -0
- node/planes/peering/budget/core.py +537 -0
- node/planes/peering/budget/evaluation.py +80 -0
- node/planes/peering/budget/meter.py +100 -0
- node/planes/peering/budget/receipts.py +439 -0
- node/planes/peering/budget/shape.py +101 -0
- node/planes/peering/budget/tick.py +110 -0
- node/planes/peering/budget/types.py +67 -0
- node/planes/peering/budget/windows.py +346 -0
- node/planes/peering/capacity/__init__.py +7 -0
- node/planes/peering/capacity/compartment.py +342 -0
- node/planes/peering/capacity/types.py +24 -0
- node/planes/peering/peering/__init__.py +25 -0
- node/planes/peering/peering/core.py +464 -0
- node/planes/peering/peering/envelope.py +142 -0
- node/planes/peering/peering/handshake.py +198 -0
- node/planes/peering/peering/shape.py +80 -0
- node/planes/peering/peering/types.py +39 -0
- node/planes/peering/reporter/__init__.py +42 -0
- node/planes/peering/reporter/constants.py +29 -0
- node/planes/peering/reporter/core.py +299 -0
- node/planes/peering/reporter/finding.py +74 -0
- node/planes/peering/reporter/findings.py +171 -0
- node/planes/peering/reporter/publish.py +285 -0
- node/planes/peering/reporter/reading.py +193 -0
- node/planes/peering/reporter/shape.py +86 -0
- node/planes/peering/reporter/types.py +50 -0
- node/planes/peering/strain/__init__.py +9 -0
- node/planes/peering/strain/compartment.py +204 -0
- node/planes/peering/strain/types.py +21 -0
- node/planes/peering/trust/__init__.py +9 -0
- node/planes/peering/trust/compartment.py +239 -0
- node/planes/peering/trust/types.py +30 -0
- node/planes/services/__init__.py +33 -0
- node/planes/services/compute/__init__.py +160 -0
- node/planes/services/compute/common.py +134 -0
- node/planes/services/compute/compartment.py +681 -0
- node/planes/services/compute/executors/__init__.py +121 -0
- node/planes/services/compute/executors/agent.py +840 -0
- node/planes/services/compute/executors/bacalhau.py +605 -0
- node/planes/services/compute/executors/base.py +127 -0
- node/planes/services/compute/executors/harness.py +439 -0
- node/planes/services/compute/executors/mcp.py +454 -0
- node/planes/services/compute/executors/media.py +898 -0
- node/planes/services/compute/executors/nondet.py +283 -0
- node/planes/services/compute/executors/providers.py +499 -0
- node/planes/services/compute/executors/registry.py +140 -0
- node/planes/services/compute/executors/wasm.py +244 -0
- node/planes/services/compute/executors/zk.py +604 -0
- node/planes/services/compute/jobs.py +227 -0
- node/planes/services/compute/load.py +180 -0
- node/planes/services/compute/market.py +433 -0
- node/planes/services/compute/sandbox.py +282 -0
- node/planes/services/compute/types.py +84 -0
- node/planes/services/compute_wire/__init__.py +58 -0
- node/planes/services/compute_wire/compartment.py +256 -0
- node/planes/services/compute_wire/messages.py +320 -0
- node/planes/services/compute_wire/sealing.py +147 -0
- node/planes/services/compute_wire/session.py +572 -0
- node/planes/services/compute_wire/types.py +49 -0
- node/planes/services/compute_wire/verify.py +331 -0
- node/planes/services/dvm/__init__.py +24 -0
- node/planes/services/dvm/gateway.py +526 -0
- node/planes/services/dvm/protocol.py +35 -0
- node/planes/services/dvm/translate.py +15 -0
- node/planes/services/dvm/types.py +25 -0
- node/planes/services/gateway/__init__.py +40 -0
- node/planes/services/gateway/settle.py +279 -0
- node/planes/services/gateway/translate.py +644 -0
- node/planes/services/gossip/__init__.py +22 -0
- node/planes/services/gossip/core.py +929 -0
- node/planes/services/gossip/envelope.py +154 -0
- node/planes/services/gossip/mesh.py +127 -0
- node/planes/services/gossip/types.py +50 -0
- node/planes/services/nsite/__init__.py +7 -0
- node/planes/services/nsite/client.py +62 -0
- node/planes/services/nsite/compartment.py +752 -0
- node/planes/services/nsite/types.py +44 -0
- node/planes/services/paidfetch/__init__.py +7 -0
- node/planes/services/paidfetch/compartment.py +862 -0
- node/planes/services/paidfetch/types.py +99 -0
- node/planes/services/settle/__init__.py +27 -0
- node/planes/services/settle/compartment.py +384 -0
- node/planes/services/settle/engine.py +456 -0
- node/planes/services/settle/gateways.py +541 -0
- node/planes/services/settle/payment.py +512 -0
- node/planes/services/settle/store.py +74 -0
- node/planes/services/settle/types.py +58 -0
- node/planes/services/sidecars/__init__.py +8 -0
- node/planes/services/sidecars/compartment.py +461 -0
- node/planes/services/sidecars/manifest.py +304 -0
- node/planes/services/sidecars/manifests/10-grasp.json +37 -0
- node/planes/services/sidecars/manifests/20-ncontainer.json +31 -0
- node/planes/services/sidecars/manifests/30-magpie.json +28 -0
- node/planes/services/sidecars/types.py +43 -0
- node/planes/services/video/__init__.py +106 -0
- node/planes/services/video/barter.py +407 -0
- node/planes/services/video/cli.py +158 -0
- node/planes/services/video/compartment.py +932 -0
- node/planes/services/video/ingest.py +524 -0
- node/planes/services/video/ledger.py +705 -0
- node/planes/services/video/packager.py +847 -0
- node/planes/services/video/player.py +456 -0
- node/planes/services/video/timed_segments.py +124 -0
- node/planes/services/video/transit.py +478 -0
- node/planes/store/__init__.py +24 -0
- node/planes/store/blob_replication/__init__.py +7 -0
- node/planes/store/blob_replication/compartment.py +496 -0
- node/planes/store/blob_replication/types.py +35 -0
- node/planes/store/blob_wire/__init__.py +48 -0
- node/planes/store/blob_wire/client.py +57 -0
- node/planes/store/blob_wire/core.py +359 -0
- node/planes/store/blob_wire/intake.py +74 -0
- node/planes/store/blob_wire/messages.py +85 -0
- node/planes/store/projected/__init__.py +7 -0
- node/planes/store/projected/compartment.py +258 -0
- node/planes/store/projected/types.py +29 -0
- node/planes/store/retention/__init__.py +7 -0
- node/planes/store/retention/compartment.py +214 -0
- node/planes/store/retention/types.py +12 -0
- node/planes/store/sync/__init__.py +35 -0
- node/planes/store/sync/core.py +311 -0
- node/planes/store/sync/envelope.py +106 -0
- node/planes/store/sync/handover.py +206 -0
- node/planes/store/sync/initiator.py +128 -0
- node/planes/store/sync/responder.py +114 -0
- node/planes/store/sync/rules.py +179 -0
- node/planes/store/sync/shape.py +80 -0
- node/planes/store/sync/types.py +51 -0
- node/planes/transport/__init__.py +23 -0
- node/planes/transport/bridge/__init__.py +13 -0
- node/planes/transport/bridge/compartment.py +785 -0
- node/planes/transport/bridge/ratelimit.py +44 -0
- node/planes/transport/bridge/types.py +43 -0
- node/planes/transport/lan/__init__.py +9 -0
- node/planes/transport/lan/compartment.py +446 -0
- node/planes/transport/lan/types.py +25 -0
- node/planes/transport/link/__init__.py +7 -0
- node/planes/transport/link/compartment.py +257 -0
- node/planes/transport/link/types.py +27 -0
- node/planes/transport/mediation/__init__.py +45 -0
- node/planes/transport/mediation/compartment.py +238 -0
- node/planes/transport/mediation/leaf.py +107 -0
- node/planes/transport/mediation/messages.py +302 -0
- node/planes/transport/mediation/service.py +233 -0
- node/planes/transport/mediation/store.py +37 -0
- node/planes/transport/mediation/types.py +20 -0
- node/planes/transport/p2p/__init__.py +7 -0
- node/planes/transport/p2p/compartment.py +349 -0
- node/planes/transport/p2p/types.py +22 -0
- node/protocols/__init__.py +277 -0
- node/protocols/activitypub/__init__.py +56 -0
- node/protocols/activitypub/actor.py +230 -0
- node/protocols/activitypub/federation.py +490 -0
- node/protocols/activitypub/media.py +145 -0
- node/protocols/activitypub/models.py +286 -0
- node/protocols/activitypub/package.py +271 -0
- node/protocols/activitypub/store.py +564 -0
- node/protocols/arcade/__init__.py +26 -0
- node/protocols/atproto/__init__.py +13 -0
- node/protocols/atproto/firehose.py +126 -0
- node/protocols/atproto/metering.py +165 -0
- node/protocols/atproto/package.py +155 -0
- node/protocols/atproto/repo.py +250 -0
- node/protocols/atproto/xrpc.py +285 -0
- node/protocols/base.py +109 -0
- node/protocols/circles/__init__.py +29 -0
- node/protocols/dm/__init__.py +50 -0
- node/protocols/grasp/__init__.py +22 -0
- node/protocols/isotime.py +15 -0
- node/protocols/nostr/__init__.py +51 -0
- node/protocols/nostr/blobstore.py +383 -0
- node/protocols/nostr/blossom.py +627 -0
- node/protocols/nostr/client.py +377 -0
- node/protocols/nostr/descriptor.py +211 -0
- node/protocols/nostr/eventstore.py +304 -0
- node/protocols/nostr/package.py +182 -0
- node/protocols/nostr/peers.py +109 -0
- node/protocols/nostr/relayserver.py +656 -0
- node/protocols/p2panda/__init__.py +22 -0
- node/protocols/p2panda/identity.py +19 -0
- node/protocols/p2panda/package.py +236 -0
- node/protocols/p2panda/rung.py +152 -0
- node/protocols/p2panda/store.py +273 -0
- node/protocols/rhio/__init__.py +33 -0
- node/protocols/rhio/package.py +275 -0
- node/protocols/rhio/rung.py +136 -0
- node/protocols/rhio/store.py +1423 -0
- node/protocols/ssb/muxrpc.py +256 -0
- node/protocols/ssb/package.py +267 -0
- node/protocols/textile/__init__.py +111 -0
- node/protocols/textile/transducers.py +259 -0
- node/release.py +119 -0
- node/storage/__init__.py +88 -0
- node/storage/accounts.py +305 -0
- node/storage/adapter.py +161 -0
- node/storage/base.py +385 -0
- node/storage/dialect.py +209 -0
- node/storage/grants.py +317 -0
- node/storage/locator.py +150 -0
- node/storage/postgres.py +294 -0
- node/storage/projected.py +953 -0
- node/storage/sharding.py +448 -0
- node/storage/standing.py +568 -0
- node/testnet/__init__.py +9 -0
- node/testnet/cluster.py +731 -0
- node/testnet/fips_daemon.py +230 -0
- node/transport/__init__.py +94 -0
- node/transport/const.py +110 -0
- node/transport/didcomm_inbound.py +251 -0
- node/transport/dnstxt.py +169 -0
- node/transport/fips_rung/__init__.py +67 -0
- node/transport/fips_rung/core.py +666 -0
- node/transport/fips_rung/listener.py +96 -0
- node/transport/fips_rung/types.py +12 -0
- node/transport/host.py +334 -0
- node/transport/iroh_rung/__init__.py +29 -0
- node/transport/iroh_rung/core.py +477 -0
- node/transport/iroh_rung/quic.py +942 -0
- node/transport/iroh_rung/types.py +22 -0
- node/transport/ladder.py +145 -0
- node/transport/lineproto.py +49 -0
- node/transport/mdns.py +303 -0
- node/transport/netfetch.py +283 -0
- node/transport/relay_rung/__init__.py +23 -0
- node/transport/relay_rung/cooldown.py +87 -0
- node/transport/relay_rung/core.py +138 -0
- node/transport/relay_rung/inbound.py +161 -0
- node/transport/relay_rung/ledger.py +104 -0
- node/transport/relay_rung/listen.py +121 -0
- node/transport/relay_rung/outbound.py +181 -0
- node/transport/relay_rung/shape.py +71 -0
- node/transport/relay_rung/types.py +58 -0
- node/transport/relay_rung/wire.py +63 -0
- node/transport/router.py +761 -0
- node/transport/rung.py +276 -0
- node/transport/ssb_rung.py +166 -0
- node/transport/ws.py +502 -0
- sidecar/fleetmesh_announce.py +338 -0
- sidecar/fleetmesh_p2p.py +373 -0
- sidecar/fleetmesh_rhio.py +352 -0
- sidecar/fleetmesh_ssb.py +603 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"""AT Protocol XRPC (XML-less RPC) server and endpoint router.
|
|
2
|
+
|
|
3
|
+
Standard library only. Routes XRPC queries (GET) and procedures (POST)
|
|
4
|
+
under `/xrpc/<nsid>`, handling core ATProto Lexicons:
|
|
5
|
+
- com.atproto.server.describeServer
|
|
6
|
+
- com.atproto.repo.describeRepo
|
|
7
|
+
- com.atproto.repo.getRecord
|
|
8
|
+
- com.atproto.repo.listRecords
|
|
9
|
+
- com.atproto.repo.createRecord
|
|
10
|
+
- com.atproto.repo.deleteRecord
|
|
11
|
+
- com.atproto.sync.getRepo
|
|
12
|
+
- com.atproto.sync.getLatestCommit
|
|
13
|
+
- com.atproto.sync.getBlob
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import re
|
|
19
|
+
import urllib.parse
|
|
20
|
+
from typing import Callable, Mapping, Optional
|
|
21
|
+
|
|
22
|
+
from fleetmesh_core.conformance.errors import FleetmeshError
|
|
23
|
+
from .repo import Repo
|
|
24
|
+
|
|
25
|
+
VALID_COLLECTION_RE = re.compile(r'^[a-zA-Z0-9.-]{1,512}$')
|
|
26
|
+
VALID_RKEY_RE = re.compile(r'^[a-zA-Z0-9._~-]{1,512}$')
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
'XRPCError',
|
|
30
|
+
'XRPCRouter',
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class XRPCError(FleetmeshError):
|
|
35
|
+
"""Represents an XRPC error with HTTP status and Lexicon error string."""
|
|
36
|
+
|
|
37
|
+
def __init__(self, status: int, error: str, message: str):
|
|
38
|
+
super().__init__(f"{error}: {message}")
|
|
39
|
+
self.status = status
|
|
40
|
+
self.error = error
|
|
41
|
+
self.message = message
|
|
42
|
+
|
|
43
|
+
def to_dict(self) -> dict[str, str]:
|
|
44
|
+
return {'error': self.error, 'message': self.message}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class XRPCRouter:
|
|
48
|
+
"""Dispatches XRPC queries and procedures to registered Lexicon handlers."""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
repo: Optional[Repo] = None,
|
|
53
|
+
service_domain: str = "node.fleetmesh.local",
|
|
54
|
+
):
|
|
55
|
+
self.repo = repo
|
|
56
|
+
self.service_domain = service_domain
|
|
57
|
+
self._queries: dict[str, Callable[[dict[str, list[str]], bytes], tuple[int, str, bytes]]] = {}
|
|
58
|
+
self._procedures: dict[str, Callable[[dict[str, list[str]], bytes], tuple[int, str, bytes]]] = {}
|
|
59
|
+
self._register_default_lexicons()
|
|
60
|
+
|
|
61
|
+
def register_query(
|
|
62
|
+
self,
|
|
63
|
+
nsid: str,
|
|
64
|
+
handler: Callable[[dict[str, list[str]], bytes], tuple[int, str, bytes]],
|
|
65
|
+
) -> None:
|
|
66
|
+
"""Register a GET XRPC query handler."""
|
|
67
|
+
self._queries[nsid] = handler
|
|
68
|
+
|
|
69
|
+
def register_procedure(
|
|
70
|
+
self,
|
|
71
|
+
nsid: str,
|
|
72
|
+
handler: Callable[[dict[str, list[str]], bytes], tuple[int, str, bytes]],
|
|
73
|
+
) -> None:
|
|
74
|
+
"""Register a POST XRPC procedure handler."""
|
|
75
|
+
self._procedures[nsid] = handler
|
|
76
|
+
|
|
77
|
+
def _register_default_lexicons(self) -> None:
|
|
78
|
+
self.register_query('com.atproto.server.describeServer', self._describe_server)
|
|
79
|
+
self.register_query('com.atproto.repo.describeRepo', self._describe_repo)
|
|
80
|
+
self.register_query('com.atproto.repo.getRecord', self._get_record)
|
|
81
|
+
self.register_query('com.atproto.repo.listRecords', self._list_records)
|
|
82
|
+
self.register_procedure('com.atproto.repo.createRecord', self._create_record)
|
|
83
|
+
self.register_procedure('com.atproto.repo.deleteRecord', self._delete_record)
|
|
84
|
+
self.register_query('com.atproto.sync.getRepo', self._get_repo_car)
|
|
85
|
+
self.register_query('com.atproto.sync.getLatestCommit', self._get_latest_commit)
|
|
86
|
+
self.register_query('com.atproto.sync.getBlob', self._get_blob)
|
|
87
|
+
|
|
88
|
+
def handle_request(
|
|
89
|
+
self,
|
|
90
|
+
method: str,
|
|
91
|
+
raw_path: str,
|
|
92
|
+
body: bytes = b"",
|
|
93
|
+
) -> tuple[int, Mapping[str, str], bytes]:
|
|
94
|
+
"""Dispatch HTTP request to XRPC handler.
|
|
95
|
+
|
|
96
|
+
Returns (status_code, headers_dict, body_bytes).
|
|
97
|
+
"""
|
|
98
|
+
parsed = urllib.parse.urlparse(raw_path)
|
|
99
|
+
path = parsed.path
|
|
100
|
+
if not path.startswith('/xrpc/'):
|
|
101
|
+
return 404, {'Content-Type': 'application/json'}, json.dumps({'error': 'NotFound', 'message': 'Not an XRPC endpoint'}).encode()
|
|
102
|
+
|
|
103
|
+
nsid = path[len('/xrpc/'):].strip('/')
|
|
104
|
+
params = urllib.parse.parse_qs(parsed.query)
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
if method == 'GET':
|
|
108
|
+
if nsid not in self._queries:
|
|
109
|
+
return 404, {'Content-Type': 'application/json'}, json.dumps({'error': 'MethodNotImplemented', 'message': f"Query {nsid} not implemented"}).encode()
|
|
110
|
+
status, content_type, resp_body = self._queries[nsid](params, body)
|
|
111
|
+
return status, {'Content-Type': content_type}, resp_body
|
|
112
|
+
elif method == 'POST':
|
|
113
|
+
if nsid not in self._procedures:
|
|
114
|
+
return 404, {'Content-Type': 'application/json'}, json.dumps({'error': 'MethodNotImplemented', 'message': f"Procedure {nsid} not implemented"}).encode()
|
|
115
|
+
status, content_type, resp_body = self._procedures[nsid](params, body)
|
|
116
|
+
return status, {'Content-Type': content_type}, resp_body
|
|
117
|
+
else:
|
|
118
|
+
return 405, {'Allow': 'GET, POST', 'Content-Type': 'application/json'}, json.dumps({'error': 'MethodNotAllowed', 'message': f"HTTP {method} not allowed"}).encode()
|
|
119
|
+
except XRPCError as e:
|
|
120
|
+
return e.status, {'Content-Type': 'application/json'}, json.dumps(e.to_dict()).encode()
|
|
121
|
+
except Exception as e:
|
|
122
|
+
return 500, {'Content-Type': 'application/json'}, json.dumps({'error': 'InternalServerError', 'message': str(e)}).encode()
|
|
123
|
+
|
|
124
|
+
# --- Core Lexicon Handlers ---
|
|
125
|
+
|
|
126
|
+
def _describe_server(self, params: dict[str, list[str]], body: bytes) -> tuple[int, str, bytes]:
|
|
127
|
+
data = {
|
|
128
|
+
'availableUserDomains': [self.service_domain],
|
|
129
|
+
'inviteCodeRequired': False,
|
|
130
|
+
'links': {
|
|
131
|
+
'privacyPolicy': f"https://{self.service_domain}/privacy",
|
|
132
|
+
'termsOfService': f"https://{self.service_domain}/tos",
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
return 200, 'application/json', json.dumps(data).encode()
|
|
136
|
+
|
|
137
|
+
def _describe_repo(self, params: dict[str, list[str]], body: bytes) -> tuple[int, str, bytes]:
|
|
138
|
+
if self.repo is None:
|
|
139
|
+
raise XRPCError(404, 'RepoNotFound', 'No repository hosted on this node')
|
|
140
|
+
repo_param = params.get('repo', [self.repo.did])[0]
|
|
141
|
+
if repo_param != self.repo.did:
|
|
142
|
+
raise XRPCError(404, 'RepoNotFound', f"Repo {repo_param} not found")
|
|
143
|
+
|
|
144
|
+
records = self.repo.list_records()
|
|
145
|
+
collections = sorted(list(set(c for c, _, _, _ in records)))
|
|
146
|
+
data = {
|
|
147
|
+
'did': self.repo.did,
|
|
148
|
+
'handle': self.service_domain,
|
|
149
|
+
'didDoc': None,
|
|
150
|
+
'collections': collections,
|
|
151
|
+
'handleIsCorrect': True,
|
|
152
|
+
}
|
|
153
|
+
return 200, 'application/json', json.dumps(data).encode()
|
|
154
|
+
|
|
155
|
+
def _get_record(self, params: dict[str, list[str]], body: bytes) -> tuple[int, str, bytes]:
|
|
156
|
+
repo_val = params.get('repo', [''])[0]
|
|
157
|
+
col_val = params.get('collection', [''])[0]
|
|
158
|
+
rkey_val = params.get('rkey', [''])[0]
|
|
159
|
+
|
|
160
|
+
if not col_val or not rkey_val:
|
|
161
|
+
raise XRPCError(400, 'InvalidRequest', 'Missing required params: collection and rkey')
|
|
162
|
+
if not VALID_COLLECTION_RE.match(col_val):
|
|
163
|
+
raise XRPCError(400, 'InvalidRequest', f"Malformed collection format: {col_val}")
|
|
164
|
+
if not VALID_RKEY_RE.match(rkey_val):
|
|
165
|
+
raise XRPCError(400, 'InvalidRequest', f"Malformed rkey format: {rkey_val}")
|
|
166
|
+
|
|
167
|
+
if self.repo is None:
|
|
168
|
+
raise XRPCError(404, 'RepoNotFound', 'No repository hosted on this node')
|
|
169
|
+
if repo_val and repo_val != self.repo.did:
|
|
170
|
+
raise XRPCError(404, 'RepoNotFound', f"Unknown repo: {repo_val}")
|
|
171
|
+
|
|
172
|
+
record_info = self.repo.get_record(col_val, rkey_val)
|
|
173
|
+
if record_info is None:
|
|
174
|
+
raise XRPCError(404, 'RecordNotFound', f"Record {col_val}/{rkey_val} not found")
|
|
175
|
+
|
|
176
|
+
rec_cid, rec_doc = record_info
|
|
177
|
+
out = {
|
|
178
|
+
'uri': f"at://{self.repo.did}/{col_val}/{rkey_val}",
|
|
179
|
+
'cid': rec_cid.cid_str,
|
|
180
|
+
'value': rec_doc,
|
|
181
|
+
}
|
|
182
|
+
return 200, 'application/json', json.dumps(out).encode()
|
|
183
|
+
|
|
184
|
+
def _list_records(self, params: dict[str, list[str]], body: bytes) -> tuple[int, str, bytes]:
|
|
185
|
+
col_val = params.get('collection', [''])[0]
|
|
186
|
+
if not col_val:
|
|
187
|
+
raise XRPCError(400, 'InvalidRequest', 'Missing required param: collection')
|
|
188
|
+
if not VALID_COLLECTION_RE.match(col_val):
|
|
189
|
+
raise XRPCError(400, 'InvalidRequest', f"Malformed collection format: {col_val}")
|
|
190
|
+
|
|
191
|
+
if self.repo is None:
|
|
192
|
+
raise XRPCError(404, 'RepoNotFound', 'No repository hosted on this node')
|
|
193
|
+
|
|
194
|
+
limit = 50
|
|
195
|
+
if 'limit' in params:
|
|
196
|
+
try:
|
|
197
|
+
limit = min(100, max(1, int(params['limit'][0])))
|
|
198
|
+
except ValueError:
|
|
199
|
+
pass
|
|
200
|
+
|
|
201
|
+
records = self.repo.list_records(col_val)[:limit]
|
|
202
|
+
out_records = []
|
|
203
|
+
for col, rkey, cid, doc in records:
|
|
204
|
+
out_records.append({
|
|
205
|
+
'uri': f"at://{self.repo.did}/{col}/{rkey}",
|
|
206
|
+
'cid': cid.cid_str,
|
|
207
|
+
'value': doc,
|
|
208
|
+
})
|
|
209
|
+
return 200, 'application/json', json.dumps({'records': out_records}).encode()
|
|
210
|
+
|
|
211
|
+
def _create_record(self, params: dict[str, list[str]], body: bytes) -> tuple[int, str, bytes]:
|
|
212
|
+
try:
|
|
213
|
+
req_data = json.loads(body.decode('utf-8'))
|
|
214
|
+
except Exception:
|
|
215
|
+
raise XRPCError(400, 'InvalidJson', 'Invalid JSON body')
|
|
216
|
+
|
|
217
|
+
col = req_data.get('collection')
|
|
218
|
+
rec = req_data.get('record')
|
|
219
|
+
rkey = req_data.get('rkey')
|
|
220
|
+
if not col or not rec:
|
|
221
|
+
raise XRPCError(400, 'InvalidRequest', 'Missing collection or record in body')
|
|
222
|
+
if not VALID_COLLECTION_RE.match(col):
|
|
223
|
+
raise XRPCError(400, 'InvalidRequest', f"Malformed collection format: {col}")
|
|
224
|
+
if rkey is not None and not VALID_RKEY_RE.match(rkey):
|
|
225
|
+
raise XRPCError(400, 'InvalidRequest', f"Malformed rkey format: {rkey}")
|
|
226
|
+
|
|
227
|
+
if self.repo is None:
|
|
228
|
+
raise XRPCError(404, 'RepoNotFound', 'No repository hosted on this node')
|
|
229
|
+
|
|
230
|
+
assigned_rkey, rec_cid, commit_cid = self.repo.put_record(col, rec, rkey=rkey)
|
|
231
|
+
out = {
|
|
232
|
+
'uri': f"at://{self.repo.did}/{col}/{assigned_rkey}",
|
|
233
|
+
'cid': rec_cid.cid_str,
|
|
234
|
+
'commit': {'cid': commit_cid.cid_str, 'rev': self.repo.head_commit_cid.cid_str if self.repo.head_commit_cid else ''},
|
|
235
|
+
}
|
|
236
|
+
return 200, 'application/json', json.dumps(out).encode()
|
|
237
|
+
|
|
238
|
+
def _delete_record(self, params: dict[str, list[str]], body: bytes) -> tuple[int, str, bytes]:
|
|
239
|
+
try:
|
|
240
|
+
req_data = json.loads(body.decode('utf-8'))
|
|
241
|
+
except Exception:
|
|
242
|
+
raise XRPCError(400, 'InvalidJson', 'Invalid JSON body')
|
|
243
|
+
|
|
244
|
+
col = req_data.get('collection')
|
|
245
|
+
rkey = req_data.get('rkey')
|
|
246
|
+
if not col or not rkey:
|
|
247
|
+
raise XRPCError(400, 'InvalidRequest', 'Missing collection or rkey in body')
|
|
248
|
+
if not VALID_COLLECTION_RE.match(col):
|
|
249
|
+
raise XRPCError(400, 'InvalidRequest', f"Malformed collection format: {col}")
|
|
250
|
+
if not VALID_RKEY_RE.match(rkey):
|
|
251
|
+
raise XRPCError(400, 'InvalidRequest', f"Malformed rkey format: {rkey}")
|
|
252
|
+
|
|
253
|
+
if self.repo is None:
|
|
254
|
+
raise XRPCError(404, 'RepoNotFound', 'No repository hosted on this node')
|
|
255
|
+
|
|
256
|
+
commit_cid = self.repo.delete_record(col, rkey)
|
|
257
|
+
out = {'commit': {'cid': commit_cid.cid_str}}
|
|
258
|
+
return 200, 'application/json', json.dumps(out).encode()
|
|
259
|
+
|
|
260
|
+
def _get_repo_car(self, params: dict[str, list[str]], body: bytes) -> tuple[int, str, bytes]:
|
|
261
|
+
if self.repo is None:
|
|
262
|
+
raise XRPCError(404, 'RepoNotFound', 'No repository hosted on this node')
|
|
263
|
+
car_bytes = self.repo.export_car()
|
|
264
|
+
return 200, 'application/vnd.ipld.car', car_bytes
|
|
265
|
+
|
|
266
|
+
def _get_latest_commit(self, params: dict[str, list[str]], body: bytes) -> tuple[int, str, bytes]:
|
|
267
|
+
if self.repo is None or self.repo.head_commit_cid is None:
|
|
268
|
+
raise XRPCError(404, 'RepoNotFound', 'Repository has no commits')
|
|
269
|
+
out = {
|
|
270
|
+
'cid': self.repo.head_commit_cid.cid_str,
|
|
271
|
+
'rev': self.repo.head_commit_cid.cid_str,
|
|
272
|
+
}
|
|
273
|
+
return 200, 'application/json', json.dumps(out).encode()
|
|
274
|
+
|
|
275
|
+
def _get_blob(self, params: dict[str, list[str]], body: bytes) -> tuple[int, str, bytes]:
|
|
276
|
+
if self.repo is None:
|
|
277
|
+
raise XRPCError(404, 'RepoNotFound', 'No repository hosted on this node')
|
|
278
|
+
cid_str = params.get('cid', [''])[0]
|
|
279
|
+
if not cid_str:
|
|
280
|
+
raise XRPCError(400, 'InvalidRequest', 'Missing cid param')
|
|
281
|
+
|
|
282
|
+
data = self.repo.get_blob(cid_str)
|
|
283
|
+
if data is None:
|
|
284
|
+
raise XRPCError(404, 'BlobNotFound', f"Blob {cid_str} not found")
|
|
285
|
+
return 200, 'application/octet-stream', data
|
node/protocols/base.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Shared base classes for Fleetmesh protocol adapters."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any, Optional, TypeVar
|
|
5
|
+
|
|
6
|
+
from cryptography.exceptions import InvalidSignature
|
|
7
|
+
from cryptography.hazmat.primitives.asymmetric import ed25519
|
|
8
|
+
|
|
9
|
+
from fleetmesh_core.crypto import keys
|
|
10
|
+
from fleetmesh_core.wire import spelling
|
|
11
|
+
from fleetmesh_core.wire.interfaces import ProtocolIdentityAdapter
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
'BaseProtocolPackage',
|
|
15
|
+
'Ed25519IdentityAdapter',
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
T = TypeVar('T', bound='Ed25519IdentityAdapter')
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BaseProtocolPackage:
|
|
22
|
+
"""Shared base class for Fleetmesh protocol packages."""
|
|
23
|
+
|
|
24
|
+
store: Any = None
|
|
25
|
+
|
|
26
|
+
def stop(self) -> None:
|
|
27
|
+
"""Close any attached protocol store when stopping the package."""
|
|
28
|
+
store_obj = getattr(self, 'store', None)
|
|
29
|
+
if store_obj is not None and hasattr(store_obj, 'close'):
|
|
30
|
+
store_obj.close()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Ed25519IdentityAdapter(ProtocolIdentityAdapter):
|
|
34
|
+
"""Shared Ed25519 identity and assertion adapter for protocol packages."""
|
|
35
|
+
|
|
36
|
+
uri_prefixes: tuple[str, ...] = ()
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
private_key: Any = None,
|
|
41
|
+
public_key_hex: Optional[str] = None,
|
|
42
|
+
) -> None:
|
|
43
|
+
self.private_key = private_key
|
|
44
|
+
self._private_key = private_key
|
|
45
|
+
if private_key is not None and hasattr(private_key, 'public_key'):
|
|
46
|
+
self._public_key_hex = private_key.public_key().public_bytes_raw().hex()
|
|
47
|
+
else:
|
|
48
|
+
self._public_key_hex = public_key_hex.lower() if public_key_hex else ""
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def author_id(self) -> str:
|
|
52
|
+
"""The canonical 64-character lowercase hex author identifier."""
|
|
53
|
+
return self._public_key_hex
|
|
54
|
+
|
|
55
|
+
def resolve_id(self, uri: str) -> Optional[str]:
|
|
56
|
+
"""Resolve a protocol URI or raw 64-char hex public key to a canonical ID."""
|
|
57
|
+
clean = uri.strip()
|
|
58
|
+
for prefix in self.uri_prefixes:
|
|
59
|
+
if clean.startswith(prefix):
|
|
60
|
+
clean = clean[len(prefix):]
|
|
61
|
+
break
|
|
62
|
+
if spelling.is_hex(clean, 64, upper=True):
|
|
63
|
+
return clean.lower()
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
def sign_assertion(self, payload: bytes) -> bytes:
|
|
67
|
+
"""Sign payload bytes using the configured Ed25519 private key."""
|
|
68
|
+
key = self.private_key if self.private_key is not None else self._private_key
|
|
69
|
+
if key is None:
|
|
70
|
+
raise RuntimeError("Private key not configured for signing assertions")
|
|
71
|
+
if hasattr(key, 'sign'):
|
|
72
|
+
return key.sign(payload)
|
|
73
|
+
k = ed25519.Ed25519PrivateKey.from_private_bytes(bytes(key))
|
|
74
|
+
return k.sign(payload)
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
def _verify_ed25519_bytes(payload: bytes, signature: bytes, raw_pub: bytes) -> bool:
|
|
78
|
+
try:
|
|
79
|
+
verifier = ed25519.Ed25519PublicKey.from_public_bytes(raw_pub)
|
|
80
|
+
verifier.verify(signature, payload)
|
|
81
|
+
return True
|
|
82
|
+
except (InvalidSignature, ValueError, TypeError):
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
def verify_assertion(self, payload: bytes, signature: bytes, pubkey: str) -> bool:
|
|
86
|
+
"""Verify an assertion signature against an Ed25519 public key."""
|
|
87
|
+
resolved = self.resolve_id(pubkey)
|
|
88
|
+
if resolved is None:
|
|
89
|
+
return False
|
|
90
|
+
try:
|
|
91
|
+
raw_pub = bytes.fromhex(resolved)
|
|
92
|
+
except ValueError:
|
|
93
|
+
return False
|
|
94
|
+
return self._verify_ed25519_bytes(payload, signature, raw_pub)
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def from_keystore(cls: type[T], keystore: Any) -> T:
|
|
98
|
+
"""Construct adapter from a Fleetmesh Keystore instance."""
|
|
99
|
+
if hasattr(keystore, 'transport_seed') and keystore.transport_seed:
|
|
100
|
+
priv, _ = keys.transport_keypair(keystore.transport_seed)
|
|
101
|
+
return cls(private_key=priv)
|
|
102
|
+
if hasattr(keystore, 'transport_priv') and keystore.transport_priv is not None:
|
|
103
|
+
return cls(private_key=keystore.transport_priv)
|
|
104
|
+
if hasattr(keystore, 'ed25519_sk') and keystore.ed25519_sk is not None:
|
|
105
|
+
return cls(private_key=keystore.ed25519_sk)
|
|
106
|
+
if hasattr(keystore, 'transport_pub') and keystore.transport_pub:
|
|
107
|
+
raw_pub = keys.raw_public(keystore.transport_pub)
|
|
108
|
+
return cls(public_key_hex=raw_pub.hex())
|
|
109
|
+
return cls()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""FleetMesh Node Circles Protocol Package (`node.protocols.circles`)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
# The stores and route handlers live in the fleetmesh-protocols package
|
|
5
|
+
# (fleetmesh_protocols.circles); this package re-exports them so node code and
|
|
6
|
+
# the protocol router keep their import paths.
|
|
7
|
+
|
|
8
|
+
from fleetmesh_protocols.circles.persistence import load_circles, persist_circle
|
|
9
|
+
from fleetmesh_protocols.circles.store import (
|
|
10
|
+
apply_circle_media_privacy,
|
|
11
|
+
circle_access,
|
|
12
|
+
handle_circles_post,
|
|
13
|
+
is_circle_member,
|
|
14
|
+
visible_circle,
|
|
15
|
+
visible_circles,
|
|
16
|
+
visible_invites,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"load_circles",
|
|
21
|
+
"persist_circle",
|
|
22
|
+
"apply_circle_media_privacy",
|
|
23
|
+
"circle_access",
|
|
24
|
+
"handle_circles_post",
|
|
25
|
+
"is_circle_member",
|
|
26
|
+
"visible_circle",
|
|
27
|
+
"visible_circles",
|
|
28
|
+
"visible_invites",
|
|
29
|
+
]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""FleetMesh Node Direct-Message Mailbox & Spool Package (`node.protocols.dm`)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
# The stores and route handlers live in the fleetmesh-protocols package
|
|
5
|
+
# (fleetmesh_protocols.dm); this package re-exports them so node code and
|
|
6
|
+
# the protocol router keep their import paths.
|
|
7
|
+
|
|
8
|
+
from fleetmesh_protocols.dm.mailbox import (
|
|
9
|
+
MAX_SEALED_CONTENT,
|
|
10
|
+
ensure_mailbox,
|
|
11
|
+
get_dm_summary,
|
|
12
|
+
load_dm_mailbox,
|
|
13
|
+
peer_records,
|
|
14
|
+
persist_dm_message,
|
|
15
|
+
persist_dm_view,
|
|
16
|
+
persist_leaf_binding,
|
|
17
|
+
record_message,
|
|
18
|
+
update_view,
|
|
19
|
+
viewer_conversations,
|
|
20
|
+
viewer_envelopes,
|
|
21
|
+
viewer_messages,
|
|
22
|
+
)
|
|
23
|
+
from fleetmesh_protocols.dm.spool import (
|
|
24
|
+
ensure_dm_spool_fields,
|
|
25
|
+
get_active_typing_indicators,
|
|
26
|
+
get_dm_peers,
|
|
27
|
+
handle_dm_post,
|
|
28
|
+
record_typing_indicator,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"MAX_SEALED_CONTENT",
|
|
33
|
+
"ensure_mailbox",
|
|
34
|
+
"get_dm_summary",
|
|
35
|
+
"load_dm_mailbox",
|
|
36
|
+
"peer_records",
|
|
37
|
+
"persist_dm_message",
|
|
38
|
+
"persist_dm_view",
|
|
39
|
+
"persist_leaf_binding",
|
|
40
|
+
"record_message",
|
|
41
|
+
"update_view",
|
|
42
|
+
"viewer_conversations",
|
|
43
|
+
"viewer_envelopes",
|
|
44
|
+
"viewer_messages",
|
|
45
|
+
"ensure_dm_spool_fields",
|
|
46
|
+
"get_active_typing_indicators",
|
|
47
|
+
"get_dm_peers",
|
|
48
|
+
"handle_dm_post",
|
|
49
|
+
"record_typing_indicator",
|
|
50
|
+
]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""FleetMesh Node GRASP Git Protocol Package (`node.protocols.grasp`)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
# The stores and route handlers live in the fleetmesh-protocols package
|
|
5
|
+
# (fleetmesh_protocols.grasp); this package re-exports them so node code and
|
|
6
|
+
# the protocol router keep their import paths.
|
|
7
|
+
|
|
8
|
+
from fleetmesh_protocols.grasp.git_http import _pkt_line, handle_git_smart_http, render_node_portal_html
|
|
9
|
+
from fleetmesh_protocols.grasp.git_store import SEED_EPOCH, StandaloneGraspGitStore, _clean_slug, _commit_time, handle_grasp_api_http
|
|
10
|
+
from fleetmesh_protocols.grasp.seeds import SEEDED_GRASP_REPOS
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"SEEDED_GRASP_REPOS",
|
|
14
|
+
"SEED_EPOCH",
|
|
15
|
+
"StandaloneGraspGitStore",
|
|
16
|
+
"_clean_slug",
|
|
17
|
+
"_commit_time",
|
|
18
|
+
"handle_grasp_api_http",
|
|
19
|
+
"_pkt_line",
|
|
20
|
+
"handle_git_smart_http",
|
|
21
|
+
"render_node_portal_html",
|
|
22
|
+
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""The timestamp text the protocol stores write: UTC, whole seconds, a Z.
|
|
2
|
+
|
|
3
|
+
Four stores wrote this function out for themselves, byte for byte (audit
|
|
4
|
+
2026-10-15, D8). It reads the wall clock, as all four did: what it stamps is
|
|
5
|
+
when a row was written, for an operator reading the table, never a protocol
|
|
6
|
+
time another node compares.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import datetime
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def now_iso() -> str:
|
|
14
|
+
"""Now, as 2026-10-15T09:20:00Z."""
|
|
15
|
+
return datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Nostr & Blossom protocol package for Fleetmesh.
|
|
2
|
+
|
|
3
|
+
Supports:
|
|
4
|
+
- NIP-01..NIP-77 relay server, event store, and client connections.
|
|
5
|
+
- BUD-01..BUD-04 Blossom blob storage and media servers.
|
|
6
|
+
- Peer descriptor exchange and cryptographic identity assertions.
|
|
7
|
+
- Unified ProtocolPackage ingress and lifecycle management.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from .client import DEAD_AFTER, PING_AFTER, Relay, RelayError, publish_all, query_all
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
'DEAD_AFTER', 'PING_AFTER', 'Relay', 'RelayError', 'publish_all', 'query_all',
|
|
17
|
+
'RelayServer', 'NodeStore', 'BlossomServer', 'BlossomClient', 'BlobStore', 'Peers',
|
|
18
|
+
'NostrProtocolPackage', 'NostrIdentityAdapter',
|
|
19
|
+
'blobstore', 'blossom', 'client', 'descriptor', 'eventstore', 'package', 'peers', 'relayserver',
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def __getattr__(name: str) -> Any:
|
|
24
|
+
if name == 'RelayServer':
|
|
25
|
+
from .relayserver import RelayServer
|
|
26
|
+
return RelayServer
|
|
27
|
+
if name == 'NodeStore':
|
|
28
|
+
from .eventstore import NodeStore
|
|
29
|
+
return NodeStore
|
|
30
|
+
if name == 'BlossomServer':
|
|
31
|
+
from .blossom import BlossomServer
|
|
32
|
+
return BlossomServer
|
|
33
|
+
if name == 'BlossomClient':
|
|
34
|
+
from .blossom import BlossomClient
|
|
35
|
+
return BlossomClient
|
|
36
|
+
if name == 'BlobStore':
|
|
37
|
+
from .blobstore import BlobStore
|
|
38
|
+
return BlobStore
|
|
39
|
+
if name == 'Peers':
|
|
40
|
+
from .peers import Peers
|
|
41
|
+
return Peers
|
|
42
|
+
if name == 'NostrProtocolPackage':
|
|
43
|
+
from .package import NostrProtocolPackage
|
|
44
|
+
return NostrProtocolPackage
|
|
45
|
+
if name == 'NostrIdentityAdapter':
|
|
46
|
+
from .package import NostrIdentityAdapter
|
|
47
|
+
return NostrIdentityAdapter
|
|
48
|
+
if name in ('blobstore', 'blossom', 'client', 'descriptor', 'eventstore', 'package', 'peers', 'relayserver'):
|
|
49
|
+
import importlib
|
|
50
|
+
return importlib.import_module(f'.{name}', __package__)
|
|
51
|
+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|