-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtemplate.js
118 lines (103 loc) · 3.59 KB
/
template.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
// SPDX-FileCopyrightText: 2021 Andre 'Staltz' Medeiros
//
// SPDX-License-Identifier: LGPL-3.0-only
const { QL0 } = require('ssb-subset-ql')
const pickShard = require('ssb-meta-feeds/pick-shard')
function isEmptyObject(obj) {
return Object.keys(obj).length === 0
}
/**
* Algorithms for the "replication template" object from the SSB config.
*/
module.exports = class Template {
constructor(leafShapes) {
/** @type Array{*} */
this._leafShapes = leafShapes
}
hasIndexLeaf() {
return this._leafShapes.some((shape) => shape && shape.purpose === 'index')
}
matchBranch(branch, mainFeedId, myGroupSecrets) {
return this._matchBranch(branch, mainFeedId, myGroupSecrets)
}
_matchBranch(branch, mainFeedId = null, myGroupSecrets) {
if (!branch || !Array.isArray(branch)) return false
const [rootMF, , shardMF, leafFeed] = branch
switch (branch.length) {
case 0:
return false
case 1:
return true
case 2:
return true
case 3:
return this._matchShard(rootMF, shardMF, myGroupSecrets)
case 4:
return this._matchLeaf(leafFeed, rootMF.id, mainFeedId, myGroupSecrets)
default:
return false
}
}
_matchShard(rootMF, shardMF, myGroupSecrets) {
return this._leafShapes.some((leaf) => {
if (isEmptyObject(leaf)) return true
if (leaf.purpose === '$groupSecret') {
return Array.from(myGroupSecrets)
.map((secretPurpose) => pickShard(rootMF.id, secretPurpose))
.includes(shardMF.purpose)
}
const shardPurpose = pickShard(rootMF.id, leaf.purpose)
return shardPurpose === shardMF.purpose
})
}
_matchLeaf(leafFeedDetails, rootID, mainID = null, myGroupSecrets) {
return this._leafShapes.some((shape) => {
// Empty shape means "accept any leaf"
if (isEmptyObject(shape)) return true
if (shape.purpose === '$groupSecret') {
if (!myGroupSecrets.has(leafFeedDetails.purpose)) {
return false
}
}
// If present, purpose must match
else if (leafFeedDetails.purpose !== shape.purpose) {
return false
}
// If present, metadata must match
if (shape.metadata && leafFeedDetails.metadata) {
// If querylang is present, match ssb-ql-0 queries
if (shape.metadata.querylang !== leafFeedDetails.metadata.querylang) {
return false
}
if (shape.metadata.querylang === 'ssb-ql-0') {
if (!QL0.parse(leafFeedDetails.metadata.query)) return false
if (shape.metadata.query) {
const shapeQuery = { ...shape.metadata.query }
if (shapeQuery.author === '$main') shapeQuery.author = mainID
if (shapeQuery.author === '$root') shapeQuery.author = rootID
if (!QL0.isEquals(shapeQuery, leafFeedDetails.metadata.query)) {
return false
}
}
}
// Any other metadata field must match exactly
for (const field of Object.keys(shape.metadata)) {
// Ignore these because we already handled them:
if (field === 'query') continue
if (field === 'querylang') continue
if (typeof shape.metadata[field] === 'string') {
const fieldValue = shape.metadata[field]
.replace('$main', mainID)
.replace('$root', rootID)
if (fieldValue !== leafFeedDetails.metadata[field]) return false
} else if (
shape.metadata[field] !== leafFeedDetails.metadata[field]
) {
return false
}
}
}
return true
})
}
}