UNPKG

4.2 kBJavaScriptView Raw
1// @flow
2
3const ObservedRemoveSet = require('./set');
4const getVerifier = require('./verifier');
5const { InvalidSignatureError } = require('./signed-error');
6
7type Options = {
8 maxAge?:number,
9 bufferPublishing?:number,
10 generateId?: () => string,
11 key: any,
12 format?: string
13};
14
15class SignedObservedRemoveSet<T> extends ObservedRemoveSet<T> {
16 constructor(entries?: Iterable<[T, string, string]>, options?:Options) {
17 super([], options);
18 if (!options || !options.key) {
19 throw new Error('Missing required options.key parameter');
20 }
21 this.verify = getVerifier(options.key, options.format);
22 this.insertionSignatureMap = new Map();
23 this.deletionSignatureMap = new Map();
24 if (!entries) {
25 return;
26 }
27 for (const [value, id, signature] of entries) {
28 this.addSigned(value, id, signature);
29 }
30 }
31
32 insertionSignatureMap: Map<string, string>;
33 deletionSignatureMap: Map<string, string>;
34 verify: (string, ...Array<any>) => boolean;
35
36 /**
37 * Return an array containing all of the set's insertions and deletions.
38 * @return {Array<Array<any>>}
39 */
40 dump():[Array<*>, Array<*>] {
41 const [insertQueue, deleteQueue] = super.dump();
42 const signedInsertQueue = insertQueue.map(([hash, [id, value]]) => {
43 const signature = this.insertionSignatureMap.get(id);
44 if (!signature) {
45 throw new Error(`Missing signature for insertion with id "${id}" and value "${JSON.stringify(value)}"`);
46 }
47 return [signature, id, hash, value];
48 });
49 const signedDeleteQueue = deleteQueue.map(([id, hash]) => {
50 const signature = this.deletionSignatureMap.get(id);
51 if (!signature) {
52 throw new Error(`Missing signature for deletion with id "${id}"`);
53 }
54 return [signature, id, hash];
55 });
56 const queue = [signedInsertQueue, signedDeleteQueue];
57 return queue;
58 }
59
60 flush():void {
61 const now = Date.now();
62 for (const [id] of this.deletions) {
63 const timestamp = parseInt(id.slice(0, 9), 36);
64 if (now - timestamp >= this.maxAge) {
65 this.deletions.delete(id);
66 this.deletionSignatureMap.delete(id);
67 }
68 }
69 }
70
71 process(signedQueue:[Array<*>, Array<*>], skipFlush?: boolean = false):void {
72 const [signedInsertQueue, signedDeleteQueue] = signedQueue;
73 const insertQueue = signedInsertQueue.map(([signature, id, hash, value]) => {
74 if (!this.verify(signature, value, id)) {
75 throw new InvalidSignatureError(`Signature does not match for value ${JSON.stringify(value)}`);
76 }
77 this.insertionSignatureMap.set(id, signature);
78 return [hash, [id, value]];
79 });
80 const deleteQueue = signedDeleteQueue.map(([signature, id, hash]) => {
81 if (!this.verify(signature, id)) {
82 throw new InvalidSignatureError(`Signature does not match for id ${JSON.stringify(id)}`);
83 }
84 this.deletionSignatureMap.set(id, signature);
85 return [id, hash];
86 });
87 const queue:[Array<[string, [string, T]]>, Array<[string, string]>] = [insertQueue, deleteQueue];
88 super.process(queue, skipFlush);
89 for (const [signature, id, hash] of signedInsertQueue) { // eslint-disable-line no-unused-vars
90 const pair = this.pairs.get(hash);
91 if (!pair || pair[0] !== id) {
92 this.insertionSignatureMap.delete(id);
93 }
94 }
95 }
96
97 addSigned(value:T, id:string, signature:string) {
98 const hash = this.hash(value);
99 const message = [signature, id, hash, value];
100 this.process([[message], []], true);
101 this.insertQueue.push(message);
102 this.dequeue();
103 return this;
104 }
105
106 deleteSignedId(id:string, signature:string):void {
107 for (const [hash, [pairId]] of this.pairs) {
108 if (pairId === id) {
109 const message = [signature, id, hash];
110 this.process([[], [message]], true);
111 this.deleteQueue.push(message);
112 this.dequeue();
113 return;
114 }
115 }
116 }
117
118 clear():void {
119 throw new Error('Unsupported method clear()');
120 }
121
122 add() {
123 throw new Error('Unsupported method add(), use addSigned()');
124 }
125
126 delete() {
127 throw new Error('Unsupported method delete(), use deleteSignedId()');
128 }
129}
130
131module.exports = SignedObservedRemoveSet;