@protontech/drive-sdk 0.12.1 → 0.13.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/crypto/driveCrypto.d.ts +1 -0
- package/dist/crypto/driveCrypto.js +1 -0
- package/dist/crypto/driveCrypto.js.map +1 -1
- package/dist/interface/events.d.ts +1 -1
- package/dist/interface/featureFlags.d.ts +2 -1
- package/dist/interface/featureFlags.js +1 -0
- package/dist/interface/featureFlags.js.map +1 -1
- package/dist/interface/httpClient.d.ts +1 -0
- package/dist/interface/nodes.d.ts +7 -0
- package/dist/interface/nodes.js.map +1 -1
- package/dist/interface/telemetry.d.ts +4 -0
- package/dist/interface/telemetry.js.map +1 -1
- package/dist/internal/apiService/apiService.d.ts +1 -0
- package/dist/internal/apiService/apiService.js +16 -7
- package/dist/internal/apiService/apiService.js.map +1 -1
- package/dist/internal/apiService/apiService.test.js +24 -0
- package/dist/internal/apiService/apiService.test.js.map +1 -1
- package/dist/internal/apiService/driveTypes.d.ts +162 -101
- package/dist/internal/download/telemetry.js +4 -0
- package/dist/internal/download/telemetry.js.map +1 -1
- package/dist/internal/download/telemetry.test.js +5 -0
- package/dist/internal/download/telemetry.test.js.map +1 -1
- package/dist/internal/events/index.js +2 -2
- package/dist/internal/events/index.js.map +1 -1
- package/dist/internal/events/interface.d.ts +1 -1
- package/dist/internal/nodes/apiService.d.ts +4 -0
- package/dist/internal/nodes/apiService.js +4 -0
- package/dist/internal/nodes/apiService.js.map +1 -1
- package/dist/internal/nodes/apiService.test.js +8 -0
- package/dist/internal/nodes/apiService.test.js.map +1 -1
- package/dist/internal/nodes/cryptoService.js +3 -0
- package/dist/internal/nodes/cryptoService.js.map +1 -1
- package/dist/internal/nodes/extendedAttributes.d.ts +5 -5
- package/dist/internal/nodes/extendedAttributes.js +5 -14
- package/dist/internal/nodes/extendedAttributes.js.map +1 -1
- package/dist/internal/nodes/extendedAttributes.test.js +16 -22
- package/dist/internal/nodes/extendedAttributes.test.js.map +1 -1
- package/dist/internal/nodes/interface.d.ts +5 -0
- package/dist/internal/nodes/nodesManagement.d.ts +3 -3
- package/dist/internal/nodes/nodesManagement.js +7 -5
- package/dist/internal/nodes/nodesManagement.js.map +1 -1
- package/dist/internal/photos/albumsManager.js +1 -0
- package/dist/internal/photos/albumsManager.js.map +1 -1
- package/dist/internal/photos/nodes.d.ts +1 -1
- package/dist/internal/photos/nodes.js +2 -2
- package/dist/internal/photos/nodes.js.map +1 -1
- package/dist/internal/photos/upload.d.ts +5 -5
- package/dist/internal/photos/upload.js +8 -2
- package/dist/internal/photos/upload.js.map +1 -1
- package/dist/internal/sharing/apiService.js +1 -1
- package/dist/internal/sharing/apiService.js.map +1 -1
- package/dist/internal/sharingPublic/nodes.d.ts +1 -0
- package/dist/internal/upload/apiService.d.ts +45 -1
- package/dist/internal/upload/apiService.js +69 -1
- package/dist/internal/upload/apiService.js.map +1 -1
- package/dist/internal/upload/blockVerifier.d.ts +4 -1
- package/dist/internal/upload/blockVerifier.js +5 -0
- package/dist/internal/upload/blockVerifier.js.map +1 -1
- package/dist/internal/upload/cryptoService.d.ts +2 -2
- package/dist/internal/upload/cryptoService.js +1 -3
- package/dist/internal/upload/cryptoService.js.map +1 -1
- package/dist/internal/upload/fileUploader.d.ts +4 -3
- package/dist/internal/upload/fileUploader.js +17 -7
- package/dist/internal/upload/fileUploader.js.map +1 -1
- package/dist/internal/upload/index.d.ts +3 -3
- package/dist/internal/upload/index.js +17 -1
- package/dist/internal/upload/index.js.map +1 -1
- package/dist/internal/upload/index.test.d.ts +1 -0
- package/dist/internal/upload/index.test.js +71 -0
- package/dist/internal/upload/index.test.js.map +1 -0
- package/dist/internal/upload/interface.d.ts +2 -0
- package/dist/internal/upload/manager.d.ts +41 -2
- package/dist/internal/upload/manager.js +126 -44
- package/dist/internal/upload/manager.js.map +1 -1
- package/dist/internal/upload/manager.test.js +268 -1
- package/dist/internal/upload/manager.test.js.map +1 -1
- package/dist/internal/upload/smallFileUploader.d.ts +83 -0
- package/dist/internal/upload/smallFileUploader.js +197 -0
- package/dist/internal/upload/smallFileUploader.js.map +1 -0
- package/dist/internal/upload/smallFileUploader.test.d.ts +1 -0
- package/dist/internal/upload/smallFileUploader.test.js +358 -0
- package/dist/internal/upload/smallFileUploader.test.js.map +1 -0
- package/dist/internal/upload/streamReader.d.ts +4 -0
- package/dist/internal/upload/streamReader.js +37 -0
- package/dist/internal/upload/streamReader.js.map +1 -0
- package/dist/internal/upload/streamReader.test.d.ts +1 -0
- package/dist/internal/upload/streamReader.test.js +90 -0
- package/dist/internal/upload/streamReader.test.js.map +1 -0
- package/dist/internal/upload/streamUploader.d.ts +6 -0
- package/dist/internal/upload/streamUploader.js +3 -3
- package/dist/internal/upload/streamUploader.js.map +1 -1
- package/dist/internal/upload/telemetry.d.ts +3 -2
- package/dist/internal/upload/telemetry.js +5 -0
- package/dist/internal/upload/telemetry.js.map +1 -1
- package/dist/internal/upload/telemetry.test.js +6 -0
- package/dist/internal/upload/telemetry.test.js.map +1 -1
- package/dist/protonDrivePhotosClient.d.ts +1 -1
- package/dist/protonDrivePublicLinkClient.js +3 -1
- package/dist/protonDrivePublicLinkClient.js.map +1 -1
- package/dist/telemetry.d.ts +1 -0
- package/dist/telemetry.js +21 -0
- package/dist/telemetry.js.map +1 -1
- package/dist/telemetry.test.d.ts +1 -0
- package/dist/telemetry.test.js +37 -0
- package/dist/telemetry.test.js.map +1 -0
- package/dist/transformers.d.ts +1 -1
- package/dist/transformers.js +1 -0
- package/dist/transformers.js.map +1 -1
- package/package.json +1 -1
- package/src/crypto/driveCrypto.ts +2 -0
- package/src/interface/events.ts +1 -1
- package/src/interface/featureFlags.ts +1 -0
- package/src/interface/httpClient.ts +1 -0
- package/src/interface/nodes.ts +7 -0
- package/src/interface/telemetry.ts +4 -0
- package/src/internal/apiService/apiService.test.ts +30 -0
- package/src/internal/apiService/apiService.ts +23 -7
- package/src/internal/apiService/driveTypes.ts +162 -101
- package/src/internal/download/telemetry.test.ts +5 -0
- package/src/internal/download/telemetry.ts +5 -1
- package/src/internal/events/index.ts +2 -2
- package/src/internal/events/interface.ts +1 -1
- package/src/internal/nodes/apiService.test.ts +9 -0
- package/src/internal/nodes/apiService.ts +4 -0
- package/src/internal/nodes/cryptoService.ts +11 -1
- package/src/internal/nodes/extendedAttributes.test.ts +25 -25
- package/src/internal/nodes/extendedAttributes.ts +10 -19
- package/src/internal/nodes/interface.ts +5 -0
- package/src/internal/nodes/nodesManagement.ts +8 -6
- package/src/internal/photos/albumsManager.ts +1 -0
- package/src/internal/photos/nodes.ts +2 -2
- package/src/internal/photos/upload.ts +23 -10
- package/src/internal/sharing/apiService.ts +5 -5
- package/src/internal/upload/apiService.ts +167 -2
- package/src/internal/upload/blockVerifier.ts +12 -0
- package/src/internal/upload/cryptoService.ts +10 -10
- package/src/internal/upload/fileUploader.ts +20 -7
- package/src/internal/upload/index.test.ts +99 -0
- package/src/internal/upload/index.ts +45 -4
- package/src/internal/upload/interface.ts +2 -0
- package/src/internal/upload/manager.test.ts +368 -2
- package/src/internal/upload/manager.ts +229 -78
- package/src/internal/upload/smallFileUploader.test.ts +491 -0
- package/src/internal/upload/smallFileUploader.ts +353 -0
- package/src/internal/upload/streamReader.test.ts +109 -0
- package/src/internal/upload/streamReader.ts +38 -0
- package/src/internal/upload/streamUploader.ts +1 -1
- package/src/internal/upload/telemetry.test.ts +6 -0
- package/src/internal/upload/telemetry.ts +8 -2
- package/src/protonDrivePhotosClient.ts +1 -1
- package/src/protonDrivePublicLinkClient.ts +2 -0
- package/src/telemetry.test.ts +40 -0
- package/src/telemetry.ts +22 -0
- package/src/transformers.ts +2 -0
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import { FeatureFlagProvider, ProtonDriveTelemetry, UploadMetadata } from '../../interface';
|
|
1
|
+
import { FeatureFlagProvider, FeatureFlags, ProtonDriveTelemetry, UploadMetadata } from '../../interface';
|
|
2
|
+
import type { FileUploader } from '../../interface';
|
|
2
3
|
import { DriveAPIService } from '../apiService';
|
|
3
4
|
import { DriveCrypto } from '../../crypto';
|
|
4
5
|
import { UploadAPIService } from './apiService';
|
|
5
6
|
import { UploadCryptoService } from './cryptoService';
|
|
6
|
-
import { FileUploader, FileRevisionUploader } from './fileUploader';
|
|
7
|
+
import { FileUploader as FileUploaderClass, FileRevisionUploader } from './fileUploader';
|
|
7
8
|
import { NodesService, SharesService } from './interface';
|
|
8
9
|
import { UploadManager } from './manager';
|
|
9
10
|
import { UploadQueue } from './queue';
|
|
11
|
+
import { SmallFileRevisionUploader, SmallFileUploader } from './smallFileUploader';
|
|
10
12
|
import { UploadTelemetry } from './telemetry';
|
|
11
13
|
|
|
14
|
+
const SMALL_FILE_SIZE_LIMIT = 128 * 1024; // 128 KiB
|
|
15
|
+
|
|
12
16
|
/**
|
|
13
17
|
* Provides facade for the upload module.
|
|
14
18
|
*
|
|
@@ -24,6 +28,7 @@ export function initUploadModule(
|
|
|
24
28
|
nodesService: NodesService,
|
|
25
29
|
featureFlagProvider: FeatureFlagProvider,
|
|
26
30
|
clientUid?: string,
|
|
31
|
+
allowSmallFileUpload: boolean = true,
|
|
27
32
|
) {
|
|
28
33
|
const api = new UploadAPIService(apiService, clientUid);
|
|
29
34
|
const cryptoService = new UploadCryptoService(telemetry, driveCrypto, nodesService, featureFlagProvider);
|
|
@@ -33,6 +38,15 @@ export function initUploadModule(
|
|
|
33
38
|
|
|
34
39
|
const queue = new UploadQueue();
|
|
35
40
|
|
|
41
|
+
async function useSmallFileUpload(metadata: UploadMetadata): Promise<boolean> {
|
|
42
|
+
const isEnabled =
|
|
43
|
+
allowSmallFileUpload && (await featureFlagProvider.isEnabled(FeatureFlags.DriveSmallFileUpload));
|
|
44
|
+
if (!isEnabled) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
return metadata.expectedSize < SMALL_FILE_SIZE_LIMIT;
|
|
48
|
+
}
|
|
49
|
+
|
|
36
50
|
/**
|
|
37
51
|
* Returns a FileUploader instance that can be used to upload a file to
|
|
38
52
|
* a parent folder.
|
|
@@ -52,7 +66,21 @@ export function initUploadModule(
|
|
|
52
66
|
queue.releaseCapacity(metadata.expectedSize);
|
|
53
67
|
};
|
|
54
68
|
|
|
55
|
-
|
|
69
|
+
if (await useSmallFileUpload(metadata)) {
|
|
70
|
+
return new SmallFileUploader(
|
|
71
|
+
uploadTelemetry,
|
|
72
|
+
api,
|
|
73
|
+
cryptoService,
|
|
74
|
+
manager,
|
|
75
|
+
metadata,
|
|
76
|
+
onFinish,
|
|
77
|
+
signal,
|
|
78
|
+
parentFolderUid,
|
|
79
|
+
name,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return new FileUploaderClass(
|
|
56
84
|
uploadTelemetry,
|
|
57
85
|
api,
|
|
58
86
|
cryptoService,
|
|
@@ -76,13 +104,26 @@ export function initUploadModule(
|
|
|
76
104
|
nodeUid: string,
|
|
77
105
|
metadata: UploadMetadata,
|
|
78
106
|
signal?: AbortSignal,
|
|
79
|
-
): Promise<
|
|
107
|
+
): Promise<FileUploader> {
|
|
80
108
|
await queue.waitForCapacity(metadata.expectedSize, signal);
|
|
81
109
|
|
|
82
110
|
const onFinish = () => {
|
|
83
111
|
queue.releaseCapacity(metadata.expectedSize);
|
|
84
112
|
};
|
|
85
113
|
|
|
114
|
+
if (await useSmallFileUpload(metadata)) {
|
|
115
|
+
return new SmallFileRevisionUploader(
|
|
116
|
+
uploadTelemetry,
|
|
117
|
+
api,
|
|
118
|
+
cryptoService,
|
|
119
|
+
manager,
|
|
120
|
+
metadata,
|
|
121
|
+
onFinish,
|
|
122
|
+
signal,
|
|
123
|
+
nodeUid,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
86
127
|
return new FileRevisionUploader(
|
|
87
128
|
uploadTelemetry,
|
|
88
129
|
api,
|
|
@@ -40,6 +40,7 @@ export type NodeCrypto = {
|
|
|
40
40
|
};
|
|
41
41
|
contentKey: {
|
|
42
42
|
encrypted: {
|
|
43
|
+
contentKeyPacket: Uint8Array<ArrayBuffer>;
|
|
43
44
|
base64ContentKeyPacket: string;
|
|
44
45
|
armoredContentKeyPacketSignature: string;
|
|
45
46
|
};
|
|
@@ -100,6 +101,7 @@ export interface NodesService {
|
|
|
100
101
|
getNodeKeys(nodeUid: string): Promise<{
|
|
101
102
|
key: PrivateKey;
|
|
102
103
|
passphraseSessionKey: SessionKey;
|
|
104
|
+
contentKeyPacket?: Uint8Array<ArrayBuffer>;
|
|
103
105
|
contentKeyPacketSessionKey?: SessionKey;
|
|
104
106
|
hashKey?: Uint8Array<ArrayBuffer>;
|
|
105
107
|
}>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ValidationError } from '../../errors';
|
|
2
|
-
import { ProtonDriveTelemetry, UploadMetadata } from '../../interface';
|
|
2
|
+
import { ProtonDriveTelemetry, ThumbnailType, UploadMetadata } from '../../interface';
|
|
3
3
|
import { getMockTelemetry } from '../../tests/telemetry';
|
|
4
4
|
import { ErrorCode } from '../apiService';
|
|
5
5
|
import { UploadAPIService } from './apiService';
|
|
@@ -27,6 +27,14 @@ describe('UploadManager', () => {
|
|
|
27
27
|
}),
|
|
28
28
|
deleteDraft: jest.fn(),
|
|
29
29
|
commitDraftRevision: jest.fn(),
|
|
30
|
+
uploadSmallFile: jest.fn().mockResolvedValue({
|
|
31
|
+
nodeUid: 'uploaded:nodeUid',
|
|
32
|
+
nodeRevisionUid: 'uploaded:nodeRevisionUid',
|
|
33
|
+
}),
|
|
34
|
+
uploadSmallRevision: jest.fn().mockResolvedValue({
|
|
35
|
+
nodeUid: 'revised:nodeUid',
|
|
36
|
+
nodeRevisionUid: 'revised:nodeRevisionUid',
|
|
37
|
+
}),
|
|
30
38
|
};
|
|
31
39
|
// @ts-expect-error No need to implement all methods for mocking
|
|
32
40
|
cryptoService = {
|
|
@@ -59,6 +67,12 @@ describe('UploadManager', () => {
|
|
|
59
67
|
signatureEmail: 'signatureEmail',
|
|
60
68
|
armoredExtendedAttributes: 'newNode:armoredExtendedAttributes',
|
|
61
69
|
}),
|
|
70
|
+
getSigningKeysForExistingNode: jest.fn().mockResolvedValue({
|
|
71
|
+
email: 'signatureEmail',
|
|
72
|
+
addressId: 'addressId',
|
|
73
|
+
nameAndPassphraseSigningKey: {} as any,
|
|
74
|
+
contentSigningKey: {} as any,
|
|
75
|
+
}),
|
|
62
76
|
};
|
|
63
77
|
nodesService = {
|
|
64
78
|
getNode: jest.fn(async (nodeUid: string) => ({
|
|
@@ -119,7 +133,7 @@ describe('UploadManager', () => {
|
|
|
119
133
|
armoredEncryptedName: 'newNode:encryptedName',
|
|
120
134
|
hash: 'newNode:hash',
|
|
121
135
|
mediaType: 'myMimeType',
|
|
122
|
-
intendedUploadSize:
|
|
136
|
+
intendedUploadSize: 100_000,
|
|
123
137
|
armoredNodeKey: 'newNode:armoredKey',
|
|
124
138
|
armoredNodePassphrase: 'newNode:armoredPassphrase',
|
|
125
139
|
armoredNodePassphraseSignature: 'newNode:armoredPassphraseSignature',
|
|
@@ -263,6 +277,358 @@ describe('UploadManager', () => {
|
|
|
263
277
|
});
|
|
264
278
|
});
|
|
265
279
|
|
|
280
|
+
describe('generateNewFileCrypto', () => {
|
|
281
|
+
it('should throw when parent is not a folder (no hashKey)', async () => {
|
|
282
|
+
nodesService.getNodeKeys = jest.fn().mockResolvedValue({ hashKey: undefined });
|
|
283
|
+
|
|
284
|
+
const result = manager.generateNewFileCrypto('parentUid', 'fileName');
|
|
285
|
+
|
|
286
|
+
await expect(result).rejects.toThrow('Creating files in non-folders is not allowed');
|
|
287
|
+
expect(nodesService.getNodeKeys).toHaveBeenCalledWith('parentUid');
|
|
288
|
+
expect(cryptoService.generateFileCrypto).not.toHaveBeenCalled();
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('should return generated crypto with parentHashKey when parent is folder', async () => {
|
|
292
|
+
const result = await manager.generateNewFileCrypto('parentUid', 'fileName');
|
|
293
|
+
|
|
294
|
+
expect(nodesService.getNodeKeys).toHaveBeenCalledWith('parentUid');
|
|
295
|
+
expect(cryptoService.generateFileCrypto).toHaveBeenCalledWith(
|
|
296
|
+
'parentUid',
|
|
297
|
+
{ key: 'parentNode:nodekey', hashKey: 'parentNode:hashKey' },
|
|
298
|
+
'fileName',
|
|
299
|
+
);
|
|
300
|
+
expect(result).toMatchObject({
|
|
301
|
+
parentHashKey: 'parentNode:hashKey',
|
|
302
|
+
encryptedNode: { encryptedName: 'newNode:encryptedName', hash: 'newNode:hash' },
|
|
303
|
+
nodeKeys: expect.anything(),
|
|
304
|
+
contentKey: expect.anything(),
|
|
305
|
+
signingKeys: { email: 'signatureEmail' },
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
describe('getExistingFileNodeCrypto', () => {
|
|
311
|
+
it('should throw when node has no active revision', async () => {
|
|
312
|
+
nodesService.getNode = jest.fn().mockResolvedValue({
|
|
313
|
+
uid: 'fileNodeUid',
|
|
314
|
+
parentUid: 'parentUid',
|
|
315
|
+
activeRevision: { ok: false, error: new Error('No revision') },
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
const result = manager.getExistingFileNodeCrypto('fileNodeUid');
|
|
319
|
+
|
|
320
|
+
await expect(result).rejects.toThrow('Creating revisions in non-files is not allowed');
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should throw when nodeKeys has no contentKeyPacketSessionKey', async () => {
|
|
324
|
+
nodesService.getNode = jest.fn().mockResolvedValue({
|
|
325
|
+
uid: 'fileNodeUid',
|
|
326
|
+
parentUid: 'parentUid',
|
|
327
|
+
activeRevision: { ok: true, value: { uid: 'revisionUid' } },
|
|
328
|
+
});
|
|
329
|
+
nodesService.getNodeKeys = jest.fn().mockResolvedValue({
|
|
330
|
+
key: 'nodeKey',
|
|
331
|
+
contentKeyPacket: new Uint8Array([1, 2, 3]),
|
|
332
|
+
hashKey: 'hashKey',
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
const result = manager.getExistingFileNodeCrypto('fileNodeUid');
|
|
336
|
+
|
|
337
|
+
await expect(result).rejects.toThrow('Creating revisions in non-files is not allowed');
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('should throw when nodeKeys has no contentKeyPacket', async () => {
|
|
341
|
+
nodesService.getNode = jest.fn().mockResolvedValue({
|
|
342
|
+
uid: 'fileNodeUid',
|
|
343
|
+
parentUid: 'parentUid',
|
|
344
|
+
activeRevision: { ok: true, value: { uid: 'revisionUid' } },
|
|
345
|
+
});
|
|
346
|
+
nodesService.getNodeKeys = jest.fn().mockResolvedValue({
|
|
347
|
+
key: 'nodeKey',
|
|
348
|
+
contentKeyPacketSessionKey: 'sessionKey',
|
|
349
|
+
hashKey: 'hashKey',
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
const result = manager.getExistingFileNodeCrypto('fileNodeUid');
|
|
353
|
+
|
|
354
|
+
await expect(result).rejects.toThrow('Content key packet is required for small revision upload');
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('should return key, contentKeyPacket, contentKeyPacketSessionKey and signingKeys', async () => {
|
|
358
|
+
const contentKeyPacket = new Uint8Array([1, 2, 3]);
|
|
359
|
+
nodesService.getNode = jest.fn().mockResolvedValue({
|
|
360
|
+
uid: 'fileNodeUid',
|
|
361
|
+
parentUid: 'parentUid',
|
|
362
|
+
activeRevision: { ok: true, value: { uid: 'revisionUid' } },
|
|
363
|
+
});
|
|
364
|
+
nodesService.getNodeKeys = jest.fn().mockResolvedValue({
|
|
365
|
+
key: 'nodeKey',
|
|
366
|
+
contentKeyPacket,
|
|
367
|
+
contentKeyPacketSessionKey: 'sessionKey',
|
|
368
|
+
hashKey: 'hashKey',
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
const result = await manager.getExistingFileNodeCrypto('fileNodeUid');
|
|
372
|
+
|
|
373
|
+
expect(cryptoService.getSigningKeysForExistingNode).toHaveBeenCalledWith({
|
|
374
|
+
nodeUid: 'fileNodeUid',
|
|
375
|
+
parentNodeUid: 'parentUid',
|
|
376
|
+
});
|
|
377
|
+
expect(result).toEqual({
|
|
378
|
+
key: 'nodeKey',
|
|
379
|
+
contentKeyPacket,
|
|
380
|
+
contentKeyPacketSessionKey: 'sessionKey',
|
|
381
|
+
signingKeys: {
|
|
382
|
+
email: 'signatureEmail',
|
|
383
|
+
addressId: 'addressId',
|
|
384
|
+
nameAndPassphraseSigningKey: {},
|
|
385
|
+
contentSigningKey: {},
|
|
386
|
+
},
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
describe('uploadFile', () => {
|
|
392
|
+
const nodeCrypto = {
|
|
393
|
+
encryptedNode: { encryptedName: 'encName', hash: 'hash' },
|
|
394
|
+
nodeKeys: {
|
|
395
|
+
encrypted: {
|
|
396
|
+
armoredKey: 'armoredKey',
|
|
397
|
+
armoredPassphrase: 'armoredPassphrase',
|
|
398
|
+
armoredPassphraseSignature: 'armoredPassphraseSignature',
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
contentKey: {
|
|
402
|
+
encrypted: {
|
|
403
|
+
base64ContentKeyPacket: 'base64ContentKeyPacket',
|
|
404
|
+
armoredContentKeyPacketSignature: 'armoredContentKeyPacketSignature',
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
signingKeys: { email: 'signatureEmail' },
|
|
408
|
+
} as any;
|
|
409
|
+
const metadata = { mediaType: 'application/octet-stream', expectedSize: 100 } as UploadMetadata;
|
|
410
|
+
const commitPayload = {
|
|
411
|
+
armoredManifestSignature: 'manifestSignature',
|
|
412
|
+
armoredExtendedAttributes: 'extAttr',
|
|
413
|
+
};
|
|
414
|
+
const encryptedBlock = {
|
|
415
|
+
encryptedData: new Uint8Array([1, 2, 3]),
|
|
416
|
+
armoredSignature: 'blockSig',
|
|
417
|
+
verificationToken: new Uint8Array([4, 5, 6]),
|
|
418
|
+
};
|
|
419
|
+
const encryptedThumbnails = [{ type: ThumbnailType.Type1, encryptedData: new Uint8Array([7, 8, 9]) }];
|
|
420
|
+
|
|
421
|
+
it('should call uploadSmallFile and notifyChildCreated on success', async () => {
|
|
422
|
+
const result = await manager.uploadFile(
|
|
423
|
+
'parentUid',
|
|
424
|
+
nodeCrypto,
|
|
425
|
+
metadata,
|
|
426
|
+
commitPayload,
|
|
427
|
+
encryptedBlock,
|
|
428
|
+
encryptedThumbnails,
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
expect(result).toEqual({
|
|
432
|
+
nodeUid: 'uploaded:nodeUid',
|
|
433
|
+
nodeRevisionUid: 'uploaded:nodeRevisionUid',
|
|
434
|
+
});
|
|
435
|
+
expect(apiService.uploadSmallFile).toHaveBeenCalledWith(
|
|
436
|
+
'parentUid',
|
|
437
|
+
{
|
|
438
|
+
armoredEncryptedName: 'encName',
|
|
439
|
+
hash: 'hash',
|
|
440
|
+
mediaType: 'application/octet-stream',
|
|
441
|
+
armoredNodeKey: 'armoredKey',
|
|
442
|
+
armoredNodePassphrase: 'armoredPassphrase',
|
|
443
|
+
armoredNodePassphraseSignature: 'armoredPassphraseSignature',
|
|
444
|
+
base64ContentKeyPacket: 'base64ContentKeyPacket',
|
|
445
|
+
armoredContentKeyPacketSignature: 'armoredContentKeyPacketSignature',
|
|
446
|
+
armoredExtendedAttributes: 'extAttr',
|
|
447
|
+
signatureEmail: 'signatureEmail',
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
armoredManifestSignature: 'manifestSignature',
|
|
451
|
+
block: encryptedBlock,
|
|
452
|
+
thumbnails: encryptedThumbnails,
|
|
453
|
+
},
|
|
454
|
+
undefined,
|
|
455
|
+
);
|
|
456
|
+
expect(nodesService.notifyChildCreated).toHaveBeenCalledWith('parentUid');
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it('should delete existing draft and retry on ALREADY_EXISTS when own draft', async () => {
|
|
460
|
+
let firstCall = true;
|
|
461
|
+
apiService.uploadSmallFile = jest.fn().mockImplementation(() => {
|
|
462
|
+
if (firstCall) {
|
|
463
|
+
firstCall = false;
|
|
464
|
+
throw new ValidationError('Already exists', ErrorCode.ALREADY_EXISTS, {
|
|
465
|
+
ConflictLinkID: 'existingLinkId',
|
|
466
|
+
ConflictDraftRevisionID: 'existingDraftRevisionId',
|
|
467
|
+
ConflictDraftClientUID: clientUid,
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
return {
|
|
471
|
+
nodeUid: 'uploaded:nodeUid',
|
|
472
|
+
nodeRevisionUid: 'uploaded:nodeRevisionUid',
|
|
473
|
+
};
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
const result = await manager.uploadFile(
|
|
477
|
+
'volumeId~parentUid',
|
|
478
|
+
nodeCrypto,
|
|
479
|
+
{ ...metadata, overrideExistingDraftByOtherClient: false },
|
|
480
|
+
commitPayload,
|
|
481
|
+
encryptedBlock,
|
|
482
|
+
encryptedThumbnails,
|
|
483
|
+
);
|
|
484
|
+
|
|
485
|
+
expect(result).toEqual({
|
|
486
|
+
nodeUid: 'uploaded:nodeUid',
|
|
487
|
+
nodeRevisionUid: 'uploaded:nodeRevisionUid',
|
|
488
|
+
});
|
|
489
|
+
expect(apiService.deleteDraft).toHaveBeenCalledWith('volumeId~existingLinkId');
|
|
490
|
+
expect(apiService.uploadSmallFile).toHaveBeenCalledTimes(2);
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
it('should call uploadSmallFile with block undefined for zero-byte file', async () => {
|
|
494
|
+
const result = await manager.uploadFile(
|
|
495
|
+
'parentUid',
|
|
496
|
+
nodeCrypto,
|
|
497
|
+
{ ...metadata, expectedSize: 0 },
|
|
498
|
+
commitPayload,
|
|
499
|
+
undefined,
|
|
500
|
+
[],
|
|
501
|
+
);
|
|
502
|
+
|
|
503
|
+
expect(result).toEqual({
|
|
504
|
+
nodeUid: 'uploaded:nodeUid',
|
|
505
|
+
nodeRevisionUid: 'uploaded:nodeRevisionUid',
|
|
506
|
+
});
|
|
507
|
+
expect(apiService.uploadSmallFile).toHaveBeenCalledWith(
|
|
508
|
+
'parentUid',
|
|
509
|
+
expect.objectContaining({
|
|
510
|
+
armoredEncryptedName: 'encName',
|
|
511
|
+
hash: 'hash',
|
|
512
|
+
mediaType: 'application/octet-stream',
|
|
513
|
+
armoredExtendedAttributes: 'extAttr',
|
|
514
|
+
signatureEmail: 'signatureEmail',
|
|
515
|
+
}),
|
|
516
|
+
{
|
|
517
|
+
armoredManifestSignature: 'manifestSignature',
|
|
518
|
+
block: undefined,
|
|
519
|
+
thumbnails: [],
|
|
520
|
+
},
|
|
521
|
+
undefined,
|
|
522
|
+
);
|
|
523
|
+
expect(nodesService.notifyChildCreated).toHaveBeenCalledWith('parentUid');
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
describe('uploadSmallRevision', () => {
|
|
528
|
+
const nodeCrypto = { signingKeys: { email: 'signatureEmail' } } as any;
|
|
529
|
+
const commitPayload = {
|
|
530
|
+
armoredManifestSignature: 'manifestSig',
|
|
531
|
+
armoredExtendedAttributes: 'extAttr',
|
|
532
|
+
};
|
|
533
|
+
const encryptedBlock = {
|
|
534
|
+
encryptedData: new Uint8Array([1, 2, 3]),
|
|
535
|
+
armoredSignature: 'blockSig',
|
|
536
|
+
verificationToken: new Uint8Array([4, 5, 6]),
|
|
537
|
+
};
|
|
538
|
+
const encryptedThumbnails = [{ type: ThumbnailType.Type1, encryptedData: new Uint8Array([7, 8, 9]) }];
|
|
539
|
+
|
|
540
|
+
it('should throw when file has no revision', async () => {
|
|
541
|
+
nodesService.getNode = jest.fn().mockResolvedValue({
|
|
542
|
+
uid: 'fileNodeUid',
|
|
543
|
+
parentUid: 'parentUid',
|
|
544
|
+
activeRevision: { ok: false, error: new Error('No revision') },
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
const result = manager.uploadSmallRevision(
|
|
548
|
+
'fileNodeUid',
|
|
549
|
+
nodeCrypto,
|
|
550
|
+
commitPayload,
|
|
551
|
+
encryptedBlock,
|
|
552
|
+
encryptedThumbnails,
|
|
553
|
+
);
|
|
554
|
+
|
|
555
|
+
await expect(result).rejects.toThrow('File has no revision');
|
|
556
|
+
expect(apiService.uploadSmallRevision).not.toHaveBeenCalled();
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
it('should call uploadSmallRevision and notifyNodeChanged on success', async () => {
|
|
560
|
+
nodesService.getNode = jest.fn().mockResolvedValue({
|
|
561
|
+
uid: 'fileNodeUid',
|
|
562
|
+
parentUid: 'parentUid',
|
|
563
|
+
activeRevision: { ok: true, value: { uid: 'currentRevisionUid' } },
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
const result = await manager.uploadSmallRevision(
|
|
567
|
+
'fileNodeUid',
|
|
568
|
+
nodeCrypto,
|
|
569
|
+
commitPayload,
|
|
570
|
+
encryptedBlock,
|
|
571
|
+
encryptedThumbnails,
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
expect(result).toEqual({
|
|
575
|
+
nodeUid: 'revised:nodeUid',
|
|
576
|
+
nodeRevisionUid: 'revised:nodeRevisionUid',
|
|
577
|
+
});
|
|
578
|
+
expect(apiService.uploadSmallRevision).toHaveBeenCalledWith(
|
|
579
|
+
'fileNodeUid',
|
|
580
|
+
'currentRevisionUid',
|
|
581
|
+
{
|
|
582
|
+
signatureEmail: 'signatureEmail',
|
|
583
|
+
armoredExtendedAttributes: 'extAttr',
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
armoredManifestSignature: 'manifestSig',
|
|
587
|
+
block: encryptedBlock,
|
|
588
|
+
thumbnails: encryptedThumbnails,
|
|
589
|
+
},
|
|
590
|
+
undefined,
|
|
591
|
+
);
|
|
592
|
+
expect(nodesService.notifyNodeChanged).toHaveBeenCalledWith('fileNodeUid');
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
it('should call uploadSmallRevision with block undefined for zero-byte revision', async () => {
|
|
596
|
+
nodesService.getNode = jest.fn().mockResolvedValue({
|
|
597
|
+
uid: 'fileNodeUid',
|
|
598
|
+
parentUid: 'parentUid',
|
|
599
|
+
activeRevision: { ok: true, value: { uid: 'currentRevisionUid' } },
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
const result = await manager.uploadSmallRevision(
|
|
603
|
+
'fileNodeUid',
|
|
604
|
+
nodeCrypto,
|
|
605
|
+
commitPayload,
|
|
606
|
+
undefined,
|
|
607
|
+
[],
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
expect(result).toEqual({
|
|
611
|
+
nodeUid: 'revised:nodeUid',
|
|
612
|
+
nodeRevisionUid: 'revised:nodeRevisionUid',
|
|
613
|
+
});
|
|
614
|
+
expect(apiService.uploadSmallRevision).toHaveBeenCalledWith(
|
|
615
|
+
'fileNodeUid',
|
|
616
|
+
'currentRevisionUid',
|
|
617
|
+
{
|
|
618
|
+
signatureEmail: 'signatureEmail',
|
|
619
|
+
armoredExtendedAttributes: 'extAttr',
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
armoredManifestSignature: 'manifestSig',
|
|
623
|
+
block: undefined,
|
|
624
|
+
thumbnails: [],
|
|
625
|
+
},
|
|
626
|
+
undefined,
|
|
627
|
+
);
|
|
628
|
+
expect(nodesService.notifyNodeChanged).toHaveBeenCalledWith('fileNodeUid');
|
|
629
|
+
});
|
|
630
|
+
});
|
|
631
|
+
|
|
266
632
|
describe('commit draft', () => {
|
|
267
633
|
const nodeRevisionDraft = {
|
|
268
634
|
nodeUid: 'newNode:nodeUid',
|