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,270 @@
|
|
|
1
|
+
"""The schema.org face of a node: what it publishes for strangers, projected
|
|
2
|
+
as JSON-LD and served read-only over the relay face's HTTP fallthrough.
|
|
3
|
+
|
|
4
|
+
Three documents, each a projection of a signed event (fleetmesh_core
|
|
5
|
+
.linked_data) and never a second source of truth:
|
|
6
|
+
|
|
7
|
+
GET /.well-known/fleetmesh/service.jsonld the node's kind 11801 as a
|
|
8
|
+
schema:Service, floor from
|
|
9
|
+
its kind 10050
|
|
10
|
+
GET /.well-known/fleetmesh/blob/<sha256>.jsonld
|
|
11
|
+
one stored blob as a
|
|
12
|
+
schema:MediaObject
|
|
13
|
+
GET /.well-known/fleetmesh/standing.jsonld the node's own tier, as this
|
|
14
|
+
node's accounts have it - an
|
|
15
|
+
Organization with one
|
|
16
|
+
PropertyValue, not a Review
|
|
17
|
+
|
|
18
|
+
Nothing here is signed and nothing here goes on a relay: the signed thing
|
|
19
|
+
is the event, this is a reading of it. A consumer that wants proof fetches
|
|
20
|
+
the event. That is why JSON-LD does not ride inside events (see
|
|
21
|
+
CONTRIBUTING, "linked data").
|
|
22
|
+
|
|
23
|
+
Every answer carries `Cache-Control: no-store` and the id of the event it
|
|
24
|
+
was read from in `X-Fleetmesh-Source`, so a reader can go and check. No
|
|
25
|
+
POST, no query strings, unknown paths fall through to the rest of the face.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import json
|
|
31
|
+
import re
|
|
32
|
+
from typing import Any, Callable, Mapping, Optional, cast
|
|
33
|
+
|
|
34
|
+
from fleetmesh_core.storage import linked_data
|
|
35
|
+
from fleetmesh_core.wire.interfaces import NostrEvent
|
|
36
|
+
|
|
37
|
+
from node.compartment import stores as st
|
|
38
|
+
from node.compartment import Command, Compartment, Need
|
|
39
|
+
from node.transport import ws
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
from .types import (
|
|
43
|
+
LinkedTotals,
|
|
44
|
+
LinkedState,
|
|
45
|
+
BlobFacts,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
PREFIX = '/.well-known/fleetmesh/'
|
|
49
|
+
CTYPE = 'application/ld+json'
|
|
50
|
+
_SHA = re.compile(r'^[0-9a-f]{64}$')
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
import dataclasses
|
|
54
|
+
from node.channels import Channel, INFO
|
|
55
|
+
from node.metrics import COUNTER, Metric
|
|
56
|
+
from node.config.parameter import Parameter, ParamBlock
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclasses.dataclass(frozen=True)
|
|
60
|
+
class LinkedConfig(ParamBlock):
|
|
61
|
+
key = 'linked'
|
|
62
|
+
name = 'linked'
|
|
63
|
+
DERIVED: bool = False
|
|
64
|
+
PARAMS = (
|
|
65
|
+
Parameter(key='enabled', name='Linked Data Enabled', description='Whether JSON-LD schema.org projections are served', type=bool, default=True, tags=('linked', 'schema')),
|
|
66
|
+
)
|
|
67
|
+
enabled: bool = True
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class LinkedFace(Compartment):
|
|
71
|
+
"""Mounted on the relay face's HTTP fallthrough, before nsite."""
|
|
72
|
+
|
|
73
|
+
name: str = 'linked'
|
|
74
|
+
optional: bool = False
|
|
75
|
+
requires: tuple[str, ...] = ('presence',) # the kind 10050 it last published is the floor
|
|
76
|
+
uses: tuple[str, ...] = ()
|
|
77
|
+
reads: tuple[str, ...] = ()
|
|
78
|
+
live: tuple[str, ...] = ()
|
|
79
|
+
config_cls = LinkedConfig
|
|
80
|
+
CHANNELS = (
|
|
81
|
+
Channel('doc_served', 'JSON-LD document served', level=INFO, say='served JSON-LD {path}', tags=('linked', 'schema')),
|
|
82
|
+
)
|
|
83
|
+
METRICS = (
|
|
84
|
+
Metric(name='linked_docs_served', description='Times JSON-LD projections were served.', kind=COUNTER, counts='doc_served', tags=('linked', 'schema')),
|
|
85
|
+
)
|
|
86
|
+
NEEDS = (
|
|
87
|
+
Need('face'),
|
|
88
|
+
Need('blobs', 'stores.blobs'),
|
|
89
|
+
Need('descriptor'),
|
|
90
|
+
Need('presence'),
|
|
91
|
+
Need('ledger'),
|
|
92
|
+
Need('ks'),
|
|
93
|
+
)
|
|
94
|
+
COMMANDS = (
|
|
95
|
+
Command(
|
|
96
|
+
key='show',
|
|
97
|
+
name='Linked Data Status',
|
|
98
|
+
description='the projections this node serves and how often each was read',
|
|
99
|
+
tags=('linked', 'status'),
|
|
100
|
+
permission='linked/show',
|
|
101
|
+
run='state',
|
|
102
|
+
reply='paths and counts',
|
|
103
|
+
),
|
|
104
|
+
Command(
|
|
105
|
+
key='service',
|
|
106
|
+
name='Service Document',
|
|
107
|
+
description="this node's descriptor as a schema:Service (JSON-LD)",
|
|
108
|
+
tags=('linked', 'service'),
|
|
109
|
+
permission='linked/service',
|
|
110
|
+
run='service_cmd',
|
|
111
|
+
reply='the document, or refused when no descriptor yet',
|
|
112
|
+
),
|
|
113
|
+
Command(
|
|
114
|
+
key='standing',
|
|
115
|
+
name='Standing Document',
|
|
116
|
+
description="this node's own tier as a schema:Organization (JSON-LD)",
|
|
117
|
+
tags=('linked', 'standing'),
|
|
118
|
+
permission='linked/standing',
|
|
119
|
+
run='standing_cmd',
|
|
120
|
+
reply='the document, or refused when the ledger has nothing to say',
|
|
121
|
+
),
|
|
122
|
+
Command(
|
|
123
|
+
key='blob',
|
|
124
|
+
name='Blob Document',
|
|
125
|
+
description='one stored blob as a schema:MediaObject (JSON-LD)',
|
|
126
|
+
tags=('linked', 'blob'),
|
|
127
|
+
parameters=(
|
|
128
|
+
Parameter(key='sha256', name='sha256', description='sha256 hex digest of blob', type=str, default='', required=True),
|
|
129
|
+
),
|
|
130
|
+
permission='linked/blob',
|
|
131
|
+
run='blob_cmd',
|
|
132
|
+
reply='the document, or refused when the blob is not here',
|
|
133
|
+
),
|
|
134
|
+
)
|
|
135
|
+
STORES = (
|
|
136
|
+
st.Store(key='served', description='linked data requests served by category',
|
|
137
|
+
kind=st.COUNTERS, shape=LinkedTotals),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
def __init__(self, node: Optional[Any] = None, face: Optional[Any] = None,
|
|
141
|
+
blobs: Optional[Any] = None,
|
|
142
|
+
descriptor: Optional[Callable[[], Optional[NostrEvent]]] = None,
|
|
143
|
+
inbox: Optional[Callable[[], Optional[NostrEvent]]] = None,
|
|
144
|
+
standing: Optional[Callable[[], Optional[Mapping[str, Any]]]] = None,
|
|
145
|
+
clock: Optional[Callable[[], int]] = None,
|
|
146
|
+
blossom_url: Optional[str] = None,
|
|
147
|
+
presence: Optional[Any] = None,
|
|
148
|
+
ledger: Optional[Any] = None,
|
|
149
|
+
ks: Optional[Any] = None) -> None:
|
|
150
|
+
super().__init__(node, clock)
|
|
151
|
+
self.face = face
|
|
152
|
+
self.blobs = blobs
|
|
153
|
+
self._descriptor = descriptor or (lambda: None)
|
|
154
|
+
self._inbox: Optional[Callable[[], Optional[NostrEvent]]]
|
|
155
|
+
if inbox is not None:
|
|
156
|
+
self._inbox = inbox
|
|
157
|
+
elif presence is not None:
|
|
158
|
+
self._inbox = lambda: getattr(presence, 'last_inbox', None)
|
|
159
|
+
else:
|
|
160
|
+
self._inbox = None
|
|
161
|
+
|
|
162
|
+
self._standing: Optional[Callable[[], Optional[Mapping[str, Any]]]]
|
|
163
|
+
if standing is not None:
|
|
164
|
+
self._standing = standing
|
|
165
|
+
elif ledger is not None and ks is not None:
|
|
166
|
+
me = getattr(ks, 'node_pk', '')
|
|
167
|
+
self._standing = (lambda: cast(Optional[Mapping[str, Any]], ledger.tell(me))) if me else None
|
|
168
|
+
else:
|
|
169
|
+
self._standing = None
|
|
170
|
+
|
|
171
|
+
self.blossom_url = blossom_url if blossom_url is not None else getattr(node, 'blossom_url', None)
|
|
172
|
+
self.served: st.Counters
|
|
173
|
+
|
|
174
|
+
# ---- lifecycle ----------------------------------------------------------
|
|
175
|
+
def start(self) -> None:
|
|
176
|
+
if self.face is not None and hasattr(self.face, 'http_handlers'):
|
|
177
|
+
# First in line: fixed paths, cheap, never host-routed.
|
|
178
|
+
self.face.http_handlers.insert(0, self.handle_request)
|
|
179
|
+
|
|
180
|
+
def tick(self, now: int) -> list[tuple[Any, ...]]:
|
|
181
|
+
return []
|
|
182
|
+
|
|
183
|
+
def state(self) -> LinkedState:
|
|
184
|
+
return {'served': dict(self.served), 'paths': self.paths()}
|
|
185
|
+
|
|
186
|
+
def service_cmd(self) -> Mapping[str, Any]:
|
|
187
|
+
got = self.service()
|
|
188
|
+
if got is None:
|
|
189
|
+
return {'refused': 'nothing to say'}
|
|
190
|
+
doc, source = got
|
|
191
|
+
return {'source': source, 'document': doc}
|
|
192
|
+
|
|
193
|
+
def standing_cmd(self) -> Mapping[str, Any]:
|
|
194
|
+
got = self.standing()
|
|
195
|
+
if got is None:
|
|
196
|
+
return {'refused': 'nothing to say'}
|
|
197
|
+
doc, source = got
|
|
198
|
+
return {'source': source, 'document': doc}
|
|
199
|
+
|
|
200
|
+
def blob_cmd(self, args: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
201
|
+
got = self.blob(str(args.get('sha256', '')).lower())
|
|
202
|
+
if got is None:
|
|
203
|
+
return {'refused': 'nothing to say'}
|
|
204
|
+
doc, source = got
|
|
205
|
+
return {'source': source, 'document': doc}
|
|
206
|
+
|
|
207
|
+
@staticmethod
|
|
208
|
+
def paths() -> list[str]:
|
|
209
|
+
return [PREFIX + 'service.jsonld', PREFIX + 'blob/<sha256>.jsonld', PREFIX + 'standing.jsonld']
|
|
210
|
+
|
|
211
|
+
# ---- the documents --------------------------------------------------------
|
|
212
|
+
def service(self) -> Optional[tuple[linked_data.LinkedData, str]]:
|
|
213
|
+
desc = self._descriptor()
|
|
214
|
+
if not desc:
|
|
215
|
+
return None
|
|
216
|
+
inbox = self._inbox() if self._inbox is not None else None
|
|
217
|
+
return linked_data.service(desc, inbox), desc['id']
|
|
218
|
+
|
|
219
|
+
def blob(self, sha: str) -> Optional[tuple[linked_data.LinkedData, str]]:
|
|
220
|
+
if self.blobs is None or not _SHA.match(sha):
|
|
221
|
+
return None
|
|
222
|
+
st = self.blobs.stat(sha)
|
|
223
|
+
if not st:
|
|
224
|
+
return None
|
|
225
|
+
url = self.blossom_url
|
|
226
|
+
d: BlobFacts = {'sha256': sha, 'size': st.get('size'), 'type': st.get('type')}
|
|
227
|
+
if st.get('uploaded') is not None:
|
|
228
|
+
d['uploaded'] = st['uploaded']
|
|
229
|
+
if url:
|
|
230
|
+
d['url'] = '%s/%s' % (url.rstrip('/'), sha)
|
|
231
|
+
return linked_data.media_object(d), sha
|
|
232
|
+
|
|
233
|
+
def standing(self) -> Optional[tuple[linked_data.LinkedData, str]]:
|
|
234
|
+
desc = self._descriptor()
|
|
235
|
+
tell = self._standing() if self._standing is not None else None
|
|
236
|
+
if not desc or not tell:
|
|
237
|
+
return None
|
|
238
|
+
return linked_data.standing(desc['pubkey'], tell), desc['id']
|
|
239
|
+
|
|
240
|
+
# ---- the hook ---------------------------------------------------------------
|
|
241
|
+
def handle_request(self, conn: Any, req: Any) -> bool:
|
|
242
|
+
path = getattr(req, 'path', '/') or '/'
|
|
243
|
+
if not path.startswith(PREFIX):
|
|
244
|
+
return False
|
|
245
|
+
method = (getattr(req, 'method', 'GET') or 'GET').upper()
|
|
246
|
+
if method not in ('GET', 'HEAD'):
|
|
247
|
+
ws.http_response(conn, '405 Method Not Allowed', b'', 'text/plain', {'Allow': 'GET, HEAD'})
|
|
248
|
+
return True
|
|
249
|
+
rest = path[len(PREFIX):].split('?', 1)[0]
|
|
250
|
+
got: Optional[tuple[linked_data.LinkedData, str]] = None
|
|
251
|
+
which = ''
|
|
252
|
+
if rest == 'service.jsonld':
|
|
253
|
+
which, got = 'service', self.service()
|
|
254
|
+
elif rest == 'standing.jsonld':
|
|
255
|
+
which, got = 'standing', self.standing()
|
|
256
|
+
elif rest.startswith('blob/') and rest.endswith('.jsonld'):
|
|
257
|
+
which, got = 'blob', self.blob(rest[5:-7].lower())
|
|
258
|
+
else:
|
|
259
|
+
ws.http_response(conn, '404 Not Found', b'no such projection\n', 'text/plain')
|
|
260
|
+
return True
|
|
261
|
+
if got is None:
|
|
262
|
+
ws.http_response(conn, '404 Not Found', b'nothing to say\n', 'text/plain',
|
|
263
|
+
{'Cache-Control': 'no-store'})
|
|
264
|
+
return True
|
|
265
|
+
doc, source = got
|
|
266
|
+
body = json.dumps(doc, sort_keys=True, separators=(',', ':')).encode()
|
|
267
|
+
self.served[which] += 1
|
|
268
|
+
ws.http_response(conn, '200 OK', b'' if method == 'HEAD' else body, CTYPE,
|
|
269
|
+
{'Cache-Control': 'no-store', 'X-Fleetmesh-Source': source})
|
|
270
|
+
return True
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Types and records for the Linked Data compartment."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Optional, TypedDict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class LinkedTotals(TypedDict):
|
|
8
|
+
service: int
|
|
9
|
+
blob: int
|
|
10
|
+
standing: int
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LinkedState(TypedDict):
|
|
14
|
+
served: dict[str, int]
|
|
15
|
+
paths: list[str]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BlobFacts(TypedDict, total=False):
|
|
19
|
+
"""What blobstore.stat() says about one blob, in the field names
|
|
20
|
+
linked_data.media_object() reads (a blossom_auth.BlobDescriptor)."""
|
|
21
|
+
sha256: str
|
|
22
|
+
size: Optional[int]
|
|
23
|
+
type: Optional[str]
|
|
24
|
+
uploaded: int
|
|
25
|
+
url: str
|