@protontech/drive-sdk 0.4.1 → 0.5.1
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/diagnostic/{sdkDiagnosticFull.d.ts → diagnostic.d.ts} +5 -4
- package/dist/diagnostic/{sdkDiagnosticFull.js → diagnostic.js} +13 -10
- package/dist/diagnostic/diagnostic.js.map +1 -0
- package/dist/diagnostic/index.js +2 -4
- package/dist/diagnostic/index.js.map +1 -1
- package/dist/diagnostic/interface.d.ts +22 -1
- package/dist/diagnostic/sdkDiagnostic.d.ts +3 -2
- package/dist/diagnostic/sdkDiagnostic.js +80 -8
- package/dist/diagnostic/sdkDiagnostic.js.map +1 -1
- package/dist/interface/download.d.ts +4 -4
- package/dist/interface/index.d.ts +1 -1
- package/dist/interface/index.js.map +1 -1
- package/dist/interface/nodes.d.ts +9 -0
- package/dist/interface/telemetry.d.ts +4 -1
- package/dist/interface/telemetry.js.map +1 -1
- package/dist/interface/upload.d.ts +6 -3
- package/dist/internal/apiService/apiService.d.ts +3 -0
- package/dist/internal/apiService/apiService.js +25 -2
- package/dist/internal/apiService/apiService.js.map +1 -1
- package/dist/internal/apiService/apiService.test.js +38 -0
- package/dist/internal/apiService/apiService.test.js.map +1 -1
- package/dist/internal/apiService/driveTypes.d.ts +2595 -2397
- package/dist/internal/apiService/errors.js +3 -0
- package/dist/internal/apiService/errors.js.map +1 -1
- package/dist/internal/apiService/errors.test.js +15 -7
- package/dist/internal/apiService/errors.test.js.map +1 -1
- package/dist/internal/asyncIteratorMap.d.ts +1 -1
- package/dist/internal/asyncIteratorMap.js +6 -1
- package/dist/internal/asyncIteratorMap.js.map +1 -1
- package/dist/internal/asyncIteratorMap.test.js +9 -0
- package/dist/internal/asyncIteratorMap.test.js.map +1 -1
- package/dist/internal/download/controller.d.ts +2 -0
- package/dist/internal/download/controller.js +15 -1
- package/dist/internal/download/controller.js.map +1 -1
- package/dist/internal/download/fileDownloader.d.ts +3 -3
- package/dist/internal/download/fileDownloader.js +11 -6
- package/dist/internal/download/fileDownloader.js.map +1 -1
- package/dist/internal/download/fileDownloader.test.js +8 -8
- package/dist/internal/download/fileDownloader.test.js.map +1 -1
- package/dist/internal/nodes/apiService.d.ts +6 -1
- package/dist/internal/nodes/apiService.js +71 -44
- package/dist/internal/nodes/apiService.js.map +1 -1
- package/dist/internal/nodes/apiService.test.js +204 -15
- package/dist/internal/nodes/apiService.test.js.map +1 -1
- package/dist/internal/nodes/debouncer.d.ts +24 -0
- package/dist/internal/nodes/debouncer.js +92 -0
- package/dist/internal/nodes/debouncer.js.map +1 -0
- package/dist/internal/nodes/debouncer.test.d.ts +1 -0
- package/dist/internal/nodes/debouncer.test.js +108 -0
- package/dist/internal/nodes/debouncer.test.js.map +1 -0
- package/dist/internal/nodes/extendedAttributes.js +2 -2
- package/dist/internal/nodes/extendedAttributes.js.map +1 -1
- package/dist/internal/nodes/index.js +1 -1
- package/dist/internal/nodes/index.js.map +1 -1
- package/dist/internal/nodes/nodesAccess.d.ts +6 -4
- package/dist/internal/nodes/nodesAccess.js +29 -9
- package/dist/internal/nodes/nodesAccess.js.map +1 -1
- package/dist/internal/nodes/nodesAccess.test.js +19 -7
- package/dist/internal/nodes/nodesAccess.test.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.d.ts +2 -2
- package/dist/internal/nodes/nodesManagement.js +5 -3
- package/dist/internal/nodes/nodesManagement.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.test.js +3 -1
- package/dist/internal/nodes/nodesManagement.test.js.map +1 -1
- package/dist/internal/photos/apiService.js +9 -20
- package/dist/internal/photos/apiService.js.map +1 -1
- package/dist/internal/photos/upload.d.ts +2 -1
- package/dist/internal/photos/upload.js +9 -3
- package/dist/internal/photos/upload.js.map +1 -1
- package/dist/internal/sharing/apiService.d.ts +1 -1
- package/dist/internal/sharing/apiService.js +2 -2
- package/dist/internal/sharing/apiService.js.map +1 -1
- package/dist/internal/sharing/sharingManagement.d.ts +4 -1
- package/dist/internal/sharing/sharingManagement.js +7 -4
- package/dist/internal/sharing/sharingManagement.js.map +1 -1
- package/dist/internal/sharingPublic/apiService.d.ts +8 -10
- package/dist/internal/sharingPublic/apiService.js +9 -125
- package/dist/internal/sharingPublic/apiService.js.map +1 -1
- package/dist/internal/sharingPublic/cryptoReporter.d.ts +16 -0
- package/dist/internal/sharingPublic/{cryptoService.js → cryptoReporter.js} +3 -16
- package/dist/internal/sharingPublic/cryptoReporter.js.map +1 -0
- package/dist/internal/sharingPublic/index.d.ts +22 -4
- package/dist/internal/sharingPublic/index.js +37 -12
- package/dist/internal/sharingPublic/index.js.map +1 -1
- package/dist/internal/sharingPublic/nodes.d.ts +18 -0
- package/dist/internal/sharingPublic/nodes.js +46 -0
- package/dist/internal/sharingPublic/nodes.js.map +1 -0
- package/dist/internal/sharingPublic/session/apiService.d.ts +7 -5
- package/dist/internal/sharingPublic/session/apiService.js +25 -4
- package/dist/internal/sharingPublic/session/apiService.js.map +1 -1
- package/dist/internal/sharingPublic/session/interface.d.ts +17 -0
- package/dist/internal/sharingPublic/session/manager.d.ts +12 -4
- package/dist/internal/sharingPublic/session/manager.js +14 -4
- package/dist/internal/sharingPublic/session/manager.js.map +1 -1
- package/dist/internal/sharingPublic/session/session.d.ts +7 -4
- package/dist/internal/sharingPublic/session/session.js +7 -3
- package/dist/internal/sharingPublic/session/session.js.map +1 -1
- package/dist/internal/sharingPublic/session/url.test.js +3 -3
- package/dist/internal/sharingPublic/shares.d.ts +27 -0
- package/dist/internal/sharingPublic/shares.js +46 -0
- package/dist/internal/sharingPublic/shares.js.map +1 -0
- package/dist/internal/upload/apiService.js +10 -1
- package/dist/internal/upload/apiService.js.map +1 -1
- package/dist/internal/upload/controller.d.ts +11 -3
- package/dist/internal/upload/controller.js +16 -2
- package/dist/internal/upload/controller.js.map +1 -1
- package/dist/internal/upload/fileUploader.d.ts +6 -3
- package/dist/internal/upload/fileUploader.js +4 -4
- package/dist/internal/upload/fileUploader.js.map +1 -1
- package/dist/internal/upload/fileUploader.test.js +23 -11
- package/dist/internal/upload/fileUploader.test.js.map +1 -1
- package/dist/internal/upload/streamUploader.d.ts +9 -4
- package/dist/internal/upload/streamUploader.js +67 -20
- package/dist/internal/upload/streamUploader.js.map +1 -1
- package/dist/internal/upload/streamUploader.test.js +43 -13
- package/dist/internal/upload/streamUploader.test.js.map +1 -1
- package/dist/protonDriveClient.d.ts +11 -6
- package/dist/protonDriveClient.js +11 -10
- package/dist/protonDriveClient.js.map +1 -1
- package/dist/protonDrivePublicLinkClient.d.ts +34 -6
- package/dist/protonDrivePublicLinkClient.js +52 -9
- package/dist/protonDrivePublicLinkClient.js.map +1 -1
- package/dist/tests/telemetry.d.ts +4 -2
- package/dist/tests/telemetry.js +3 -1
- package/dist/tests/telemetry.js.map +1 -1
- package/dist/transformers.d.ts +3 -2
- package/dist/transformers.js +6 -0
- package/dist/transformers.js.map +1 -1
- package/package.json +1 -1
- package/src/diagnostic/{sdkDiagnosticFull.ts → diagnostic.ts} +10 -6
- package/src/diagnostic/index.ts +3 -5
- package/src/diagnostic/interface.ts +39 -0
- package/src/diagnostic/sdkDiagnostic.ts +111 -10
- package/src/interface/download.ts +4 -4
- package/src/interface/index.ts +1 -0
- package/src/interface/nodes.ts +3 -0
- package/src/interface/telemetry.ts +5 -0
- package/src/interface/upload.ts +3 -3
- package/src/internal/apiService/apiService.test.ts +50 -0
- package/src/internal/apiService/apiService.ts +33 -2
- package/src/internal/apiService/driveTypes.ts +2713 -2561
- package/src/internal/apiService/errors.test.ts +10 -0
- package/src/internal/apiService/errors.ts +5 -1
- package/src/internal/asyncIteratorMap.test.ts +12 -0
- package/src/internal/asyncIteratorMap.ts +8 -0
- package/src/internal/download/controller.ts +13 -1
- package/src/internal/download/fileDownloader.test.ts +8 -8
- package/src/internal/download/fileDownloader.ts +13 -6
- package/src/internal/nodes/apiService.test.ts +261 -14
- package/src/internal/nodes/apiService.ts +99 -65
- package/src/internal/nodes/debouncer.test.ts +141 -0
- package/src/internal/nodes/debouncer.ts +109 -0
- package/src/internal/nodes/extendedAttributes.ts +2 -2
- package/src/internal/nodes/index.ts +1 -8
- package/src/internal/nodes/nodesAccess.test.ts +19 -7
- package/src/internal/nodes/nodesAccess.ts +44 -9
- package/src/internal/nodes/nodesManagement.test.ts +3 -1
- package/src/internal/nodes/nodesManagement.ts +11 -5
- package/src/internal/photos/apiService.ts +12 -29
- package/src/internal/photos/upload.ts +22 -1
- package/src/internal/sharing/apiService.ts +2 -2
- package/src/internal/sharing/sharingManagement.ts +7 -4
- package/src/internal/sharingPublic/apiService.ts +23 -160
- package/src/internal/sharingPublic/{cryptoService.ts → cryptoReporter.ts} +2 -27
- package/src/internal/sharingPublic/index.ts +76 -13
- package/src/internal/sharingPublic/nodes.ts +59 -0
- package/src/internal/sharingPublic/session/apiService.ts +32 -10
- package/src/internal/sharingPublic/session/interface.ts +20 -0
- package/src/internal/sharingPublic/session/manager.ts +31 -8
- package/src/internal/sharingPublic/session/session.ts +12 -7
- package/src/internal/sharingPublic/session/url.test.ts +3 -3
- package/src/internal/sharingPublic/shares.ts +50 -0
- package/src/internal/upload/apiService.ts +12 -1
- package/src/internal/upload/controller.ts +16 -4
- package/src/internal/upload/fileUploader.test.ts +25 -11
- package/src/internal/upload/fileUploader.ts +6 -5
- package/src/internal/upload/streamUploader.test.ts +56 -12
- package/src/internal/upload/streamUploader.ts +78 -20
- package/src/protonDriveClient.ts +29 -11
- package/src/protonDrivePublicLinkClient.ts +100 -16
- package/src/tests/telemetry.ts +6 -3
- package/src/transformers.ts +8 -0
- package/dist/diagnostic/sdkDiagnosticFull.js.map +0 -1
- package/dist/internal/sharingPublic/cryptoCache.d.ts +0 -19
- package/dist/internal/sharingPublic/cryptoCache.js +0 -72
- package/dist/internal/sharingPublic/cryptoCache.js.map +0 -1
- package/dist/internal/sharingPublic/cryptoService.d.ts +0 -9
- package/dist/internal/sharingPublic/cryptoService.js.map +0 -1
- package/dist/internal/sharingPublic/interface.d.ts +0 -6
- package/dist/internal/sharingPublic/interface.js +0 -3
- package/dist/internal/sharingPublic/interface.js.map +0 -1
- package/dist/internal/sharingPublic/manager.d.ts +0 -19
- package/dist/internal/sharingPublic/manager.js +0 -81
- package/dist/internal/sharingPublic/manager.js.map +0 -1
- package/src/internal/sharingPublic/cryptoCache.ts +0 -79
- package/src/internal/sharingPublic/interface.ts +0 -14
- package/src/internal/sharingPublic/manager.ts +0 -86
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { c } from 'ttag';
|
|
2
2
|
|
|
3
3
|
import { PrivateKey, SessionKey } from '../../crypto';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
InvalidNameError,
|
|
6
|
+
Logger,
|
|
7
|
+
MissingNode,
|
|
8
|
+
NodeType,
|
|
9
|
+
ProtonDriveTelemetry,
|
|
10
|
+
Result,
|
|
11
|
+
resultError,
|
|
12
|
+
resultOk,
|
|
13
|
+
} from '../../interface';
|
|
5
14
|
import { DecryptionError, ProtonDriveError } from '../../errors';
|
|
6
15
|
import { asyncIteratorMap } from '../asyncIteratorMap';
|
|
7
16
|
import { getErrorMessage } from '../errors';
|
|
@@ -11,6 +20,7 @@ import { NodeAPIService } from './apiService';
|
|
|
11
20
|
import { NodesCache } from './cache';
|
|
12
21
|
import { NodesCryptoCache } from './cryptoCache';
|
|
13
22
|
import { NodesCryptoService } from './cryptoService';
|
|
23
|
+
import { NodesDebouncer } from './debouncer';
|
|
14
24
|
import { parseFileExtendedAttributes, parseFolderExtendedAttributes } from './extendedAttributes';
|
|
15
25
|
import {
|
|
16
26
|
SharesService,
|
|
@@ -40,20 +50,27 @@ const DECRYPTION_CONCURRENCY = 30;
|
|
|
40
50
|
* nodes metadata.
|
|
41
51
|
*/
|
|
42
52
|
export class NodesAccess {
|
|
53
|
+
private logger: Logger;
|
|
54
|
+
private debouncer: NodesDebouncer;
|
|
55
|
+
|
|
43
56
|
constructor(
|
|
44
|
-
private
|
|
57
|
+
private telemetry: ProtonDriveTelemetry,
|
|
45
58
|
private apiService: NodeAPIService,
|
|
46
59
|
private cache: NodesCache,
|
|
47
60
|
private cryptoCache: NodesCryptoCache,
|
|
48
61
|
private cryptoService: NodesCryptoService,
|
|
49
|
-
private shareService:
|
|
62
|
+
private shareService: Pick<
|
|
63
|
+
SharesService,
|
|
64
|
+
'getOwnVolumeIDs' | 'getSharePrivateKey' | 'getContextShareMemberEmailKey'
|
|
65
|
+
>,
|
|
50
66
|
) {
|
|
51
|
-
this.logger =
|
|
67
|
+
this.logger = telemetry.getLogger('nodes');
|
|
52
68
|
this.apiService = apiService;
|
|
53
69
|
this.cache = cache;
|
|
54
70
|
this.cryptoCache = cryptoCache;
|
|
55
71
|
this.cryptoService = cryptoService;
|
|
56
72
|
this.shareService = shareService;
|
|
73
|
+
this.debouncer = new NodesDebouncer(this.telemetry);
|
|
57
74
|
}
|
|
58
75
|
|
|
59
76
|
async getVolumeRootFolder() {
|
|
@@ -65,6 +82,7 @@ export class NodesAccess {
|
|
|
65
82
|
async getNode(nodeUid: string): Promise<DecryptedNode> {
|
|
66
83
|
let cachedNode;
|
|
67
84
|
try {
|
|
85
|
+
await this.debouncer.waitForLoadingNode(nodeUid);
|
|
68
86
|
cachedNode = await this.cache.getNode(nodeUid);
|
|
69
87
|
} catch {}
|
|
70
88
|
|
|
@@ -112,6 +130,7 @@ export class NodesAccess {
|
|
|
112
130
|
for await (const nodeUid of this.apiService.iterateChildrenNodeUids(parentNode.uid, onlyFolders, signal)) {
|
|
113
131
|
let node;
|
|
114
132
|
try {
|
|
133
|
+
await this.debouncer.waitForLoadingNode(nodeUid);
|
|
115
134
|
node = await this.cache.getNode(nodeUid);
|
|
116
135
|
} catch {}
|
|
117
136
|
|
|
@@ -143,6 +162,7 @@ export class NodesAccess {
|
|
|
143
162
|
for await (const nodeUid of this.apiService.iterateTrashedNodeUids(volumeId, signal)) {
|
|
144
163
|
let node;
|
|
145
164
|
try {
|
|
165
|
+
await this.debouncer.waitForLoadingNode(nodeUid);
|
|
146
166
|
node = await this.cache.getNode(nodeUid);
|
|
147
167
|
} catch {}
|
|
148
168
|
|
|
@@ -208,9 +228,14 @@ export class NodesAccess {
|
|
|
208
228
|
}
|
|
209
229
|
|
|
210
230
|
private async loadNode(nodeUid: string): Promise<{ node: DecryptedNode; keys?: DecryptedNodeKeys }> {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
231
|
+
this.debouncer.loadingNode(nodeUid);
|
|
232
|
+
try {
|
|
233
|
+
const { volumeId: ownVolumeId } = await this.shareService.getOwnVolumeIDs();
|
|
234
|
+
const encryptedNode = await this.apiService.getNode(nodeUid, ownVolumeId);
|
|
235
|
+
return this.decryptNode(encryptedNode);
|
|
236
|
+
} finally {
|
|
237
|
+
this.debouncer.finishedLoadingNode(nodeUid);
|
|
238
|
+
}
|
|
214
239
|
}
|
|
215
240
|
|
|
216
241
|
private async *loadNodes(
|
|
@@ -236,7 +261,14 @@ export class NodesAccess {
|
|
|
236
261
|
|
|
237
262
|
const { volumeId: ownVolumeId } = await this.shareService.getOwnVolumeIDs();
|
|
238
263
|
|
|
239
|
-
const
|
|
264
|
+
const apiNodesIterator = this.apiService.iterateNodes(nodeUids, ownVolumeId, filterOptions, signal);
|
|
265
|
+
|
|
266
|
+
const debouncedNodeMapper = async (encryptedNode: EncryptedNode): Promise<EncryptedNode> => {
|
|
267
|
+
this.debouncer.loadingNode(encryptedNode.uid);
|
|
268
|
+
return encryptedNode;
|
|
269
|
+
};
|
|
270
|
+
const encryptedNodesIterator = asyncIteratorMap(apiNodesIterator, debouncedNodeMapper, 1);
|
|
271
|
+
|
|
240
272
|
const decryptNodeMapper = async (encryptedNode: EncryptedNode): Promise<Result<DecryptedNode, unknown>> => {
|
|
241
273
|
returnedNodeUids.push(encryptedNode.uid);
|
|
242
274
|
try {
|
|
@@ -250,6 +282,7 @@ export class NodesAccess {
|
|
|
250
282
|
encryptedNodesIterator,
|
|
251
283
|
decryptNodeMapper,
|
|
252
284
|
DECRYPTION_CONCURRENCY,
|
|
285
|
+
signal,
|
|
253
286
|
);
|
|
254
287
|
for await (const node of decryptedNodesIterator) {
|
|
255
288
|
if (node.ok) {
|
|
@@ -329,11 +362,12 @@ export class NodesAccess {
|
|
|
329
362
|
this.logger.error(`Failed to cache node keys ${node.uid}`, error);
|
|
330
363
|
}
|
|
331
364
|
}
|
|
365
|
+
this.debouncer.finishedLoadingNode(node.uid);
|
|
332
366
|
return { node, keys };
|
|
333
367
|
}
|
|
334
368
|
|
|
335
369
|
async getParentKeys(
|
|
336
|
-
node: Pick<DecryptedNode, 'parentUid' | 'shareId'>,
|
|
370
|
+
node: Pick<DecryptedNode, 'uid' | 'parentUid' | 'shareId'>,
|
|
337
371
|
): Promise<Pick<DecryptedNodeKeys, 'key' | 'hashKey'>> {
|
|
338
372
|
if (node.parentUid) {
|
|
339
373
|
try {
|
|
@@ -360,6 +394,7 @@ export class NodesAccess {
|
|
|
360
394
|
|
|
361
395
|
async getNodeKeys(nodeUid: string): Promise<DecryptedNodeKeys> {
|
|
362
396
|
try {
|
|
397
|
+
await this.debouncer.waitForLoadingNode(nodeUid);
|
|
363
398
|
return await this.cryptoCache.getNodeKeys(nodeUid);
|
|
364
399
|
} catch {
|
|
365
400
|
const { keys } = await this.loadNode(nodeUid);
|
|
@@ -50,7 +50,7 @@ describe('NodesManagement', () => {
|
|
|
50
50
|
apiService = {
|
|
51
51
|
renameNode: jest.fn(),
|
|
52
52
|
moveNode: jest.fn(),
|
|
53
|
-
copyNode: jest.fn(),
|
|
53
|
+
copyNode: jest.fn().mockResolvedValue('newCopiedNodeUid'),
|
|
54
54
|
trashNodes: jest.fn(async function* (uids) {
|
|
55
55
|
yield* uids.map((uid) => ({ ok: true, uid }) as NodeResult);
|
|
56
56
|
}),
|
|
@@ -251,6 +251,7 @@ describe('NodesManagement', () => {
|
|
|
251
251
|
|
|
252
252
|
expect(newNode).toEqual({
|
|
253
253
|
...nodes.nodeUid,
|
|
254
|
+
uid: 'newCopiedNodeUid',
|
|
254
255
|
parentUid: 'newParentNodeUid',
|
|
255
256
|
encryptedName: 'copiedArmoredNodeName',
|
|
256
257
|
hash: 'copiedHash',
|
|
@@ -307,6 +308,7 @@ describe('NodesManagement', () => {
|
|
|
307
308
|
);
|
|
308
309
|
expect(newNode).toEqual({
|
|
309
310
|
...nodes.anonymousNodeUid,
|
|
311
|
+
uid: 'newCopiedNodeUid',
|
|
310
312
|
parentUid: 'newParentNodeUid',
|
|
311
313
|
encryptedName: 'copiedArmoredNodeName',
|
|
312
314
|
hash: 'copiedHash',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { c } from 'ttag';
|
|
2
2
|
|
|
3
|
-
import { MemberRole, NodeType, NodeResult, resultOk } from '../../interface';
|
|
3
|
+
import { MemberRole, NodeType, NodeResult, NodeResultWithNewUid, resultOk } from '../../interface';
|
|
4
4
|
import { AbortError, ValidationError } from '../../errors';
|
|
5
5
|
import { getErrorMessage } from '../errors';
|
|
6
6
|
import { splitNodeUid } from '../uids';
|
|
@@ -182,16 +182,21 @@ export class NodesManagement {
|
|
|
182
182
|
return newNode;
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
// Improvement requested: copy nodes in parallel
|
|
186
|
-
async *copyNodes(
|
|
185
|
+
// Improvement requested: copy nodes in parallel using copy_multiple endpoint
|
|
186
|
+
async *copyNodes(
|
|
187
|
+
nodeUids: string[],
|
|
188
|
+
newParentNodeUid: string,
|
|
189
|
+
signal?: AbortSignal,
|
|
190
|
+
): AsyncGenerator<NodeResultWithNewUid> {
|
|
187
191
|
for (const nodeUid of nodeUids) {
|
|
188
192
|
if (signal?.aborted) {
|
|
189
193
|
throw new AbortError(c('Error').t`Copy operation aborted`);
|
|
190
194
|
}
|
|
191
195
|
try {
|
|
192
|
-
await this.copyNode(nodeUid, newParentNodeUid);
|
|
196
|
+
const { uid: newNodeUid } = await this.copyNode(nodeUid, newParentNodeUid);
|
|
193
197
|
yield {
|
|
194
198
|
uid: nodeUid,
|
|
199
|
+
newUid: newNodeUid,
|
|
195
200
|
ok: true,
|
|
196
201
|
};
|
|
197
202
|
} catch (error: unknown) {
|
|
@@ -237,7 +242,7 @@ export class NodesManagement {
|
|
|
237
242
|
signatureEmail: encryptedCrypto.signatureEmail,
|
|
238
243
|
armoredNodePassphraseSignature: encryptedCrypto.armoredNodePassphraseSignature,
|
|
239
244
|
};
|
|
240
|
-
await this.apiService.copyNode(nodeUid, {
|
|
245
|
+
const newNodeUid = await this.apiService.copyNode(nodeUid, {
|
|
241
246
|
...keySignatureProperties,
|
|
242
247
|
parentUid: newParentUid,
|
|
243
248
|
armoredNodePassphrase: encryptedCrypto.armoredNodePassphrase,
|
|
@@ -247,6 +252,7 @@ export class NodesManagement {
|
|
|
247
252
|
});
|
|
248
253
|
const newNode: DecryptedNode = {
|
|
249
254
|
...node,
|
|
255
|
+
uid: newNodeUid,
|
|
250
256
|
encryptedName: encryptedCrypto.encryptedName,
|
|
251
257
|
parentUid: newParentUid,
|
|
252
258
|
hash: encryptedCrypto.hash,
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { DriveAPIService, drivePaths, NotFoundAPIError } from '../apiService';
|
|
1
|
+
import { DriveAPIService, drivePaths } from '../apiService';
|
|
4
2
|
import { EncryptedRootShare, EncryptedShareCrypto, ShareType } from '../shares/interface';
|
|
5
3
|
import { makeNodeUid } from '../uids';
|
|
6
4
|
|
|
7
|
-
type
|
|
8
|
-
|
|
9
|
-
type GetShareResponse = drivePaths['/drive/shares/{shareID}']['get']['responses']['200']['content']['application/json'];
|
|
5
|
+
type GetPhotoShareResponse =
|
|
6
|
+
drivePaths['/drive/v2/shares/photos']['get']['responses']['200']['content']['application/json'];
|
|
10
7
|
|
|
11
8
|
type PostCreateVolumeRequest = Extract<
|
|
12
9
|
drivePaths['/drive/photos/volumes']['post']['requestBody'],
|
|
@@ -34,33 +31,19 @@ export class PhotosAPIService {
|
|
|
34
31
|
}
|
|
35
32
|
|
|
36
33
|
async getPhotoShare(): Promise<EncryptedRootShare> {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const volumesResponse = await this.apiService.get<GetVolumesResponse>('drive/volumes');
|
|
40
|
-
|
|
41
|
-
const photoVolume = volumesResponse.Volumes.find((volume) => volume.Type === 2);
|
|
42
|
-
|
|
43
|
-
if (!photoVolume) {
|
|
44
|
-
throw new NotFoundAPIError(c('Error').t`Photo volume not found`);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const response = await this.apiService.get<GetShareResponse>(`drive/shares/${photoVolume.Share.ShareID}`);
|
|
48
|
-
|
|
49
|
-
if (!response.AddressID) {
|
|
50
|
-
throw new Error('Photo root share has not address ID set');
|
|
51
|
-
}
|
|
34
|
+
const response = await this.apiService.get<GetPhotoShareResponse>('drive/v2/shares/photos');
|
|
52
35
|
|
|
53
36
|
return {
|
|
54
|
-
volumeId: response.VolumeID,
|
|
55
|
-
shareId: response.ShareID,
|
|
56
|
-
rootNodeId: response.LinkID,
|
|
57
|
-
creatorEmail: response.
|
|
37
|
+
volumeId: response.Volume.VolumeID,
|
|
38
|
+
shareId: response.Share.ShareID,
|
|
39
|
+
rootNodeId: response.Link.Link.LinkID,
|
|
40
|
+
creatorEmail: response.Share.CreatorEmail,
|
|
58
41
|
encryptedCrypto: {
|
|
59
|
-
armoredKey: response.Key,
|
|
60
|
-
armoredPassphrase: response.Passphrase,
|
|
61
|
-
armoredPassphraseSignature: response.PassphraseSignature,
|
|
42
|
+
armoredKey: response.Share.Key,
|
|
43
|
+
armoredPassphrase: response.Share.Passphrase,
|
|
44
|
+
armoredPassphraseSignature: response.Share.PassphraseSignature,
|
|
62
45
|
},
|
|
63
|
-
addressId: response.AddressID,
|
|
46
|
+
addressId: response.Share.AddressID,
|
|
64
47
|
type: ShareType.Photo,
|
|
65
48
|
};
|
|
66
49
|
}
|
|
@@ -5,6 +5,7 @@ import { generateFileExtendedAttributes } from '../nodes';
|
|
|
5
5
|
import { splitNodeRevisionUid } from '../uids';
|
|
6
6
|
import { UploadAPIService } from '../upload/apiService';
|
|
7
7
|
import { BlockVerifier } from '../upload/blockVerifier';
|
|
8
|
+
import { UploadController } from '../upload/controller';
|
|
8
9
|
import { UploadCryptoService } from '../upload/cryptoService';
|
|
9
10
|
import { FileUploader } from '../upload/fileUploader';
|
|
10
11
|
import { NodeRevisionDraft, NodesService } from '../upload/interface';
|
|
@@ -62,6 +63,7 @@ export class PhotoFileUploader extends FileUploader {
|
|
|
62
63
|
revisionDraft,
|
|
63
64
|
this.photoMetadata,
|
|
64
65
|
onFinish,
|
|
66
|
+
this.controller,
|
|
65
67
|
this.signal,
|
|
66
68
|
);
|
|
67
69
|
}
|
|
@@ -80,9 +82,28 @@ export class PhotoStreamUploader extends StreamUploader {
|
|
|
80
82
|
revisionDraft: NodeRevisionDraft,
|
|
81
83
|
metadata: PhotoUploadMetadata,
|
|
82
84
|
onFinish: (failure: boolean) => Promise<void>,
|
|
85
|
+
controller: UploadController,
|
|
83
86
|
signal?: AbortSignal,
|
|
84
87
|
) {
|
|
85
|
-
|
|
88
|
+
const abortController = new AbortController();
|
|
89
|
+
if (signal) {
|
|
90
|
+
signal.addEventListener('abort', () => {
|
|
91
|
+
abortController.abort();
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
super(
|
|
96
|
+
telemetry,
|
|
97
|
+
apiService,
|
|
98
|
+
cryptoService,
|
|
99
|
+
uploadManager,
|
|
100
|
+
blockVerifier,
|
|
101
|
+
revisionDraft,
|
|
102
|
+
metadata,
|
|
103
|
+
onFinish,
|
|
104
|
+
controller,
|
|
105
|
+
abortController,
|
|
106
|
+
);
|
|
86
107
|
this.photoUploadManager = uploadManager;
|
|
87
108
|
this.photoMetadata = metadata;
|
|
88
109
|
}
|
|
@@ -347,8 +347,8 @@ export class SharingAPIService {
|
|
|
347
347
|
return response.Share.ID;
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
-
async deleteShare(shareId: string): Promise<void> {
|
|
351
|
-
await this.apiService.delete(`drive/shares/${shareId}?Force
|
|
350
|
+
async deleteShare(shareId: string, force: boolean = false): Promise<void> {
|
|
351
|
+
await this.apiService.delete(`drive/shares/${shareId}?Force=${force ? 1 : 0}`);
|
|
352
352
|
}
|
|
353
353
|
|
|
354
354
|
async inviteProtonUser(
|
|
@@ -286,7 +286,7 @@ export class SharingManagement {
|
|
|
286
286
|
|
|
287
287
|
if (!settings) {
|
|
288
288
|
this.logger.info(`Unsharing node ${nodeUid}`);
|
|
289
|
-
await this.
|
|
289
|
+
await this.deleteShareWithForce(currentSharing.share.shareId, nodeUid);
|
|
290
290
|
return;
|
|
291
291
|
}
|
|
292
292
|
|
|
@@ -348,7 +348,7 @@ export class SharingManagement {
|
|
|
348
348
|
// update local state immediately.
|
|
349
349
|
this.logger.info(`Deleting share ${currentSharing.share.shareId} for node ${nodeUid}`);
|
|
350
350
|
try {
|
|
351
|
-
await this.
|
|
351
|
+
await this.deleteShareWithForce(currentSharing.share.shareId, nodeUid);
|
|
352
352
|
} catch (error: unknown) {
|
|
353
353
|
// If deleting the share fails, we don't want to throw an error
|
|
354
354
|
// as it might be a race condition that other client updated
|
|
@@ -433,8 +433,11 @@ export class SharingManagement {
|
|
|
433
433
|
};
|
|
434
434
|
}
|
|
435
435
|
|
|
436
|
-
|
|
437
|
-
|
|
436
|
+
/**
|
|
437
|
+
* Deletes the share even if it is not empty.
|
|
438
|
+
*/
|
|
439
|
+
private async deleteShareWithForce(shareId: string, nodeUid: string): Promise<void> {
|
|
440
|
+
await this.apiService.deleteShare(shareId, true);
|
|
438
441
|
await this.nodesService.notifyNodeChanged(nodeUid);
|
|
439
442
|
if (await this.cache.hasSharedByMeNodeUidsLoaded()) {
|
|
440
443
|
await this.cache.removeSharedByMeNodeUid(nodeUid);
|
|
@@ -1,175 +1,38 @@
|
|
|
1
|
-
import { DriveAPIService, drivePaths
|
|
2
|
-
import { Logger, MemberRole } from '../../interface';
|
|
3
|
-
import { makeNodeUid, splitNodeUid } from '../uids';
|
|
4
|
-
import { EncryptedShareCrypto, EncryptedNode } from './interface';
|
|
1
|
+
import { DriveAPIService, drivePaths } from '../apiService';
|
|
5
2
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
type
|
|
11
|
-
drivePaths['/drive/urls/{token}/
|
|
3
|
+
type PostTokenInfoRequest = Extract<
|
|
4
|
+
drivePaths['/drive/v2/urls/{token}/bookmark']['post']['requestBody'],
|
|
5
|
+
{ content: object }
|
|
6
|
+
>['content']['application/json'];
|
|
7
|
+
type PostTokenInfoResponse =
|
|
8
|
+
drivePaths['/drive/v2/urls/{token}/bookmark']['post']['responses']['200']['content']['application/json'];
|
|
12
9
|
|
|
13
10
|
/**
|
|
14
|
-
* Provides API communication for
|
|
11
|
+
* Provides API communication for actions on the public link.
|
|
15
12
|
*
|
|
16
13
|
* The service is responsible for transforming local objects to API payloads
|
|
17
14
|
* and vice versa. It should not contain any business logic.
|
|
18
15
|
*/
|
|
19
16
|
export class SharingPublicAPIService {
|
|
20
|
-
constructor(
|
|
21
|
-
private logger: Logger,
|
|
22
|
-
private apiService: DriveAPIService,
|
|
23
|
-
) {
|
|
24
|
-
this.logger = logger;
|
|
17
|
+
constructor(private apiService: DriveAPIService) {
|
|
25
18
|
this.apiService = apiService;
|
|
26
19
|
}
|
|
27
20
|
|
|
28
|
-
async
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
},
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async *iterateFolderChildren(parentUid: string, signal?: AbortSignal): AsyncGenerator<EncryptedNode> {
|
|
46
|
-
const { volumeId: token, nodeId } = splitNodeUid(parentUid);
|
|
47
|
-
|
|
48
|
-
let page = 0;
|
|
49
|
-
while (true) {
|
|
50
|
-
const response = await this.apiService.get<GetTokenFolderChildrenResponse>(
|
|
51
|
-
`drive/urls/${token}/folders/${nodeId}/children?Page=${page}&PageSize=${PAGE_SIZE}`,
|
|
52
|
-
signal,
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
for (const link of response.Links) {
|
|
56
|
-
yield linkToEncryptedNode(this.logger, token, link);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (response.Links.length < PAGE_SIZE) {
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
page++;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function tokenToEncryptedNode(logger: Logger, token: GetTokenInfoResponse['Token']): EncryptedNode {
|
|
68
|
-
const baseNodeMetadata = {
|
|
69
|
-
// Internal metadata
|
|
70
|
-
encryptedName: token.Name,
|
|
71
|
-
|
|
72
|
-
// Basic node metadata
|
|
73
|
-
uid: makeNodeUid(token.Token, token.LinkID),
|
|
74
|
-
parentUid: undefined,
|
|
75
|
-
type: nodeTypeNumberToNodeType(logger, token.LinkType),
|
|
76
|
-
creationTime: new Date(), // TODO
|
|
77
|
-
|
|
78
|
-
isShared: false,
|
|
79
|
-
isSharedPublicly: false,
|
|
80
|
-
directRole: MemberRole.Viewer, // TODO
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const baseCryptoNodeMetadata = {
|
|
84
|
-
signatureEmail: token.SignatureEmail || undefined,
|
|
85
|
-
armoredKey: token.NodeKey,
|
|
86
|
-
armoredNodePassphrase: token.NodePassphrase,
|
|
87
|
-
armoredNodePassphraseSignature: token.NodePassphraseSignature || undefined,
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
if (token.LinkType === 1 && token.NodeHashKey) {
|
|
91
|
-
return {
|
|
92
|
-
...baseNodeMetadata,
|
|
93
|
-
encryptedCrypto: {
|
|
94
|
-
...baseCryptoNodeMetadata,
|
|
95
|
-
folder: {
|
|
96
|
-
armoredHashKey: token.NodeHashKey as string,
|
|
21
|
+
async bookmarkPublicLink(bookmark: {
|
|
22
|
+
token: string;
|
|
23
|
+
encryptedUrlPassword: string;
|
|
24
|
+
addressId: string;
|
|
25
|
+
addressKeyId: string;
|
|
26
|
+
}): Promise<void> {
|
|
27
|
+
await this.apiService.post<PostTokenInfoRequest, PostTokenInfoResponse>(
|
|
28
|
+
`drive/v2/urls/${bookmark.token}/bookmark`,
|
|
29
|
+
{
|
|
30
|
+
BookmarkShareURL: {
|
|
31
|
+
EncryptedUrlPassword: bookmark.encryptedUrlPassword,
|
|
32
|
+
AddressID: bookmark.addressId,
|
|
33
|
+
AddressKeyID: bookmark.addressKeyId,
|
|
97
34
|
},
|
|
98
35
|
},
|
|
99
|
-
|
|
36
|
+
);
|
|
100
37
|
}
|
|
101
|
-
|
|
102
|
-
if (token.LinkType === 2 && token.ContentKeyPacket) {
|
|
103
|
-
return {
|
|
104
|
-
...baseNodeMetadata,
|
|
105
|
-
totalStorageSize: token.Size || undefined,
|
|
106
|
-
mediaType: token.MIMEType || undefined,
|
|
107
|
-
encryptedCrypto: {
|
|
108
|
-
...baseCryptoNodeMetadata,
|
|
109
|
-
file: {
|
|
110
|
-
base64ContentKeyPacket: token.ContentKeyPacket,
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
throw new Error(`Unknown node type: ${token.LinkType}`);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function linkToEncryptedNode(
|
|
120
|
-
logger: Logger,
|
|
121
|
-
token: string,
|
|
122
|
-
link: GetTokenFolderChildrenResponse['Links'][0],
|
|
123
|
-
): EncryptedNode {
|
|
124
|
-
const baseNodeMetadata = {
|
|
125
|
-
// Internal metadata
|
|
126
|
-
hash: link.Hash || undefined,
|
|
127
|
-
encryptedName: link.Name,
|
|
128
|
-
|
|
129
|
-
// Basic node metadata
|
|
130
|
-
uid: makeNodeUid(token, link.LinkID),
|
|
131
|
-
parentUid: link.ParentLinkID ? makeNodeUid(token, link.ParentLinkID) : undefined,
|
|
132
|
-
type: nodeTypeNumberToNodeType(logger, link.Type),
|
|
133
|
-
creationTime: new Date(), // TODO
|
|
134
|
-
totalStorageSize: link.TotalSize,
|
|
135
|
-
|
|
136
|
-
isShared: false,
|
|
137
|
-
isSharedPublicly: false,
|
|
138
|
-
directRole: MemberRole.Viewer, // TODO
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
const baseCryptoNodeMetadata = {
|
|
142
|
-
signatureEmail: link.SignatureEmail || undefined,
|
|
143
|
-
armoredKey: link.NodeKey,
|
|
144
|
-
armoredNodePassphrase: link.NodePassphrase,
|
|
145
|
-
armoredNodePassphraseSignature: link.NodePassphraseSignature || undefined,
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
if (link.Type === 1 && link.FolderProperties) {
|
|
149
|
-
return {
|
|
150
|
-
...baseNodeMetadata,
|
|
151
|
-
encryptedCrypto: {
|
|
152
|
-
...baseCryptoNodeMetadata,
|
|
153
|
-
folder: {
|
|
154
|
-
armoredHashKey: link.FolderProperties.NodeHashKey as string,
|
|
155
|
-
},
|
|
156
|
-
},
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (link.Type === 2 && link.FileProperties?.ContentKeyPacket) {
|
|
161
|
-
return {
|
|
162
|
-
...baseNodeMetadata,
|
|
163
|
-
totalStorageSize: link.FileProperties.ActiveRevision?.Size || undefined,
|
|
164
|
-
mediaType: link.MIMEType || undefined,
|
|
165
|
-
encryptedCrypto: {
|
|
166
|
-
...baseCryptoNodeMetadata,
|
|
167
|
-
file: {
|
|
168
|
-
base64ContentKeyPacket: link.FileProperties.ContentKeyPacket,
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
throw new Error(`Unknown node type: ${link.Type}`);
|
|
175
38
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { c } from 'ttag';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { VERIFICATION_STATUS } from '../../crypto';
|
|
4
4
|
import { getVerificationMessage } from '../errors';
|
|
5
5
|
import {
|
|
6
6
|
resultOk,
|
|
@@ -12,34 +12,9 @@ import {
|
|
|
12
12
|
MetricVolumeType,
|
|
13
13
|
MetricsDecryptionErrorField,
|
|
14
14
|
Logger,
|
|
15
|
-
ProtonDriveAccount,
|
|
16
15
|
} from '../../interface';
|
|
17
|
-
import { NodesCryptoService } from '../nodes/cryptoService';
|
|
18
|
-
import { EncryptedShareCrypto } from './interface';
|
|
19
16
|
|
|
20
|
-
export class
|
|
21
|
-
constructor(
|
|
22
|
-
telemetry: ProtonDriveTelemetry,
|
|
23
|
-
driveCrypto: DriveCrypto,
|
|
24
|
-
account: ProtonDriveAccount,
|
|
25
|
-
private password: string,
|
|
26
|
-
) {
|
|
27
|
-
super(telemetry, driveCrypto, account, new SharingPublicCryptoReporter(telemetry));
|
|
28
|
-
this.password = password;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async decryptPublicLinkShareKey(encryptedShare: EncryptedShareCrypto): Promise<PrivateKey> {
|
|
32
|
-
const { key: shareKey } = await this.driveCrypto.decryptKeyWithSrpPassword(
|
|
33
|
-
this.password,
|
|
34
|
-
encryptedShare.base64UrlPasswordSalt,
|
|
35
|
-
encryptedShare.armoredKey,
|
|
36
|
-
encryptedShare.armoredPassphrase,
|
|
37
|
-
);
|
|
38
|
-
return shareKey;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
class SharingPublicCryptoReporter {
|
|
17
|
+
export class SharingPublicCryptoReporter {
|
|
43
18
|
private logger: Logger;
|
|
44
19
|
private telemetry: ProtonDriveTelemetry;
|
|
45
20
|
|