/
index.ts
112 lines (98 loc) · 2.96 KB
/
index.ts
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
import type { CID } from 'multiformats/cid'
import type {
CidList,
PinningBackend,
PinningBackendStatic,
PinningInfo,
IpfsApi,
} from '@ceramicnetwork/common'
import { toString } from 'uint8arrays/to-string'
import * as sha256 from '@stablelib/sha256'
export class UnknownPinningService extends Error {
constructor(designator: string | null) {
super(`Unknown pinning service ${designator}`)
}
}
const textEncoder = new TextEncoder()
function uniq<A>(input: A[]): A[] {
return [...new Set(input)]
}
/**
* Multitude of pinning services united.
*/
export class PinningAggregation implements PinningBackend {
readonly id: string
readonly backends: PinningBackend[]
static build(
ipfs: IpfsApi,
connectionStrings: string[],
pinners: Array<PinningBackendStatic> = []
): PinningAggregation {
const backends = connectionStrings.map<PinningBackend>((s) => {
const protocol = s.match(`://`) ? new URL(s).protocol.replace(':', '') : s
const match = protocol.match(/^(\w+)\+?/)
const designator = match ? match[1] : ''
const found = pinners.find((pinner) => pinner.designator === designator)
if (found) {
return new found(s, ipfs)
} else {
throw new UnknownPinningService(designator)
}
})
return new PinningAggregation(backends)
}
constructor(backends: PinningBackend[]) {
this.backends = backends
const allIds = this.backends.map((b) => b.id).join('\n')
const bytes = textEncoder.encode(allIds)
const digest = toString(sha256.hash(bytes), 'base64urlpad')
this.id = `pinning-aggregation@${digest}`
}
/**
* Open all the services.
* Async semantics: every call should succeed.
*/
open(): void {
this.backends.forEach((service) => service.open())
}
/**
* Close all the services.
* Async semantics: every call should succeed.
*/
async close(): Promise<void> {
await Promise.all(this.backends.map(async (service) => service.close()))
}
/**
* Pin stream.
* Async semantics: every call should succeed.
*/
async pin(cid: CID): Promise<void> {
await Promise.all(this.backends.map(async (service) => service.pin(cid)))
}
/**
* Unpin CID.
* Async semantics: individual call failures do not propagate upstream; anything goes.
* @param cid
*/
async unpin(cid: CID): Promise<void> {
Promise.all(this.backends.map(async (service) => service.unpin(cid))).catch(() => {
// noop
})
}
/**
* List pinned CIDs.
*/
async ls(): Promise<CidList> {
const perBackend = await Promise.all(this.backends.map((b) => b.ls()))
const allCids = uniq(perBackend.flatMap((p) => Object.keys(p)))
const result: CidList = {}
allCids.forEach((cid) => {
result[cid] = perBackend.flatMap((p) => p[cid]).filter(Boolean)
})
return result
}
async info(): Promise<PinningInfo> {
const perBackend = await Promise.all(this.backends.map((b) => b.info()))
return Object.assign({}, ...perBackend)
}
}