fedipod-server 0.11.0 → 0.13.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/README.md +22 -6
- package/dist/claims.d.ts +8 -0
- package/dist/claims.js +10 -0
- package/dist/handler.d.ts +13 -0
- package/dist/handler.js +56 -11
- package/dist/handler.jsonld +8 -0
- package/dist/store-pod.js +18 -4
- package/lib/{c2s.mjs → client/c2s.mjs} +10 -3
- package/lib/{localapi.mjs → client/localapi.mjs} +2 -2
- package/lib/client/masto/accounts.mjs +264 -0
- package/lib/client/masto/body.mjs +69 -0
- package/lib/client/masto/index.mjs +183 -0
- package/lib/client/masto/instance.mjs +104 -0
- package/lib/client/masto/media.mjs +133 -0
- package/lib/client/masto/oauth.mjs +599 -0
- package/lib/client/masto/render.mjs +459 -0
- package/lib/client/masto/statuses.mjs +331 -0
- package/lib/client/masto/timelines.mjs +316 -0
- package/lib/{streaming.mjs → client/streaming.mjs} +1 -1
- package/lib/{acctfeed.mjs → connections/acctfeed.mjs} +1 -1
- package/lib/{atproto.mjs → connections/atproto.mjs} +15 -16
- package/lib/{bskygroup.mjs → connections/bskygroup.mjs} +1 -1
- package/lib/{fediacct.mjs → connections/fediacct.mjs} +31 -35
- package/lib/{import.mjs → connections/import.mjs} +1 -1
- package/lib/{tagfeed.mjs → connections/tagfeed.mjs} +3 -3
- package/lib/connections/vault.mjs +114 -0
- package/lib/core/as2.mjs +170 -0
- package/lib/core/contexts/activitystreams.json +379 -0
- package/lib/core/contexts/did-v1.json +57 -0
- package/lib/core/contexts/fep-5711.json +36 -0
- package/lib/core/contexts/gotosocial.json +86 -0
- package/lib/core/contexts/identity-v1.json +152 -0
- package/lib/core/contexts/index.mjs +45 -0
- package/lib/core/contexts/join-lemmy.json +33 -0
- package/lib/core/contexts/joinmastodon.json +28 -0
- package/lib/core/contexts/map.json +16 -0
- package/lib/core/contexts/miscellany.json +19 -0
- package/lib/core/contexts/schemaorg.json +8845 -0
- package/lib/core/contexts/security-data-integrity-v1.json +78 -0
- package/lib/core/contexts/security-data-integrity-v2.json +81 -0
- package/lib/core/contexts/security-multikey-v1.json +35 -0
- package/lib/core/contexts/security-v1.json +74 -0
- package/lib/core/contexts/webfinger.json +10 -0
- package/lib/{deliver.mjs → core/deliver.mjs} +2 -2
- package/lib/core/graphview.mjs +269 -0
- package/lib/core/intake/activities.mjs +437 -0
- package/lib/core/intake/activity.mjs +240 -0
- package/lib/core/intake/channel.mjs +144 -0
- package/lib/core/intake/group.mjs +222 -0
- package/lib/core/intake/index.mjs +629 -0
- package/lib/core/intake/notes.mjs +288 -0
- package/lib/core/intake/verify.mjs +142 -0
- package/lib/{keys.mjs → core/keys.mjs} +1 -1
- package/lib/core/publisher/collections.mjs +229 -0
- package/lib/core/publisher/index.mjs +421 -0
- package/lib/core/publisher/notes.mjs +188 -0
- package/lib/core/publisher/questions.mjs +233 -0
- package/lib/core/publisher/restore.mjs +199 -0
- package/lib/core/shapes/activitystreams.ttl +129 -0
- package/lib/core/shapes/index.mjs +107 -0
- package/lib/core/shapes/shapes-text.mjs +13 -0
- package/lib/{social.mjs → core/social.mjs} +2 -2
- package/lib/{store.mjs → core/store.mjs} +4 -0
- package/lib/{wire.mjs → core/wire.mjs} +2 -2
- package/lib/device/admin/index.mjs +13 -0
- package/lib/device/admin/origins.mjs +35 -0
- package/lib/device/admin/routes/connections.mjs +144 -0
- package/lib/device/admin/routes/gateway.mjs +199 -0
- package/lib/device/admin/routes/lifecycle.mjs +191 -0
- package/lib/device/admin/routes/owner.mjs +322 -0
- package/lib/device/admin/routes/setup.mjs +393 -0
- package/lib/device/admin/routes/social.mjs +188 -0
- package/lib/device/admin/server.mjs +95 -0
- package/lib/device/admin/static.mjs +244 -0
- package/lib/device/admin/surface.mjs +274 -0
- package/lib/device/cli/commands/account.mjs +586 -0
- package/lib/device/cli/commands/run.mjs +278 -0
- package/lib/device/cli/commands/service.mjs +221 -0
- package/lib/device/cli/commands/setup.mjs +410 -0
- package/lib/device/cli/commands/state.mjs +559 -0
- package/lib/device/cli/context.mjs +288 -0
- package/lib/{migrate.mjs → device/migrate.mjs} +1 -1
- package/lib/{remote.mjs → device/remote.mjs} +3 -3
- package/lib/{setup.mjs → device/setup.mjs} +3 -3
- package/lib/{update.mjs → device/update.mjs} +1 -1
- package/lib/{directory.mjs → gateway/directory.mjs} +1 -1
- package/lib/{front-core.mjs → gateway/front-core.mjs} +3 -3
- package/lib/{gateway-core.mjs → gateway/gateway-core.mjs} +1 -1
- package/lib/{httpsig.mjs → gateway/httpsig.mjs} +1 -1
- package/lib/server/embed.mjs +405 -0
- package/lib/{links.mjs → shared/links.mjs} +1 -1
- package/lib/{ua.mjs → shared/ua.mjs} +1 -1
- package/package.json +1 -1
- package/run-agent.mjs +33 -25
- package/web/admin/actors.js +145 -0
- package/web/admin/common.js +23 -0
- package/web/admin/connections.js +112 -0
- package/web/admin/gateway.js +111 -0
- package/web/admin/group.js +258 -0
- package/web/admin/index.html +7 -1
- package/web/admin/record.js +378 -0
- package/web/admin/setup/index.html +1 -0
- package/web/admin/setup/setup.js +2 -13
- package/web/admin/upkeep.js +170 -0
- package/web/app/README.md +6 -6
- package/web/app/admin-facade.mjs +3 -3
- package/web/app/agent.mjs +14 -16
- package/web/app/atproto-browser.mjs +1 -1
- package/web/app/boot.mjs +2 -3
- package/web/app/deliver-relay.mjs +1 -1
- package/web/app/dist/boot.js +22 -3
- package/web/app/dist/boot.js.map +2 -2
- package/web/app/dist/sw.js +21913 -5446
- package/web/app/dist/sw.js.map +4 -4
- package/web/app/fediacct-browser.mjs +1 -1
- package/web/app/keys-browser.mjs +27 -4
- package/web/app/shims/shapes-text.mjs +8 -0
- package/web/app/signup.mjs +2 -3
- package/web/app/site/admin/actors.js +145 -0
- package/web/app/site/admin/common.js +23 -0
- package/web/app/site/admin/connections.js +112 -0
- package/web/app/site/admin/gateway.js +111 -0
- package/web/app/site/admin/group.js +258 -0
- package/web/app/site/admin/index.html +7 -1
- package/web/app/site/admin/record.js +378 -0
- package/web/app/site/admin/setup/index.html +1 -0
- package/web/app/site/admin/setup/setup.js +2 -13
- package/web/app/site/admin/upkeep.js +170 -0
- package/web/app/site/boot.js +22 -3
- package/web/app/site/sw.js +21913 -5446
- package/web/app/sw-src.mjs +17 -2
- package/lib/admin.mjs +0 -1913
- package/lib/embed.mjs +0 -220
- package/lib/intake.mjs +0 -1981
- package/lib/mastoapi.mjs +0 -2284
- package/lib/publisher.mjs +0 -1192
- package/web/admin/admin.js +0 -1181
- package/web/app/site/admin/admin.js +0 -1181
- /package/lib/{oidc-auth.mjs → client/oidc-auth.mjs} +0 -0
- /package/lib/{webpush.mjs → client/webpush.mjs} +0 -0
- /package/lib/{bskyfeed.mjs → connections/bskyfeed.mjs} +0 -0
- /package/lib/{lease.mjs → core/lease.mjs} +0 -0
- /package/lib/{polls.mjs → core/polls.mjs} +0 -0
- /package/lib/{proof.mjs → core/proof.mjs} +0 -0
- /package/lib/{storage.mjs → core/storage.mjs} +0 -0
- /package/lib/{account.mjs → device/account.mjs} +0 -0
- /package/lib/{certs.mjs → device/certs.mjs} +0 -0
- /package/lib/{export-collections.mjs → device/export-collections.mjs} +0 -0
- /package/lib/{home.mjs → device/home.mjs} +0 -0
- /package/lib/{ports.mjs → device/ports.mjs} +0 -0
- /package/lib/{guard.mjs → shared/guard.mjs} +0 -0
- /package/lib/{safefetch.mjs → shared/safefetch.mjs} +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"@context": {
|
|
3
|
+
"id": "@id",
|
|
4
|
+
"type": "@type",
|
|
5
|
+
"@protected": true,
|
|
6
|
+
"digestMultibase": {
|
|
7
|
+
"@id": "https://w3id.org/security#digestMultibase",
|
|
8
|
+
"@type": "https://w3id.org/security#multibase"
|
|
9
|
+
},
|
|
10
|
+
"proof": {
|
|
11
|
+
"@id": "https://w3id.org/security#proof",
|
|
12
|
+
"@type": "@id",
|
|
13
|
+
"@container": "@graph"
|
|
14
|
+
},
|
|
15
|
+
"DataIntegrityProof": {
|
|
16
|
+
"@id": "https://w3id.org/security#DataIntegrityProof",
|
|
17
|
+
"@context": {
|
|
18
|
+
"@protected": true,
|
|
19
|
+
"id": "@id",
|
|
20
|
+
"type": "@type",
|
|
21
|
+
"challenge": "https://w3id.org/security#challenge",
|
|
22
|
+
"created": {
|
|
23
|
+
"@id": "http://purl.org/dc/terms/created",
|
|
24
|
+
"@type": "http://www.w3.org/2001/XMLSchema#dateTime"
|
|
25
|
+
},
|
|
26
|
+
"domain": "https://w3id.org/security#domain",
|
|
27
|
+
"expires": {
|
|
28
|
+
"@id": "https://w3id.org/security#expiration",
|
|
29
|
+
"@type": "http://www.w3.org/2001/XMLSchema#dateTime"
|
|
30
|
+
},
|
|
31
|
+
"nonce": "https://w3id.org/security#nonce",
|
|
32
|
+
"proofPurpose": {
|
|
33
|
+
"@id": "https://w3id.org/security#proofPurpose",
|
|
34
|
+
"@type": "@vocab",
|
|
35
|
+
"@context": {
|
|
36
|
+
"@protected": true,
|
|
37
|
+
"id": "@id",
|
|
38
|
+
"type": "@type",
|
|
39
|
+
"assertionMethod": {
|
|
40
|
+
"@id": "https://w3id.org/security#assertionMethod",
|
|
41
|
+
"@type": "@id",
|
|
42
|
+
"@container": "@set"
|
|
43
|
+
},
|
|
44
|
+
"authentication": {
|
|
45
|
+
"@id": "https://w3id.org/security#authenticationMethod",
|
|
46
|
+
"@type": "@id",
|
|
47
|
+
"@container": "@set"
|
|
48
|
+
},
|
|
49
|
+
"capabilityInvocation": {
|
|
50
|
+
"@id": "https://w3id.org/security#capabilityInvocationMethod",
|
|
51
|
+
"@type": "@id",
|
|
52
|
+
"@container": "@set"
|
|
53
|
+
},
|
|
54
|
+
"capabilityDelegation": {
|
|
55
|
+
"@id": "https://w3id.org/security#capabilityDelegationMethod",
|
|
56
|
+
"@type": "@id",
|
|
57
|
+
"@container": "@set"
|
|
58
|
+
},
|
|
59
|
+
"keyAgreement": {
|
|
60
|
+
"@id": "https://w3id.org/security#keyAgreementMethod",
|
|
61
|
+
"@type": "@id",
|
|
62
|
+
"@container": "@set"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"cryptosuite": "https://w3id.org/security#cryptosuite",
|
|
67
|
+
"proofValue": {
|
|
68
|
+
"@id": "https://w3id.org/security#proofValue",
|
|
69
|
+
"@type": "https://w3id.org/security#multibase"
|
|
70
|
+
},
|
|
71
|
+
"verificationMethod": {
|
|
72
|
+
"@id": "https://w3id.org/security#verificationMethod",
|
|
73
|
+
"@type": "@id"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"@context": {
|
|
3
|
+
"id": "@id",
|
|
4
|
+
"type": "@type",
|
|
5
|
+
"@protected": true,
|
|
6
|
+
"proof": {
|
|
7
|
+
"@id": "https://w3id.org/security#proof",
|
|
8
|
+
"@type": "@id",
|
|
9
|
+
"@container": "@graph"
|
|
10
|
+
},
|
|
11
|
+
"DataIntegrityProof": {
|
|
12
|
+
"@id": "https://w3id.org/security#DataIntegrityProof",
|
|
13
|
+
"@context": {
|
|
14
|
+
"@protected": true,
|
|
15
|
+
"id": "@id",
|
|
16
|
+
"type": "@type",
|
|
17
|
+
"challenge": "https://w3id.org/security#challenge",
|
|
18
|
+
"created": {
|
|
19
|
+
"@id": "http://purl.org/dc/terms/created",
|
|
20
|
+
"@type": "http://www.w3.org/2001/XMLSchema#dateTime"
|
|
21
|
+
},
|
|
22
|
+
"domain": "https://w3id.org/security#domain",
|
|
23
|
+
"expires": {
|
|
24
|
+
"@id": "https://w3id.org/security#expiration",
|
|
25
|
+
"@type": "http://www.w3.org/2001/XMLSchema#dateTime"
|
|
26
|
+
},
|
|
27
|
+
"nonce": "https://w3id.org/security#nonce",
|
|
28
|
+
"previousProof": {
|
|
29
|
+
"@id": "https://w3id.org/security#previousProof",
|
|
30
|
+
"@type": "@id"
|
|
31
|
+
},
|
|
32
|
+
"proofPurpose": {
|
|
33
|
+
"@id": "https://w3id.org/security#proofPurpose",
|
|
34
|
+
"@type": "@vocab",
|
|
35
|
+
"@context": {
|
|
36
|
+
"@protected": true,
|
|
37
|
+
"id": "@id",
|
|
38
|
+
"type": "@type",
|
|
39
|
+
"assertionMethod": {
|
|
40
|
+
"@id": "https://w3id.org/security#assertionMethod",
|
|
41
|
+
"@type": "@id",
|
|
42
|
+
"@container": "@set"
|
|
43
|
+
},
|
|
44
|
+
"authentication": {
|
|
45
|
+
"@id": "https://w3id.org/security#authenticationMethod",
|
|
46
|
+
"@type": "@id",
|
|
47
|
+
"@container": "@set"
|
|
48
|
+
},
|
|
49
|
+
"capabilityInvocation": {
|
|
50
|
+
"@id": "https://w3id.org/security#capabilityInvocationMethod",
|
|
51
|
+
"@type": "@id",
|
|
52
|
+
"@container": "@set"
|
|
53
|
+
},
|
|
54
|
+
"capabilityDelegation": {
|
|
55
|
+
"@id": "https://w3id.org/security#capabilityDelegationMethod",
|
|
56
|
+
"@type": "@id",
|
|
57
|
+
"@container": "@set"
|
|
58
|
+
},
|
|
59
|
+
"keyAgreement": {
|
|
60
|
+
"@id": "https://w3id.org/security#keyAgreementMethod",
|
|
61
|
+
"@type": "@id",
|
|
62
|
+
"@container": "@set"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"cryptosuite": {
|
|
67
|
+
"@id": "https://w3id.org/security#cryptosuite",
|
|
68
|
+
"@type": "https://w3id.org/security#cryptosuiteString"
|
|
69
|
+
},
|
|
70
|
+
"proofValue": {
|
|
71
|
+
"@id": "https://w3id.org/security#proofValue",
|
|
72
|
+
"@type": "https://w3id.org/security#multibase"
|
|
73
|
+
},
|
|
74
|
+
"verificationMethod": {
|
|
75
|
+
"@id": "https://w3id.org/security#verificationMethod",
|
|
76
|
+
"@type": "@id"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"@context": {
|
|
3
|
+
"id": "@id",
|
|
4
|
+
"type": "@type",
|
|
5
|
+
"@protected": true,
|
|
6
|
+
"Multikey": {
|
|
7
|
+
"@id": "https://w3id.org/security#Multikey",
|
|
8
|
+
"@context": {
|
|
9
|
+
"@protected": true,
|
|
10
|
+
"id": "@id",
|
|
11
|
+
"type": "@type",
|
|
12
|
+
"controller": {
|
|
13
|
+
"@id": "https://w3id.org/security#controller",
|
|
14
|
+
"@type": "@id"
|
|
15
|
+
},
|
|
16
|
+
"revoked": {
|
|
17
|
+
"@id": "https://w3id.org/security#revoked",
|
|
18
|
+
"@type": "http://www.w3.org/2001/XMLSchema#dateTime"
|
|
19
|
+
},
|
|
20
|
+
"expires": {
|
|
21
|
+
"@id": "https://w3id.org/security#expiration",
|
|
22
|
+
"@type": "http://www.w3.org/2001/XMLSchema#dateTime"
|
|
23
|
+
},
|
|
24
|
+
"publicKeyMultibase": {
|
|
25
|
+
"@id": "https://w3id.org/security#publicKeyMultibase",
|
|
26
|
+
"@type": "https://w3id.org/security#multibase"
|
|
27
|
+
},
|
|
28
|
+
"secretKeyMultibase": {
|
|
29
|
+
"@id": "https://w3id.org/security#secretKeyMultibase",
|
|
30
|
+
"@type": "https://w3id.org/security#multibase"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"@context": {
|
|
3
|
+
"id": "@id",
|
|
4
|
+
"type": "@type",
|
|
5
|
+
"dc": "http://purl.org/dc/terms/",
|
|
6
|
+
"sec": "https://w3id.org/security#",
|
|
7
|
+
"xsd": "http://www.w3.org/2001/XMLSchema#",
|
|
8
|
+
"EcdsaKoblitzSignature2016": "sec:EcdsaKoblitzSignature2016",
|
|
9
|
+
"Ed25519Signature2018": "sec:Ed25519Signature2018",
|
|
10
|
+
"EncryptedMessage": "sec:EncryptedMessage",
|
|
11
|
+
"GraphSignature2012": "sec:GraphSignature2012",
|
|
12
|
+
"LinkedDataSignature2015": "sec:LinkedDataSignature2015",
|
|
13
|
+
"LinkedDataSignature2016": "sec:LinkedDataSignature2016",
|
|
14
|
+
"CryptographicKey": "sec:Key",
|
|
15
|
+
"authenticationTag": "sec:authenticationTag",
|
|
16
|
+
"canonicalizationAlgorithm": "sec:canonicalizationAlgorithm",
|
|
17
|
+
"cipherAlgorithm": "sec:cipherAlgorithm",
|
|
18
|
+
"cipherData": "sec:cipherData",
|
|
19
|
+
"cipherKey": "sec:cipherKey",
|
|
20
|
+
"created": {
|
|
21
|
+
"@id": "dc:created",
|
|
22
|
+
"@type": "xsd:dateTime"
|
|
23
|
+
},
|
|
24
|
+
"creator": {
|
|
25
|
+
"@id": "dc:creator",
|
|
26
|
+
"@type": "@id"
|
|
27
|
+
},
|
|
28
|
+
"digestAlgorithm": "sec:digestAlgorithm",
|
|
29
|
+
"digestValue": "sec:digestValue",
|
|
30
|
+
"domain": "sec:domain",
|
|
31
|
+
"encryptionKey": "sec:encryptionKey",
|
|
32
|
+
"expiration": {
|
|
33
|
+
"@id": "sec:expiration",
|
|
34
|
+
"@type": "xsd:dateTime"
|
|
35
|
+
},
|
|
36
|
+
"expires": {
|
|
37
|
+
"@id": "sec:expiration",
|
|
38
|
+
"@type": "xsd:dateTime"
|
|
39
|
+
},
|
|
40
|
+
"initializationVector": "sec:initializationVector",
|
|
41
|
+
"iterationCount": "sec:iterationCount",
|
|
42
|
+
"nonce": "sec:nonce",
|
|
43
|
+
"normalizationAlgorithm": "sec:normalizationAlgorithm",
|
|
44
|
+
"owner": {
|
|
45
|
+
"@id": "sec:owner",
|
|
46
|
+
"@type": "@id"
|
|
47
|
+
},
|
|
48
|
+
"password": "sec:password",
|
|
49
|
+
"privateKey": {
|
|
50
|
+
"@id": "sec:privateKey",
|
|
51
|
+
"@type": "@id"
|
|
52
|
+
},
|
|
53
|
+
"privateKeyPem": "sec:privateKeyPem",
|
|
54
|
+
"publicKey": {
|
|
55
|
+
"@id": "sec:publicKey",
|
|
56
|
+
"@type": "@id"
|
|
57
|
+
},
|
|
58
|
+
"publicKeyBase58": "sec:publicKeyBase58",
|
|
59
|
+
"publicKeyPem": "sec:publicKeyPem",
|
|
60
|
+
"publicKeyWif": "sec:publicKeyWif",
|
|
61
|
+
"publicKeyService": {
|
|
62
|
+
"@id": "sec:publicKeyService",
|
|
63
|
+
"@type": "@id"
|
|
64
|
+
},
|
|
65
|
+
"revoked": {
|
|
66
|
+
"@id": "sec:revoked",
|
|
67
|
+
"@type": "xsd:dateTime"
|
|
68
|
+
},
|
|
69
|
+
"salt": "sec:salt",
|
|
70
|
+
"signature": "sec:signature",
|
|
71
|
+
"signatureAlgorithm": "sec:signingAlgorithm",
|
|
72
|
+
"signatureValue": "sec:signatureValue"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
import { signRequest } from '@fedify/fedify/sig';
|
|
7
7
|
import { webcrypto } from 'node:crypto';
|
|
8
8
|
import { attachProof } from './proof.mjs';
|
|
9
|
-
import { pinnedFor, retryAfterMs, HTTP_TIMEOUT_MS } from '
|
|
10
|
-
import { USER_AGENT } from '
|
|
9
|
+
import { pinnedFor, retryAfterMs, HTTP_TIMEOUT_MS } from '../shared/safefetch.mjs';
|
|
10
|
+
import { USER_AGENT } from '../shared/ua.mjs';
|
|
11
11
|
|
|
12
12
|
const MAX_REDIRECTS = 3;
|
|
13
13
|
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
// graphview.mjs — what the handlers read.
|
|
2
|
+
//
|
|
3
|
+
// `parseAS2` produces quads. This turns them back into something a handler can
|
|
4
|
+
// read by name, so the thing being read is the GRAPH: every property access
|
|
5
|
+
// here is a lookup over statements, not a key on the JSON a stranger sent.
|
|
6
|
+
//
|
|
7
|
+
// That distinction is the whole point. A document can say the same thing in
|
|
8
|
+
// many ways — `"type": "Create"`, `"@type": "as:Create"`, a term aliased by the
|
|
9
|
+
// sender's own context — and all of them leave the same statement in the
|
|
10
|
+
// graph. Reading the graph means a handler sees one answer without knowing any
|
|
11
|
+
// of that happened.
|
|
12
|
+
//
|
|
13
|
+
// Names are resolved through the contexts we already hold, AS2 first. They are
|
|
14
|
+
// not hardcoded: `movedTo` is not in the AS2 context at all (it lives in
|
|
15
|
+
// miscellany), and `inbox` is `ldp:inbox` rather than an `as:` term, so a map
|
|
16
|
+
// written by hand would get both wrong.
|
|
17
|
+
|
|
18
|
+
import { CONTEXTS } from './contexts/index.mjs';
|
|
19
|
+
import { AS_CTX } from './wire.mjs';
|
|
20
|
+
|
|
21
|
+
const RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
|
|
22
|
+
const RDF_FIRST = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first';
|
|
23
|
+
const RDF_REST = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest';
|
|
24
|
+
const RDF_NIL = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil';
|
|
25
|
+
|
|
26
|
+
// Properties a handler treats as a collection. JSON-LD compaction would hand
|
|
27
|
+
// back a bare value when there is only one, and `Array.isArray` checks in the
|
|
28
|
+
// handlers then read it as empty. These always answer with an array.
|
|
29
|
+
// A stranger's document decides the shape of this graph, so the walk is capped
|
|
30
|
+
// rather than trusted to terminate on its own.
|
|
31
|
+
const MAX_DEPTH = 12;
|
|
32
|
+
|
|
33
|
+
const ALWAYS_LIST = new Set([
|
|
34
|
+
'items', 'orderedItems', 'to', 'cc', 'bto', 'bcc', 'tag', 'attachment',
|
|
35
|
+
'anyOf', 'oneOf', 'audience', 'attributedTo', 'alsoKnownAs',
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
/** Expand `as:object` against a context's own prefix declarations. */
|
|
39
|
+
function expandIri(value, prefixes) {
|
|
40
|
+
if (typeof value !== 'string') return null;
|
|
41
|
+
if (/^https?:\/\//.test(value)) return value;
|
|
42
|
+
const colon = value.indexOf(':');
|
|
43
|
+
if (colon < 1) return null;
|
|
44
|
+
const base = prefixes[value.slice(0, colon)];
|
|
45
|
+
return base ? base + value.slice(colon + 1) : null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* term -> { iri, list } over every context we hold, AS2 winning ties.
|
|
50
|
+
*
|
|
51
|
+
* Built once. A term already claimed by an earlier context is not overwritten,
|
|
52
|
+
* and AS2 is walked first, so an extension cannot quietly redefine `object`.
|
|
53
|
+
*/
|
|
54
|
+
function buildTerms() {
|
|
55
|
+
const terms = new Map();
|
|
56
|
+
const order = [AS_CTX, ...Object.keys(CONTEXTS).filter(u => u !== AS_CTX)];
|
|
57
|
+
for (const url of order) {
|
|
58
|
+
const ctx = CONTEXTS[url]?.['@context'];
|
|
59
|
+
if (!ctx || typeof ctx !== 'object') continue;
|
|
60
|
+
const prefixes = {};
|
|
61
|
+
for (const [k, v] of Object.entries(ctx)) {
|
|
62
|
+
if (typeof v === 'string' && /^https?:\/\//.test(v)) prefixes[k] = v;
|
|
63
|
+
}
|
|
64
|
+
for (const [name, def] of Object.entries(ctx)) {
|
|
65
|
+
if (name.startsWith('@') || terms.has(name)) continue;
|
|
66
|
+
const raw = typeof def === 'string' ? def : def?.['@id'];
|
|
67
|
+
const iri = expandIri(raw, prefixes);
|
|
68
|
+
if (!iri) continue;
|
|
69
|
+
terms.set(name, { iri, list: def?.['@container'] === '@list', container: def?.['@container'] ?? null });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return terms;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const TERMS = buildTerms();
|
|
76
|
+
|
|
77
|
+
/** iri -> shortest term naming it, so a type comes back as `Create`. */
|
|
78
|
+
const BY_IRI = (() => {
|
|
79
|
+
const m = new Map();
|
|
80
|
+
for (const [name, { iri }] of TERMS) {
|
|
81
|
+
const held = m.get(iri);
|
|
82
|
+
if (!held || name.length < held.length) m.set(iri, name);
|
|
83
|
+
}
|
|
84
|
+
return m;
|
|
85
|
+
})();
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* iri -> every term naming it.
|
|
89
|
+
*
|
|
90
|
+
* More than one term can mean the same predicate: `items` and `orderedItems`
|
|
91
|
+
* are both `as:items`, and only the list tells them apart. Emitting just the
|
|
92
|
+
* shorter name would lose `orderedItems` — and with it the ordering, which is
|
|
93
|
+
* the one thing an OrderedCollection is for.
|
|
94
|
+
*/
|
|
95
|
+
const NAMES_BY_IRI = (() => {
|
|
96
|
+
const m = new Map();
|
|
97
|
+
for (const [name, { iri, container }] of TERMS) {
|
|
98
|
+
// `contentMap` and `content` are the same predicate, but a language map is
|
|
99
|
+
// not another spelling of the plain term — emitting one as a bare string
|
|
100
|
+
// would invent a value the document never carried.
|
|
101
|
+
if (container === '@language' || container === '@index') continue;
|
|
102
|
+
const held = m.get(iri);
|
|
103
|
+
if (held) held.push(name); else m.set(iri, [name]);
|
|
104
|
+
}
|
|
105
|
+
return m;
|
|
106
|
+
})();
|
|
107
|
+
|
|
108
|
+
/** Index quads by subject, then by predicate. */
|
|
109
|
+
function indexQuads(quads) {
|
|
110
|
+
const bySubject = new Map();
|
|
111
|
+
const objects = new Set();
|
|
112
|
+
// A BlankNode's `.value` has no `_:` on it, so blankness is recorded here
|
|
113
|
+
// rather than guessed from the string. A blank node has no id, and handing a
|
|
114
|
+
// handler `id: "b0"` would be handing it an identifier that means nothing.
|
|
115
|
+
const blanks = new Set();
|
|
116
|
+
for (const q of quads) {
|
|
117
|
+
if (q.subject.termType === 'BlankNode') blanks.add(q.subject.value);
|
|
118
|
+
if (q.object.termType === 'BlankNode') blanks.add(q.object.value);
|
|
119
|
+
const s = q.subject.value;
|
|
120
|
+
let preds = bySubject.get(s);
|
|
121
|
+
if (!preds) bySubject.set(s, preds = new Map());
|
|
122
|
+
let vals = preds.get(q.predicate.value);
|
|
123
|
+
if (!vals) preds.set(q.predicate.value, vals = []);
|
|
124
|
+
vals.push(q.object);
|
|
125
|
+
if (q.object.termType === 'NamedNode' || q.object.termType === 'BlankNode') {
|
|
126
|
+
objects.add(q.object.value);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return { bySubject, objects, blanks };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* The document's own node: a subject nothing else points at. A document that
|
|
134
|
+
* points back at itself leaves no such subject, so the first typed subject is
|
|
135
|
+
* the fallback rather than nothing at all.
|
|
136
|
+
*/
|
|
137
|
+
function findRoot({ bySubject, objects }) {
|
|
138
|
+
for (const s of bySubject.keys()) if (!objects.has(s)) return s;
|
|
139
|
+
for (const [s, preds] of bySubject) if (preds.has(RDF_TYPE)) return s;
|
|
140
|
+
return bySubject.keys().next().value ?? null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Walk an rdf:List into an ordered array. `orderedItems` depends on this. */
|
|
144
|
+
function readList(head, ctx, depth, path) {
|
|
145
|
+
const out = [];
|
|
146
|
+
const walked = new Set();
|
|
147
|
+
let node = head;
|
|
148
|
+
while (node && node !== RDF_NIL && !walked.has(node)) {
|
|
149
|
+
walked.add(node);
|
|
150
|
+
const preds = ctx.bySubject.get(node);
|
|
151
|
+
if (!preds) break;
|
|
152
|
+
const first = preds.get(RDF_FIRST)?.[0];
|
|
153
|
+
if (first) {
|
|
154
|
+
const value = toValue(first, ctx, depth, path);
|
|
155
|
+
if (value !== null) out.push(value);
|
|
156
|
+
}
|
|
157
|
+
node = preds.get(RDF_REST)?.[0]?.value ?? null;
|
|
158
|
+
}
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function isListHead(term, ctx) {
|
|
163
|
+
if (term.termType !== 'BlankNode' && term.termType !== 'NamedNode') return false;
|
|
164
|
+
return !!ctx.bySubject.get(term.value)?.has(RDF_FIRST);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* A term becomes what a handler expects to find there.
|
|
169
|
+
*
|
|
170
|
+
* A node we hold statements about becomes a nested view, which is what an
|
|
171
|
+
* embedded object was in the JSON. A node we hold nothing about becomes its
|
|
172
|
+
* IRI, which is what a bare reference was. Handlers already test which they
|
|
173
|
+
* got (`typeof activity.object === 'object'`), so both stay readable.
|
|
174
|
+
*/
|
|
175
|
+
function toValue(term, ctx, depth, path) {
|
|
176
|
+
if (term.termType === 'Literal') return term.value;
|
|
177
|
+
if (isListHead(term, ctx)) return readList(term.value, ctx, depth, path);
|
|
178
|
+
const preds = ctx.bySubject.get(term.value);
|
|
179
|
+
if (preds && preds.size) return makeView(term.value, ctx, depth + 1, path);
|
|
180
|
+
return term.termType === 'BlankNode' ? null : term.value;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function readProperty(subject, name, ctx, depth = 0, path = new Set()) {
|
|
184
|
+
const preds = ctx.bySubject.get(subject);
|
|
185
|
+
if (!preds) return undefined;
|
|
186
|
+
|
|
187
|
+
if (name === 'id') return ctx.blanks.has(subject) ? undefined : subject;
|
|
188
|
+
if (name === 'type') {
|
|
189
|
+
const types = (preds.get(RDF_TYPE) ?? []).map(t => BY_IRI.get(t.value) ?? t.value);
|
|
190
|
+
if (!types.length) return undefined;
|
|
191
|
+
return types.length === 1 ? types[0] : types;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const term = TERMS.get(name);
|
|
195
|
+
if (!term) return undefined;
|
|
196
|
+
const values = preds.get(term.iri);
|
|
197
|
+
if (!values || !values.length) return undefined;
|
|
198
|
+
|
|
199
|
+
// `items` and `orderedItems` are the same predicate; the list is what tells
|
|
200
|
+
// them apart, so asking for the ordered one only answers when it is ordered.
|
|
201
|
+
const listed = values.filter(v => isListHead(v, ctx));
|
|
202
|
+
if (term.list) {
|
|
203
|
+
if (!listed.length) return undefined;
|
|
204
|
+
return readList(listed[0].value, ctx, depth, path);
|
|
205
|
+
}
|
|
206
|
+
if (listed.length === values.length && listed.length === 1) return readList(listed[0].value, ctx, depth, path);
|
|
207
|
+
|
|
208
|
+
const out = values.map(v => toValue(v, ctx, depth, path)).filter(v => v !== null);
|
|
209
|
+
if (ALWAYS_LIST.has(name)) return out.flat();
|
|
210
|
+
return out.length === 1 ? out[0] : out;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Build the plain object for one node.
|
|
215
|
+
*
|
|
216
|
+
* Materialised rather than proxied on purpose. A lazy reader would still be
|
|
217
|
+
* reading the graph, but it would be an exotic object, and the handlers pass
|
|
218
|
+
* what they read on to things that spread it, serialise it and structuredClone
|
|
219
|
+
* it into the store — all of which a Proxy either breaks or quietly truncates.
|
|
220
|
+
* What comes out here is an ordinary object whose every value came from a
|
|
221
|
+
* statement.
|
|
222
|
+
*
|
|
223
|
+
* `depth` and `seen` are the guards. The graph is built from a stranger's
|
|
224
|
+
* document, so it can be cyclic or enormous; a node already on the path
|
|
225
|
+
* becomes its IRI rather than being walked again.
|
|
226
|
+
*/
|
|
227
|
+
function makeView(subject, ctx, depth = 0, seen = new Set()) {
|
|
228
|
+
if (depth > MAX_DEPTH || seen.has(subject)) {
|
|
229
|
+
return ctx.blanks.has(subject) ? null : subject;
|
|
230
|
+
}
|
|
231
|
+
const preds = ctx.bySubject.get(subject);
|
|
232
|
+
if (!preds) return ctx.blanks.has(subject) ? null : subject;
|
|
233
|
+
|
|
234
|
+
const path = new Set(seen).add(subject);
|
|
235
|
+
const out = {};
|
|
236
|
+
const id = readProperty(subject, 'id', ctx, depth, path);
|
|
237
|
+
if (id !== undefined) out.id = id;
|
|
238
|
+
const type = readProperty(subject, 'type', ctx, depth, path);
|
|
239
|
+
if (type !== undefined) out.type = type;
|
|
240
|
+
|
|
241
|
+
for (const predicate of preds.keys()) {
|
|
242
|
+
if (predicate === RDF_TYPE || predicate === RDF_FIRST || predicate === RDF_REST) continue;
|
|
243
|
+
// A predicate no context we hold names cannot be asked for by name, so it
|
|
244
|
+
// is left out rather than carried under an IRI no handler would look up.
|
|
245
|
+
for (const name of NAMES_BY_IRI.get(predicate) ?? []) {
|
|
246
|
+
if (name === 'id' || name === 'type') continue;
|
|
247
|
+
const value = readProperty(subject, name, ctx, depth, path);
|
|
248
|
+
if (value !== undefined) out[name] = value;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return out;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* A reader over `parseAS2`'s quads.
|
|
256
|
+
*
|
|
257
|
+
* Returns null when there is nothing to read, which is the same answer the
|
|
258
|
+
* document-shaped read gave for bytes that were not a document.
|
|
259
|
+
*/
|
|
260
|
+
export function graphView(quads, { root = null } = {}) {
|
|
261
|
+
if (!quads || !quads.length) return null;
|
|
262
|
+
const ctx = indexQuads(quads);
|
|
263
|
+
const subject = root ?? findRoot(ctx);
|
|
264
|
+
if (!subject) return null;
|
|
265
|
+
const view = makeView(subject, ctx);
|
|
266
|
+
return (view && typeof view === 'object') ? view : null;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export { TERMS, BY_IRI };
|