@protontech/drive-sdk 0.5.0 → 0.6.0
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 +79 -7
- package/dist/diagnostic/sdkDiagnostic.js.map +1 -1
- package/dist/interface/index.d.ts +2 -2
- 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 +1 -12
- package/dist/internal/apiService/driveTypes.d.ts +2571 -2356
- 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.js +6 -1
- package/dist/internal/download/fileDownloader.js.map +1 -1
- package/dist/internal/nodes/apiService.d.ts +11 -1
- package/dist/internal/nodes/apiService.js +47 -13
- package/dist/internal/nodes/apiService.js.map +1 -1
- package/dist/internal/nodes/apiService.test.js +61 -3
- package/dist/internal/nodes/apiService.test.js.map +1 -1
- package/dist/internal/nodes/cryptoService.d.ts +4 -0
- package/dist/internal/nodes/cryptoService.js +6 -0
- package/dist/internal/nodes/cryptoService.js.map +1 -1
- package/dist/internal/nodes/debouncer.d.ts +3 -2
- package/dist/internal/nodes/debouncer.js +16 -4
- package/dist/internal/nodes/debouncer.js.map +1 -1
- package/dist/internal/nodes/debouncer.test.js +20 -12
- package/dist/internal/nodes/debouncer.test.js.map +1 -1
- package/dist/internal/nodes/extendedAttributes.js +2 -2
- package/dist/internal/nodes/extendedAttributes.js.map +1 -1
- package/dist/internal/nodes/index.d.ts +1 -1
- package/dist/internal/nodes/index.js +3 -3
- package/dist/internal/nodes/index.js.map +1 -1
- package/dist/internal/nodes/index.test.js +1 -1
- package/dist/internal/nodes/index.test.js.map +1 -1
- package/dist/internal/nodes/nodeName.d.ts +8 -0
- package/dist/internal/nodes/nodeName.js +30 -0
- package/dist/internal/nodes/nodeName.js.map +1 -0
- package/dist/internal/nodes/nodeName.test.d.ts +1 -0
- package/dist/internal/nodes/nodeName.test.js +50 -0
- package/dist/internal/nodes/nodeName.test.js.map +1 -0
- package/dist/internal/nodes/nodesAccess.d.ts +5 -4
- package/dist/internal/nodes/nodesAccess.js +6 -5
- package/dist/internal/nodes/nodesAccess.js.map +1 -1
- package/dist/internal/nodes/nodesAccess.test.js +17 -5
- package/dist/internal/nodes/nodesAccess.test.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.d.ts +3 -2
- package/dist/internal/nodes/nodesManagement.js +35 -4
- package/dist/internal/nodes/nodesManagement.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.test.js +64 -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/index.d.ts +2 -0
- package/dist/internal/photos/index.js +4 -0
- package/dist/internal/photos/index.js.map +1 -1
- package/dist/internal/photos/upload.js +7 -1
- package/dist/internal/photos/upload.js.map +1 -1
- package/dist/internal/shares/index.d.ts +1 -0
- package/dist/internal/shares/index.js +3 -0
- package/dist/internal/shares/index.js.map +1 -1
- package/dist/internal/shares/interface.d.ts +8 -0
- package/dist/internal/shares/interface.js +10 -1
- package/dist/internal/shares/interface.js.map +1 -1
- package/dist/internal/sharing/apiService.d.ts +4 -2
- package/dist/internal/sharing/apiService.js +18 -14
- package/dist/internal/sharing/apiService.js.map +1 -1
- package/dist/internal/sharing/index.d.ts +2 -1
- package/dist/internal/sharing/index.js +6 -2
- package/dist/internal/sharing/index.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 -63
- package/dist/internal/sharingPublic/apiService.js.map +1 -1
- package/dist/internal/sharingPublic/index.d.ts +3 -3
- package/dist/internal/sharingPublic/index.js +7 -12
- package/dist/internal/sharingPublic/index.js.map +1 -1
- package/dist/internal/sharingPublic/nodes.d.ts +13 -8
- package/dist/internal/sharingPublic/nodes.js +20 -2
- package/dist/internal/sharingPublic/nodes.js.map +1 -1
- 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 +5 -2
- package/dist/internal/sharingPublic/session/session.js +7 -3
- package/dist/internal/sharingPublic/session/session.js.map +1 -1
- package/dist/internal/sharingPublic/shares.d.ts +3 -10
- package/dist/internal/sharingPublic/shares.js +10 -33
- package/dist/internal/sharingPublic/shares.js.map +1 -1
- package/dist/internal/upload/apiService.d.ts +0 -9
- package/dist/internal/upload/apiService.js +0 -16
- package/dist/internal/upload/apiService.js.map +1 -1
- package/dist/internal/upload/controller.d.ts +3 -1
- package/dist/internal/upload/controller.js +16 -2
- package/dist/internal/upload/controller.js.map +1 -1
- package/dist/internal/upload/cryptoService.d.ts +0 -4
- package/dist/internal/upload/cryptoService.js +0 -6
- package/dist/internal/upload/cryptoService.js.map +1 -1
- package/dist/internal/upload/fileUploader.d.ts +0 -1
- package/dist/internal/upload/fileUploader.js +2 -6
- package/dist/internal/upload/fileUploader.js.map +1 -1
- package/dist/internal/upload/manager.d.ts +0 -1
- package/dist/internal/upload/manager.js +0 -51
- package/dist/internal/upload/manager.js.map +1 -1
- package/dist/internal/upload/manager.test.js +0 -61
- package/dist/internal/upload/manager.test.js.map +1 -1
- package/dist/internal/upload/streamUploader.d.ts +4 -3
- package/dist/internal/upload/streamUploader.js +61 -18
- package/dist/internal/upload/streamUploader.js.map +1 -1
- package/dist/internal/upload/streamUploader.test.js +38 -12
- package/dist/internal/upload/streamUploader.test.js.map +1 -1
- package/dist/protonDriveClient.d.ts +24 -4
- package/dist/protonDriveClient.js +26 -7
- package/dist/protonDriveClient.js.map +1 -1
- package/dist/protonDrivePhotosClient.d.ts +100 -4
- package/dist/protonDrivePhotosClient.js +160 -9
- package/dist/protonDrivePhotosClient.js.map +1 -1
- package/dist/protonDrivePublicLinkClient.d.ts +4 -3
- package/dist/protonDrivePublicLinkClient.js +2 -2
- 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 +110 -9
- package/src/interface/index.ts +2 -1
- package/src/interface/nodes.ts +3 -0
- package/src/interface/telemetry.ts +5 -0
- package/src/interface/upload.ts +1 -13
- package/src/internal/apiService/driveTypes.ts +2698 -2529
- package/src/internal/download/controller.ts +13 -1
- package/src/internal/download/fileDownloader.ts +8 -1
- package/src/internal/nodes/apiService.test.ts +65 -1
- package/src/internal/nodes/apiService.ts +80 -17
- package/src/internal/nodes/cryptoService.ts +9 -0
- package/src/internal/nodes/debouncer.test.ts +25 -13
- package/src/internal/nodes/debouncer.ts +20 -4
- package/src/internal/nodes/extendedAttributes.ts +2 -2
- package/src/internal/nodes/index.test.ts +1 -0
- package/src/internal/nodes/index.ts +3 -9
- package/src/internal/nodes/nodeName.test.ts +57 -0
- package/src/internal/nodes/nodeName.ts +26 -0
- package/src/internal/nodes/nodesAccess.test.ts +17 -5
- package/src/internal/nodes/nodesAccess.ts +15 -5
- package/src/internal/nodes/nodesManagement.test.ts +68 -1
- package/src/internal/nodes/nodesManagement.ts +54 -6
- package/src/internal/photos/apiService.ts +12 -29
- package/src/internal/photos/index.ts +4 -0
- package/src/internal/photos/upload.ts +19 -1
- package/src/internal/shares/index.ts +1 -0
- package/src/internal/shares/interface.ts +9 -0
- package/src/internal/sharing/apiService.ts +17 -14
- package/src/internal/sharing/index.ts +7 -1
- package/src/internal/sharing/sharingManagement.ts +7 -4
- package/src/internal/sharingPublic/apiService.ts +23 -77
- package/src/internal/sharingPublic/index.ts +13 -11
- package/src/internal/sharingPublic/nodes.ts +33 -11
- package/src/internal/sharingPublic/session/apiService.ts +31 -9
- 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 +10 -5
- package/src/internal/sharingPublic/shares.ts +7 -43
- package/src/internal/upload/apiService.ts +0 -39
- package/src/internal/upload/controller.ts +16 -4
- package/src/internal/upload/cryptoService.ts +0 -9
- package/src/internal/upload/fileUploader.ts +2 -7
- package/src/internal/upload/manager.test.ts +0 -65
- package/src/internal/upload/manager.ts +0 -64
- package/src/internal/upload/streamUploader.test.ts +46 -14
- package/src/internal/upload/streamUploader.ts +74 -21
- package/src/protonDriveClient.ts +46 -9
- package/src/protonDrivePhotosClient.ts +193 -8
- package/src/protonDrivePublicLinkClient.ts +7 -4
- 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 -15
- package/dist/internal/sharingPublic/cryptoCache.js +0 -44
- package/dist/internal/sharingPublic/cryptoCache.js.map +0 -1
- package/dist/internal/sharingPublic/cryptoService.d.ts +0 -8
- package/dist/internal/sharingPublic/cryptoService.js +0 -19
- package/dist/internal/sharingPublic/cryptoService.js.map +0 -1
- package/dist/internal/sharingPublic/interface.d.ts +0 -5
- package/dist/internal/sharingPublic/interface.js +0 -3
- package/dist/internal/sharingPublic/interface.js.map +0 -1
- package/src/internal/sharingPublic/cryptoCache.ts +0 -45
- package/src/internal/sharingPublic/cryptoService.ts +0 -22
- package/src/internal/sharingPublic/interface.ts +0 -5
|
@@ -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
|
}),
|
|
@@ -61,6 +61,10 @@ describe('NodesManagement', () => {
|
|
|
61
61
|
yield* uids.map((uid) => ({ ok: true, uid }) as NodeResult);
|
|
62
62
|
}),
|
|
63
63
|
createFolder: jest.fn(),
|
|
64
|
+
checkAvailableHashes: jest.fn().mockResolvedValue({
|
|
65
|
+
availableHashes: ['name1Hash'],
|
|
66
|
+
pendingHashes: [],
|
|
67
|
+
}),
|
|
64
68
|
};
|
|
65
69
|
// @ts-expect-error No need to implement all methods for mocking
|
|
66
70
|
cryptoCache = {
|
|
@@ -75,6 +79,20 @@ describe('NodesManagement', () => {
|
|
|
75
79
|
}),
|
|
76
80
|
encryptNodeWithNewParent: jest.fn(),
|
|
77
81
|
createFolder: jest.fn(),
|
|
82
|
+
generateNameHashes: jest.fn().mockResolvedValue([
|
|
83
|
+
{
|
|
84
|
+
name: 'name1',
|
|
85
|
+
hash: 'name1Hash',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'name2',
|
|
89
|
+
hash: 'name2Hash',
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'name3',
|
|
93
|
+
hash: 'name3Hash',
|
|
94
|
+
},
|
|
95
|
+
]),
|
|
78
96
|
};
|
|
79
97
|
// @ts-expect-error No need to implement all methods for mocking
|
|
80
98
|
nodesAccess = {
|
|
@@ -251,6 +269,7 @@ describe('NodesManagement', () => {
|
|
|
251
269
|
|
|
252
270
|
expect(newNode).toEqual({
|
|
253
271
|
...nodes.nodeUid,
|
|
272
|
+
uid: 'newCopiedNodeUid',
|
|
254
273
|
parentUid: 'newParentNodeUid',
|
|
255
274
|
encryptedName: 'copiedArmoredNodeName',
|
|
256
275
|
hash: 'copiedHash',
|
|
@@ -307,6 +326,7 @@ describe('NodesManagement', () => {
|
|
|
307
326
|
);
|
|
308
327
|
expect(newNode).toEqual({
|
|
309
328
|
...nodes.anonymousNodeUid,
|
|
329
|
+
uid: 'newCopiedNodeUid',
|
|
310
330
|
parentUid: 'newParentNodeUid',
|
|
311
331
|
encryptedName: 'copiedArmoredNodeName',
|
|
312
332
|
hash: 'copiedHash',
|
|
@@ -338,4 +358,51 @@ describe('NodesManagement', () => {
|
|
|
338
358
|
expect(restored).toEqual(new Set(uids));
|
|
339
359
|
expect(nodesAccess.notifyNodeChanged).toHaveBeenCalledTimes(2);
|
|
340
360
|
});
|
|
361
|
+
|
|
362
|
+
describe('findAvailableName', () => {
|
|
363
|
+
it('should find available name', async () => {
|
|
364
|
+
apiService.checkAvailableHashes = jest.fn().mockImplementation(() => {
|
|
365
|
+
return {
|
|
366
|
+
availableHashes: ['name3Hash'],
|
|
367
|
+
pendingHashes: [],
|
|
368
|
+
};
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
const result = await management.findAvailableName('parentUid', 'name');
|
|
372
|
+
expect(result).toBe('name3');
|
|
373
|
+
expect(apiService.checkAvailableHashes).toHaveBeenCalledTimes(1);
|
|
374
|
+
expect(apiService.checkAvailableHashes).toHaveBeenCalledWith('parentUid', [
|
|
375
|
+
'name1Hash',
|
|
376
|
+
'name2Hash',
|
|
377
|
+
'name3Hash',
|
|
378
|
+
]);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it('should find available name with multiple pages', async () => {
|
|
382
|
+
let firstCall = false;
|
|
383
|
+
apiService.checkAvailableHashes = jest.fn().mockImplementation(() => {
|
|
384
|
+
if (!firstCall) {
|
|
385
|
+
firstCall = true;
|
|
386
|
+
return {
|
|
387
|
+
// First page has no available hashes
|
|
388
|
+
availableHashes: [],
|
|
389
|
+
pendingHashes: [],
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
return {
|
|
393
|
+
availableHashes: ['name3Hash'],
|
|
394
|
+
pendingHashes: [],
|
|
395
|
+
};
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
const result = await management.findAvailableName('parentUid', 'name');
|
|
399
|
+
expect(result).toBe('name3');
|
|
400
|
+
expect(apiService.checkAvailableHashes).toHaveBeenCalledTimes(2);
|
|
401
|
+
expect(apiService.checkAvailableHashes).toHaveBeenCalledWith('parentUid', [
|
|
402
|
+
'name1Hash',
|
|
403
|
+
'name2Hash',
|
|
404
|
+
'name3Hash',
|
|
405
|
+
]);
|
|
406
|
+
});
|
|
407
|
+
});
|
|
341
408
|
});
|
|
@@ -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';
|
|
@@ -8,10 +8,14 @@ import { NodeAPIService } from './apiService';
|
|
|
8
8
|
import { NodesCryptoCache } from './cryptoCache';
|
|
9
9
|
import { NodesCryptoService } from './cryptoService';
|
|
10
10
|
import { NodeOutOfSyncError } from './errors';
|
|
11
|
+
import { generateFolderExtendedAttributes } from './extendedAttributes';
|
|
11
12
|
import { DecryptedNode } from './interface';
|
|
13
|
+
import { splitExtension, joinNameAndExtension } from './nodeName';
|
|
12
14
|
import { NodesAccess } from './nodesAccess';
|
|
13
15
|
import { validateNodeName } from './validations';
|
|
14
|
-
|
|
16
|
+
|
|
17
|
+
const AVAILABLE_NAME_BATCH_SIZE = 10;
|
|
18
|
+
const AVAILABLE_NAME_LIMIT = 1000;
|
|
15
19
|
|
|
16
20
|
/**
|
|
17
21
|
* Provides high-level actions for managing nodes.
|
|
@@ -182,16 +186,21 @@ export class NodesManagement {
|
|
|
182
186
|
return newNode;
|
|
183
187
|
}
|
|
184
188
|
|
|
185
|
-
// Improvement requested: copy nodes in parallel
|
|
186
|
-
async *copyNodes(
|
|
189
|
+
// Improvement requested: copy nodes in parallel using copy_multiple endpoint
|
|
190
|
+
async *copyNodes(
|
|
191
|
+
nodeUids: string[],
|
|
192
|
+
newParentNodeUid: string,
|
|
193
|
+
signal?: AbortSignal,
|
|
194
|
+
): AsyncGenerator<NodeResultWithNewUid> {
|
|
187
195
|
for (const nodeUid of nodeUids) {
|
|
188
196
|
if (signal?.aborted) {
|
|
189
197
|
throw new AbortError(c('Error').t`Copy operation aborted`);
|
|
190
198
|
}
|
|
191
199
|
try {
|
|
192
|
-
await this.copyNode(nodeUid, newParentNodeUid);
|
|
200
|
+
const { uid: newNodeUid } = await this.copyNode(nodeUid, newParentNodeUid);
|
|
193
201
|
yield {
|
|
194
202
|
uid: nodeUid,
|
|
203
|
+
newUid: newNodeUid,
|
|
195
204
|
ok: true,
|
|
196
205
|
};
|
|
197
206
|
} catch (error: unknown) {
|
|
@@ -237,7 +246,7 @@ export class NodesManagement {
|
|
|
237
246
|
signatureEmail: encryptedCrypto.signatureEmail,
|
|
238
247
|
armoredNodePassphraseSignature: encryptedCrypto.armoredNodePassphraseSignature,
|
|
239
248
|
};
|
|
240
|
-
await this.apiService.copyNode(nodeUid, {
|
|
249
|
+
const newNodeUid = await this.apiService.copyNode(nodeUid, {
|
|
241
250
|
...keySignatureProperties,
|
|
242
251
|
parentUid: newParentUid,
|
|
243
252
|
armoredNodePassphrase: encryptedCrypto.armoredNodePassphrase,
|
|
@@ -247,6 +256,7 @@ export class NodesManagement {
|
|
|
247
256
|
});
|
|
248
257
|
const newNode: DecryptedNode = {
|
|
249
258
|
...node,
|
|
259
|
+
uid: newNodeUid,
|
|
250
260
|
encryptedName: encryptedCrypto.encryptedName,
|
|
251
261
|
parentUid: newParentUid,
|
|
252
262
|
hash: encryptedCrypto.hash,
|
|
@@ -343,4 +353,42 @@ export class NodesManagement {
|
|
|
343
353
|
await this.cryptoCache.setNodeKeys(nodeUid, keys);
|
|
344
354
|
return node;
|
|
345
355
|
}
|
|
356
|
+
|
|
357
|
+
async findAvailableName(parentFolderUid: string, name: string): Promise<string> {
|
|
358
|
+
const { hashKey: parentHashKey } = await this.nodesAccess.getNodeKeys(parentFolderUid);
|
|
359
|
+
if (!parentHashKey) {
|
|
360
|
+
throw new ValidationError(c('Error').t`Creating files in non-folders is not allowed`);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const [namePart, extension] = splitExtension(name);
|
|
364
|
+
|
|
365
|
+
let startIndex = 1;
|
|
366
|
+
while (startIndex < AVAILABLE_NAME_LIMIT) {
|
|
367
|
+
const namesToCheck = startIndex === 1 ? [name] : [];
|
|
368
|
+
for (let i = startIndex; i < startIndex + AVAILABLE_NAME_BATCH_SIZE; i++) {
|
|
369
|
+
namesToCheck.push(joinNameAndExtension(namePart, i, extension));
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const hashesToCheck = await this.cryptoService.generateNameHashes(parentHashKey, namesToCheck);
|
|
373
|
+
|
|
374
|
+
const { availableHashes } = await this.apiService.checkAvailableHashes(
|
|
375
|
+
parentFolderUid,
|
|
376
|
+
hashesToCheck.map(({ hash }) => hash),
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
if (!availableHashes.length) {
|
|
380
|
+
startIndex += AVAILABLE_NAME_BATCH_SIZE;
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const availableHash = hashesToCheck.find(({ hash }) => hash === availableHashes[0]);
|
|
385
|
+
if (!availableHash) {
|
|
386
|
+
throw Error('Backend returned unexpected hash');
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return availableHash.name;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
throw new ValidationError(c('Error').t`No available name found`);
|
|
393
|
+
}
|
|
346
394
|
}
|
|
@@ -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
|
}
|
|
@@ -24,6 +24,10 @@ import {
|
|
|
24
24
|
PhotoUploadManager,
|
|
25
25
|
PhotoUploadMetadata,
|
|
26
26
|
} from './upload';
|
|
27
|
+
import { ShareTargetType } from '../shares';
|
|
28
|
+
|
|
29
|
+
// Only photos and albums can be shared in photos volume.
|
|
30
|
+
export const PHOTOS_SHARE_TARGET_TYPES = [ShareTargetType.Photo, ShareTargetType.Album];
|
|
27
31
|
|
|
28
32
|
/**
|
|
29
33
|
* Provides facade for the whole photos module.
|
|
@@ -85,7 +85,25 @@ export class PhotoStreamUploader extends StreamUploader {
|
|
|
85
85
|
controller: UploadController,
|
|
86
86
|
signal?: AbortSignal,
|
|
87
87
|
) {
|
|
88
|
-
|
|
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
|
+
);
|
|
89
107
|
this.photoUploadManager = uploadManager;
|
|
90
108
|
this.photoMetadata = metadata;
|
|
91
109
|
}
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { PrivateKey, SessionKey } from '../../crypto';
|
|
2
2
|
import { Result, UnverifiedAuthorError } from '../../interface';
|
|
3
3
|
|
|
4
|
+
export enum ShareTargetType {
|
|
5
|
+
Root = 0,
|
|
6
|
+
Folder = 1,
|
|
7
|
+
File = 2,
|
|
8
|
+
Album = 3,
|
|
9
|
+
Photo = 4,
|
|
10
|
+
ProtonVendor = 5,
|
|
11
|
+
}
|
|
12
|
+
|
|
4
13
|
/**
|
|
5
14
|
* Internal interface providing basic identification of volume and its root
|
|
6
15
|
* share and node.
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
permissionsToMemberRole,
|
|
8
8
|
memberRoleToPermission,
|
|
9
9
|
} from '../apiService';
|
|
10
|
+
import { ShareTargetType } from '../shares';
|
|
10
11
|
import {
|
|
11
12
|
makeNodeUid,
|
|
12
13
|
splitNodeUid,
|
|
@@ -119,14 +120,6 @@ type PutShareUrlRequest = Extract<
|
|
|
119
120
|
type PutShareUrlResponse =
|
|
120
121
|
drivePaths['/drive/shares/{shareID}/urls/{urlID}']['put']['responses']['200']['content']['application/json'];
|
|
121
122
|
|
|
122
|
-
// We do not support photos and albums yet.
|
|
123
|
-
const SUPPORTED_SHARE_TARGET_TYPES = [
|
|
124
|
-
0, // Root
|
|
125
|
-
1, // Folder
|
|
126
|
-
2, // File
|
|
127
|
-
5, // Proton vendor (documents and sheets)
|
|
128
|
-
];
|
|
129
|
-
|
|
130
123
|
/**
|
|
131
124
|
* Provides API communication for fetching and managing sharing.
|
|
132
125
|
*
|
|
@@ -137,9 +130,11 @@ export class SharingAPIService {
|
|
|
137
130
|
constructor(
|
|
138
131
|
private logger: Logger,
|
|
139
132
|
private apiService: DriveAPIService,
|
|
133
|
+
private shareTargetTypes: ShareTargetType[],
|
|
140
134
|
) {
|
|
141
135
|
this.logger = logger;
|
|
142
136
|
this.apiService = apiService;
|
|
137
|
+
this.shareTargetTypes = shareTargetTypes;
|
|
143
138
|
}
|
|
144
139
|
|
|
145
140
|
async *iterateSharedNodeUids(volumeId: string, signal?: AbortSignal): AsyncGenerator<string> {
|
|
@@ -163,6 +158,7 @@ export class SharingAPIService {
|
|
|
163
158
|
async *iterateSharedWithMeNodeUids(signal?: AbortSignal): AsyncGenerator<string> {
|
|
164
159
|
let anchor = '';
|
|
165
160
|
while (true) {
|
|
161
|
+
// TODO: Use ShareTargetTypes filter when it is supported by the API.
|
|
166
162
|
const response = await this.apiService.get<GetSharedWithMeNodesResponse>(
|
|
167
163
|
`drive/v2/sharedwithme?${anchor ? `AnchorID=${anchor}` : ''}`,
|
|
168
164
|
signal,
|
|
@@ -170,8 +166,8 @@ export class SharingAPIService {
|
|
|
170
166
|
for (const link of response.Links) {
|
|
171
167
|
const nodeUid = makeNodeUid(link.VolumeID, link.LinkID);
|
|
172
168
|
|
|
173
|
-
if (!
|
|
174
|
-
this.logger.
|
|
169
|
+
if (!this.shareTargetTypes.includes(link.ShareTargetType)) {
|
|
170
|
+
this.logger.debug(`Unsupported share target type ${link.ShareTargetType} for node ${nodeUid}`);
|
|
175
171
|
continue;
|
|
176
172
|
}
|
|
177
173
|
|
|
@@ -188,14 +184,21 @@ export class SharingAPIService {
|
|
|
188
184
|
async *iterateInvitationUids(signal?: AbortSignal): AsyncGenerator<string> {
|
|
189
185
|
let anchor = '';
|
|
190
186
|
while (true) {
|
|
187
|
+
const params = new URLSearchParams();
|
|
188
|
+
this.shareTargetTypes.forEach((type) => {
|
|
189
|
+
params.append('ShareTargetTypes[]', type.toString());
|
|
190
|
+
});
|
|
191
|
+
if (anchor) {
|
|
192
|
+
params.append('AnchorID', anchor);
|
|
193
|
+
}
|
|
191
194
|
const response = await this.apiService.get<GetInvitationsResponse>(
|
|
192
|
-
`drive/v2/shares/invitations?${
|
|
195
|
+
`drive/v2/shares/invitations?${params.toString()}`,
|
|
193
196
|
signal,
|
|
194
197
|
);
|
|
195
198
|
for (const invitation of response.Invitations) {
|
|
196
199
|
const invitationUid = makeInvitationUid(invitation.ShareID, invitation.InvitationID);
|
|
197
200
|
|
|
198
|
-
if (!
|
|
201
|
+
if (!this.shareTargetTypes.includes(invitation.ShareTargetType)) {
|
|
199
202
|
this.logger.warn(
|
|
200
203
|
`Unsupported share target type ${invitation.ShareTargetType} for invitation ${invitationUid}`,
|
|
201
204
|
);
|
|
@@ -347,8 +350,8 @@ export class SharingAPIService {
|
|
|
347
350
|
return response.Share.ID;
|
|
348
351
|
}
|
|
349
352
|
|
|
350
|
-
async deleteShare(shareId: string): Promise<void> {
|
|
351
|
-
await this.apiService.delete(`drive/shares/${shareId}?Force
|
|
353
|
+
async deleteShare(shareId: string, force: boolean = false): Promise<void> {
|
|
354
|
+
await this.apiService.delete(`drive/shares/${shareId}?Force=${force ? 1 : 0}`);
|
|
352
355
|
}
|
|
353
356
|
|
|
354
357
|
async inviteProtonUser(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ProtonDriveAccount, ProtonDriveEntitiesCache, ProtonDriveTelemetry } from '../../interface';
|
|
2
2
|
import { DriveCrypto } from '../../crypto';
|
|
3
3
|
import { DriveAPIService } from '../apiService';
|
|
4
|
+
import { ShareTargetType } from '../shares';
|
|
4
5
|
import { SharingAPIService } from './apiService';
|
|
5
6
|
import { SharingCache } from './cache';
|
|
6
7
|
import { SharingCryptoService } from './cryptoService';
|
|
@@ -9,6 +10,10 @@ import { SharingManagement } from './sharingManagement';
|
|
|
9
10
|
import { SharesService, NodesService } from './interface';
|
|
10
11
|
import { SharingEventHandler } from './events';
|
|
11
12
|
|
|
13
|
+
// Root shares are not allowed to be shared.
|
|
14
|
+
// Photos and Albums are not supported in main volume (core Drive).
|
|
15
|
+
const DEFAULT_SHARE_TARGET_TYPES = [ShareTargetType.Folder, ShareTargetType.File, ShareTargetType.ProtonVendor];
|
|
16
|
+
|
|
12
17
|
/**
|
|
13
18
|
* Provides facade for the whole sharing module.
|
|
14
19
|
*
|
|
@@ -24,8 +29,9 @@ export function initSharingModule(
|
|
|
24
29
|
crypto: DriveCrypto,
|
|
25
30
|
sharesService: SharesService,
|
|
26
31
|
nodesService: NodesService,
|
|
32
|
+
shareTargetTypes: ShareTargetType[] = DEFAULT_SHARE_TARGET_TYPES,
|
|
27
33
|
) {
|
|
28
|
-
const api = new SharingAPIService(telemetry.getLogger('sharing-api'), apiService);
|
|
34
|
+
const api = new SharingAPIService(telemetry.getLogger('sharing-api'), apiService, shareTargetTypes);
|
|
29
35
|
const cache = new SharingCache(driveEntitiesCache);
|
|
30
36
|
const cryptoService = new SharingCryptoService(telemetry, crypto, account, sharesService);
|
|
31
37
|
const sharingAccess = new SharingAccess(api, cache, cryptoService, sharesService, nodesService);
|
|
@@ -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,92 +1,38 @@
|
|
|
1
|
-
import { DriveAPIService, drivePaths
|
|
2
|
-
import { Logger, MemberRole } from '../../interface';
|
|
3
|
-
import { makeNodeUid } from '../uids';
|
|
4
|
-
import { EncryptedNode } from '../nodes/interface';
|
|
5
|
-
import { EncryptedShareCrypto } from './interface';
|
|
1
|
+
import { DriveAPIService, drivePaths } from '../apiService';
|
|
6
2
|
|
|
7
|
-
type
|
|
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'];
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
|
-
* Provides API communication for
|
|
11
|
+
* Provides API communication for actions on the public link.
|
|
11
12
|
*
|
|
12
13
|
* The service is responsible for transforming local objects to API payloads
|
|
13
14
|
* and vice versa. It should not contain any business logic.
|
|
14
15
|
*/
|
|
15
16
|
export class SharingPublicAPIService {
|
|
16
|
-
constructor(
|
|
17
|
-
private logger: Logger,
|
|
18
|
-
private apiService: DriveAPIService,
|
|
19
|
-
) {
|
|
20
|
-
this.logger = logger;
|
|
17
|
+
constructor(private apiService: DriveAPIService) {
|
|
21
18
|
this.apiService = apiService;
|
|
22
19
|
}
|
|
23
20
|
|
|
24
|
-
async
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
},
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function tokenToEncryptedNode(logger: Logger, token: GetTokenInfoResponse['Token']): EncryptedNode {
|
|
43
|
-
const baseNodeMetadata = {
|
|
44
|
-
// Internal metadata
|
|
45
|
-
encryptedName: token.Name,
|
|
46
|
-
|
|
47
|
-
// Basic node metadata
|
|
48
|
-
uid: makeNodeUid(token.VolumeID, token.LinkID),
|
|
49
|
-
parentUid: undefined,
|
|
50
|
-
type: nodeTypeNumberToNodeType(logger, token.LinkType),
|
|
51
|
-
creationTime: new Date(), // TODO
|
|
52
|
-
|
|
53
|
-
isShared: false,
|
|
54
|
-
isSharedPublicly: false,
|
|
55
|
-
directRole: MemberRole.Viewer, // TODO
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const baseCryptoNodeMetadata = {
|
|
59
|
-
signatureEmail: token.SignatureEmail || undefined,
|
|
60
|
-
armoredKey: token.NodeKey,
|
|
61
|
-
armoredNodePassphrase: token.NodePassphrase,
|
|
62
|
-
armoredNodePassphraseSignature: token.NodePassphraseSignature || undefined,
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
if (token.LinkType === 1 && token.NodeHashKey) {
|
|
66
|
-
return {
|
|
67
|
-
...baseNodeMetadata,
|
|
68
|
-
encryptedCrypto: {
|
|
69
|
-
...baseCryptoNodeMetadata,
|
|
70
|
-
folder: {
|
|
71
|
-
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,
|
|
72
34
|
},
|
|
73
35
|
},
|
|
74
|
-
|
|
36
|
+
);
|
|
75
37
|
}
|
|
76
|
-
|
|
77
|
-
if (token.LinkType === 2 && token.ContentKeyPacket) {
|
|
78
|
-
return {
|
|
79
|
-
...baseNodeMetadata,
|
|
80
|
-
totalStorageSize: token.Size || undefined,
|
|
81
|
-
mediaType: token.MIMEType || undefined,
|
|
82
|
-
encryptedCrypto: {
|
|
83
|
-
...baseCryptoNodeMetadata,
|
|
84
|
-
file: {
|
|
85
|
-
base64ContentKeyPacket: token.ContentKeyPacket,
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
throw new Error(`Unknown node type: ${token.LinkType}`);
|
|
92
38
|
}
|