@secrecy/lib 1.62.0-feat-node-sharing.3 → 1.62.0-feat-node-sharing.4
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/dist/lib/client/SecrecyAppClient.js +2 -2
- package/dist/lib/client/SecrecyCloudClient.js +78 -28
- package/dist/lib/utils/object.js +29 -0
- package/dist/types/client/SecrecyCloudClient.d.ts +1 -1
- package/dist/types/client/types/node.d.ts +17 -0
- package/dist/types/utils/object.d.ts +1 -0
- package/package.json +1 -1
|
@@ -76,12 +76,12 @@ export class SecrecyAppClient {
|
|
|
76
76
|
...new Set(userIds.filter((userId) => publicKeys[userId] === undefined)),
|
|
77
77
|
];
|
|
78
78
|
if (missingKeys.length > 0) {
|
|
79
|
-
const userKeysMap = await this.#apiClient.application.userPublicKey.query(
|
|
79
|
+
const userKeysMap = await this.#apiClient.application.userPublicKey.query(missingKeys);
|
|
80
80
|
if ('publicKey' in userKeysMap) {
|
|
81
81
|
throw Error('Should not happen!');
|
|
82
82
|
}
|
|
83
83
|
if (Object.keys(userKeysMap).length !== missingKeys.length) {
|
|
84
|
-
throw new Error("Unable to load some user's
|
|
84
|
+
throw new Error("Unable to load some user's public keys!");
|
|
85
85
|
}
|
|
86
86
|
for (const userId in userKeysMap) {
|
|
87
87
|
publicKeys[userId] = userKeysMap[userId];
|
|
@@ -14,6 +14,7 @@ import { promiseAllLimit } from '../utils/promise.js';
|
|
|
14
14
|
import { kiloToBytes } from '../utils.js';
|
|
15
15
|
import { fileTypeFromBuffer } from 'file-type';
|
|
16
16
|
import { encryptName, generateAndEncryptNameAndKey } from '../crypto/domain.js';
|
|
17
|
+
import { chunkByTotalItems } from '../utils/object.js';
|
|
17
18
|
export class SecrecyCloudClient {
|
|
18
19
|
#client;
|
|
19
20
|
#keys;
|
|
@@ -32,18 +33,24 @@ export class SecrecyCloudClient {
|
|
|
32
33
|
const data = node.history.find((d) => d.id === dataId);
|
|
33
34
|
if (data !== undefined) {
|
|
34
35
|
const users = node.users.filter(([u]) => u.id !== this.#client.app.userId);
|
|
36
|
+
const userIds = users.map(([user]) => user.id);
|
|
37
|
+
const userKeys = await this.#client.app.userPublicKey(userIds);
|
|
38
|
+
const shares = users.map(([user]) => {
|
|
39
|
+
const publicKey = userKeys[user.id];
|
|
40
|
+
if (!publicKey) {
|
|
41
|
+
throw new Error('Unable to retreive share by public key!');
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
id: user.id,
|
|
45
|
+
key: data.key
|
|
46
|
+
? sodium.to_hex(encryptCryptoBox(sodium.from_hex(data.key), publicKey, this.#keys.privateKey))
|
|
47
|
+
: null,
|
|
48
|
+
};
|
|
49
|
+
});
|
|
35
50
|
await this.#apiClient.cloud.shareDataInHistory.mutate({
|
|
36
51
|
dataId: data.id,
|
|
37
52
|
nodeId,
|
|
38
|
-
users:
|
|
39
|
-
const userPubKey = await this.#client.app.userPublicKey(u.id);
|
|
40
|
-
return {
|
|
41
|
-
id: u.id,
|
|
42
|
-
key: data.key
|
|
43
|
-
? sodium.to_hex(encryptCryptoBox(sodium.from_hex(data.key), userPubKey, this.#keys.privateKey))
|
|
44
|
-
: null,
|
|
45
|
-
};
|
|
46
|
-
})),
|
|
53
|
+
users: shares,
|
|
47
54
|
});
|
|
48
55
|
}
|
|
49
56
|
return internalNodeFullToNodeFull(node);
|
|
@@ -318,7 +325,7 @@ export class SecrecyCloudClient {
|
|
|
318
325
|
});
|
|
319
326
|
return apiDataToExternal(data, this.#keys);
|
|
320
327
|
}
|
|
321
|
-
async shareNode(input) {
|
|
328
|
+
async shareNode(input, progress) {
|
|
322
329
|
const userIds = 'rights' in input
|
|
323
330
|
? Array.isArray(input.nodes)
|
|
324
331
|
? input.nodes.map(({ userId }) => userId)
|
|
@@ -326,27 +333,70 @@ export class SecrecyCloudClient {
|
|
|
326
333
|
? input.nodes.userIds
|
|
327
334
|
: [input.nodes.userId]
|
|
328
335
|
: input.users.map(({ id }) => id);
|
|
329
|
-
const nodesToShare = 'nodes' in input
|
|
330
|
-
? input.nodes
|
|
331
|
-
: {
|
|
332
|
-
nodeId: input.nodeId,
|
|
333
|
-
userIds: input.users.map(({ id }) => id),
|
|
334
|
-
};
|
|
335
336
|
const [publicKeysMap, nodesIdsMap] = await Promise.all([
|
|
336
337
|
this.#client.app.userPublicKey(userIds),
|
|
337
|
-
this.#apiClient.cloud.shareNode.mutate(
|
|
338
|
+
this.#apiClient.cloud.shareNode.mutate('nodes' in input
|
|
339
|
+
? input.nodes
|
|
340
|
+
: {
|
|
341
|
+
nodeId: input.nodeId,
|
|
342
|
+
userIds: input.users.map(({ id }) => id),
|
|
343
|
+
}),
|
|
338
344
|
]);
|
|
339
|
-
const
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
345
|
+
const maxNodesBatchSize = 1000;
|
|
346
|
+
const totalNodesToShare = Object.values(nodesIdsMap).reduce((size, ids) => size + ids.length, 0);
|
|
347
|
+
const chunks = totalNodesToShare > maxNodesBatchSize
|
|
348
|
+
? chunkByTotalItems(nodesIdsMap, maxNodesBatchSize)
|
|
349
|
+
: [nodesIdsMap];
|
|
350
|
+
const details = await chunks.reduce(async (pendingState, nodesMap, index) => {
|
|
351
|
+
const state = await pendingState;
|
|
352
|
+
const infos = await this.encryptNodesForUsers(nodesMap, publicKeysMap);
|
|
353
|
+
const subState = await this.#apiClient.cloud.shareNodeFinish.mutate(Object.entries(infos).map('rights' in input
|
|
354
|
+
? ([userId, nodes]) => ({ userId, nodes, rights: input.rights })
|
|
355
|
+
: ([userId, nodes]) => {
|
|
356
|
+
const user = input.users.find((user) => user.id === userId);
|
|
357
|
+
if (!user) {
|
|
358
|
+
throw new Error(`Unable to find rights for user: ${userId}`);
|
|
359
|
+
}
|
|
360
|
+
return { userId, nodes, rights: user.rights };
|
|
361
|
+
}));
|
|
362
|
+
const currentProgress = Math.min((index + 1) * maxNodesBatchSize, totalNodesToShare);
|
|
363
|
+
progress?.({
|
|
364
|
+
total: totalNodesToShare,
|
|
365
|
+
current: currentProgress,
|
|
366
|
+
percent: Math.round((currentProgress / totalNodesToShare) * 100),
|
|
367
|
+
});
|
|
368
|
+
return {
|
|
369
|
+
missingNodeAccesses: [
|
|
370
|
+
...state.missingNodeAccesses,
|
|
371
|
+
...(subState.isFinished
|
|
372
|
+
? []
|
|
373
|
+
: subState.details.missingNodeAccesses),
|
|
374
|
+
],
|
|
375
|
+
missingDataAccesses: [
|
|
376
|
+
...state.missingDataAccesses,
|
|
377
|
+
...(subState.isFinished
|
|
378
|
+
? []
|
|
379
|
+
: subState.details.missingDataAccesses),
|
|
380
|
+
],
|
|
381
|
+
invalidRightsAccesses: [
|
|
382
|
+
...state.invalidRightsAccesses,
|
|
383
|
+
...(subState.isFinished
|
|
384
|
+
? []
|
|
385
|
+
: subState.details.invalidRightsAccesses),
|
|
386
|
+
],
|
|
387
|
+
};
|
|
388
|
+
}, Promise.resolve({
|
|
389
|
+
missingNodeAccesses: [],
|
|
390
|
+
missingDataAccesses: [],
|
|
391
|
+
invalidRightsAccesses: [],
|
|
392
|
+
}));
|
|
393
|
+
const errorDetailsLength = details.invalidRightsAccesses.length +
|
|
394
|
+
details.missingDataAccesses.length +
|
|
395
|
+
details.missingNodeAccesses.length;
|
|
396
|
+
return {
|
|
397
|
+
isFinished: errorDetailsLength === 0,
|
|
398
|
+
details: details,
|
|
399
|
+
};
|
|
350
400
|
}
|
|
351
401
|
async updateNode({ nodeId, name, isFavorite, deletedAt, }) {
|
|
352
402
|
let node = nodesCache.get(nodeId);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export function chunkByTotalItems(input, maxItemsPerChunk = 1000) {
|
|
2
|
+
const entries = Object.entries(input);
|
|
3
|
+
const chunks = [];
|
|
4
|
+
let currentChunk = {};
|
|
5
|
+
let currentCount = 0;
|
|
6
|
+
for (const [key, values] of entries) {
|
|
7
|
+
let i = 0;
|
|
8
|
+
while (i < values.length) {
|
|
9
|
+
const remaining = maxItemsPerChunk - currentCount;
|
|
10
|
+
const take = Math.min(remaining, values.length - i);
|
|
11
|
+
if (!currentChunk[key]) {
|
|
12
|
+
currentChunk[key] = [];
|
|
13
|
+
}
|
|
14
|
+
currentChunk[key].push(...values.slice(i, i + take));
|
|
15
|
+
currentCount += take;
|
|
16
|
+
i += take;
|
|
17
|
+
if (currentCount === maxItemsPerChunk) {
|
|
18
|
+
chunks.push(currentChunk);
|
|
19
|
+
currentChunk = {};
|
|
20
|
+
currentCount = 0;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// Last chunk
|
|
25
|
+
if (currentCount > 0) {
|
|
26
|
+
chunks.push(currentChunk);
|
|
27
|
+
}
|
|
28
|
+
return chunks;
|
|
29
|
+
}
|
|
@@ -75,7 +75,7 @@ export declare class SecrecyCloudClient {
|
|
|
75
75
|
id: string;
|
|
76
76
|
rights: Rights;
|
|
77
77
|
}[];
|
|
78
|
-
}): Promise<RouterOutputs['cloud']['shareNodeFinish']>;
|
|
78
|
+
}, progress?: ProgressCallback): Promise<RouterOutputs['cloud']['shareNodeFinish']>;
|
|
79
79
|
updateNode({ nodeId, name, isFavorite, deletedAt, }: {
|
|
80
80
|
nodeId: string;
|
|
81
81
|
name?: string | null | undefined;
|
|
@@ -70,4 +70,21 @@ export type EncryptedNodeInfos = {
|
|
|
70
70
|
key: string | null;
|
|
71
71
|
}[];
|
|
72
72
|
};
|
|
73
|
+
export type ShareNodeDetails = {
|
|
74
|
+
missingNodeAccesses: {
|
|
75
|
+
userId: string;
|
|
76
|
+
nodeId: string;
|
|
77
|
+
}[];
|
|
78
|
+
missingDataAccesses: {
|
|
79
|
+
userId: string;
|
|
80
|
+
dataId: string;
|
|
81
|
+
nodeId: string;
|
|
82
|
+
}[];
|
|
83
|
+
invalidRightsAccesses: {
|
|
84
|
+
userId: string;
|
|
85
|
+
current: 'admin' | 'write' | 'read';
|
|
86
|
+
nodeId: string;
|
|
87
|
+
expect: 'admin' | 'write' | 'read';
|
|
88
|
+
}[];
|
|
89
|
+
};
|
|
73
90
|
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function chunkByTotalItems<T>(input: Record<string, T[]>, maxItemsPerChunk?: number): Record<string, T[]>[];
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@secrecy/lib",
|
|
3
3
|
"author": "Anonymize <anonymize@gmail.com>",
|
|
4
4
|
"description": "Anonymize Secrecy Library",
|
|
5
|
-
"version": "1.62.0-feat-node-sharing.
|
|
5
|
+
"version": "1.62.0-feat-node-sharing.4",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "https://github.com/anonymize-org/lib.git"
|