@protontech/drive-sdk 0.0.12 → 0.1.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/cache/index.d.ts +1 -0
- package/dist/cache/index.js +3 -1
- package/dist/cache/index.js.map +1 -1
- package/dist/cache/memoryCache.d.ts +1 -1
- package/dist/cache/nullCache.d.ts +14 -0
- package/dist/cache/nullCache.js +37 -0
- package/dist/cache/nullCache.js.map +1 -0
- package/dist/config.d.ts +16 -1
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/crypto/openPGPCrypto.js +2 -0
- package/dist/crypto/openPGPCrypto.js.map +1 -1
- package/dist/diagnostic/eventsGenerator.d.ts +14 -0
- package/dist/diagnostic/eventsGenerator.js +49 -0
- package/dist/diagnostic/eventsGenerator.js.map +1 -0
- package/dist/diagnostic/httpClient.d.ts +16 -0
- package/dist/diagnostic/httpClient.js +81 -0
- package/dist/diagnostic/httpClient.js.map +1 -0
- package/dist/diagnostic/index.d.ts +10 -0
- package/dist/diagnostic/index.js +35 -0
- package/dist/diagnostic/index.js.map +1 -0
- package/dist/diagnostic/integrityVerificationStream.d.ts +21 -0
- package/dist/diagnostic/integrityVerificationStream.js +56 -0
- package/dist/diagnostic/integrityVerificationStream.js.map +1 -0
- package/dist/diagnostic/interface.d.ts +102 -0
- package/dist/diagnostic/interface.js +3 -0
- package/dist/diagnostic/interface.js.map +1 -0
- package/dist/diagnostic/sdkDiagnostic.d.ts +22 -0
- package/dist/diagnostic/sdkDiagnostic.js +216 -0
- package/dist/diagnostic/sdkDiagnostic.js.map +1 -0
- package/dist/diagnostic/sdkDiagnosticFull.d.ts +18 -0
- package/dist/diagnostic/sdkDiagnosticFull.js +35 -0
- package/dist/diagnostic/sdkDiagnosticFull.js.map +1 -0
- package/dist/diagnostic/telemetry.d.ts +25 -0
- package/dist/diagnostic/telemetry.js +70 -0
- package/dist/diagnostic/telemetry.js.map +1 -0
- package/dist/diagnostic/zipGenerators.d.ts +9 -0
- package/dist/diagnostic/zipGenerators.js +64 -0
- package/dist/diagnostic/zipGenerators.js.map +1 -0
- package/dist/diagnostic/zipGenerators.test.js +144 -0
- package/dist/diagnostic/zipGenerators.test.js.map +1 -0
- package/dist/errors.d.ts +8 -3
- package/dist/errors.js +11 -4
- package/dist/errors.js.map +1 -1
- package/dist/interface/config.d.ts +26 -0
- package/dist/interface/config.js +3 -0
- package/dist/interface/config.js.map +1 -0
- package/dist/interface/download.d.ts +2 -2
- package/dist/interface/events.d.ts +60 -20
- package/dist/interface/events.js +11 -1
- package/dist/interface/events.js.map +1 -1
- package/dist/interface/httpClient.d.ts +0 -14
- package/dist/interface/index.d.ts +9 -5
- package/dist/interface/index.js +2 -1
- package/dist/interface/index.js.map +1 -1
- package/dist/interface/nodes.d.ts +21 -1
- package/dist/interface/nodes.js +11 -0
- package/dist/interface/nodes.js.map +1 -1
- package/dist/interface/sharing.d.ts +1 -0
- package/dist/interface/upload.d.ts +57 -3
- package/dist/internal/apiService/driveTypes.d.ts +1341 -465
- package/dist/internal/apiService/errors.js +2 -2
- package/dist/internal/apiService/errors.js.map +1 -1
- package/dist/internal/apiService/transformers.js +2 -0
- package/dist/internal/apiService/transformers.js.map +1 -1
- package/dist/internal/asyncIteratorMap.d.ts +15 -0
- package/dist/internal/asyncIteratorMap.js +59 -0
- package/dist/internal/asyncIteratorMap.js.map +1 -0
- package/dist/internal/asyncIteratorMap.test.js +120 -0
- package/dist/internal/asyncIteratorMap.test.js.map +1 -0
- package/dist/internal/download/apiService.js +32 -31
- package/dist/internal/download/apiService.js.map +1 -1
- package/dist/internal/download/fileDownloader.d.ts +2 -2
- package/dist/internal/download/fileDownloader.js.map +1 -1
- package/dist/internal/events/apiService.d.ts +4 -6
- package/dist/internal/events/apiService.js +15 -22
- package/dist/internal/events/apiService.js.map +1 -1
- package/dist/internal/events/coreEventManager.d.ts +7 -10
- package/dist/internal/events/coreEventManager.js +19 -36
- package/dist/internal/events/coreEventManager.js.map +1 -1
- package/dist/internal/events/coreEventManager.test.d.ts +1 -0
- package/dist/internal/events/coreEventManager.test.js +87 -0
- package/dist/internal/events/coreEventManager.test.js.map +1 -0
- package/dist/internal/events/eventManager.d.ts +11 -36
- package/dist/internal/events/eventManager.js +59 -105
- package/dist/internal/events/eventManager.js.map +1 -1
- package/dist/internal/events/eventManager.test.js +167 -82
- package/dist/internal/events/eventManager.test.js.map +1 -1
- package/dist/internal/events/index.d.ts +13 -33
- package/dist/internal/events/index.js +56 -72
- package/dist/internal/events/index.js.map +1 -1
- package/dist/internal/events/interface.d.ts +59 -14
- package/dist/internal/events/interface.js +13 -3
- package/dist/internal/events/interface.js.map +1 -1
- package/dist/internal/events/volumeEventManager.d.ts +7 -17
- package/dist/internal/events/volumeEventManager.js +58 -45
- package/dist/internal/events/volumeEventManager.js.map +1 -1
- package/dist/internal/events/volumeEventManager.test.d.ts +1 -0
- package/dist/internal/events/volumeEventManager.test.js +203 -0
- package/dist/internal/events/volumeEventManager.test.js.map +1 -0
- package/dist/internal/nodes/apiService.d.ts +2 -2
- package/dist/internal/nodes/apiService.js +16 -6
- package/dist/internal/nodes/apiService.js.map +1 -1
- package/dist/internal/nodes/apiService.test.js +30 -8
- package/dist/internal/nodes/apiService.test.js.map +1 -1
- package/dist/internal/nodes/cache.d.ts +10 -1
- package/dist/internal/nodes/cache.js +18 -0
- package/dist/internal/nodes/cache.js.map +1 -1
- package/dist/internal/nodes/cache.test.js +1 -0
- package/dist/internal/nodes/cache.test.js.map +1 -1
- package/dist/internal/nodes/cryptoService.d.ts +1 -1
- package/dist/internal/nodes/cryptoService.js.map +1 -1
- package/dist/internal/nodes/cryptoService.test.js +34 -0
- package/dist/internal/nodes/cryptoService.test.js.map +1 -1
- package/dist/internal/nodes/events.d.ts +7 -83
- package/dist/internal/nodes/events.js +43 -217
- package/dist/internal/nodes/events.js.map +1 -1
- package/dist/internal/nodes/events.test.js +27 -277
- package/dist/internal/nodes/events.test.js.map +1 -1
- package/dist/internal/nodes/index.d.ts +3 -4
- package/dist/internal/nodes/index.js +5 -5
- package/dist/internal/nodes/index.js.map +1 -1
- package/dist/internal/nodes/interface.d.ts +3 -1
- package/dist/internal/nodes/nodesAccess.d.ts +15 -0
- package/dist/internal/nodes/nodesAccess.js +65 -7
- package/dist/internal/nodes/nodesAccess.js.map +1 -1
- package/dist/internal/nodes/nodesAccess.test.js +132 -93
- package/dist/internal/nodes/nodesAccess.test.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.d.ts +1 -3
- package/dist/internal/nodes/nodesManagement.js +12 -26
- package/dist/internal/nodes/nodesManagement.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.test.js +35 -14
- package/dist/internal/nodes/nodesManagement.test.js.map +1 -1
- package/dist/internal/shares/cache.d.ts +2 -0
- package/dist/internal/shares/cache.js +2 -0
- package/dist/internal/shares/cache.js.map +1 -1
- package/dist/internal/shares/manager.d.ts +1 -0
- package/dist/internal/shares/manager.js +3 -0
- package/dist/internal/shares/manager.js.map +1 -1
- package/dist/internal/sharing/apiService.js +20 -2
- package/dist/internal/sharing/apiService.js.map +1 -1
- package/dist/internal/sharing/cryptoService.js +1 -0
- package/dist/internal/sharing/cryptoService.js.map +1 -1
- package/dist/internal/sharing/events.d.ts +23 -55
- package/dist/internal/sharing/events.js +46 -138
- package/dist/internal/sharing/events.js.map +1 -1
- package/dist/internal/sharing/events.test.js +77 -180
- package/dist/internal/sharing/events.test.js.map +1 -1
- package/dist/internal/sharing/index.d.ts +4 -5
- package/dist/internal/sharing/index.js +5 -5
- package/dist/internal/sharing/index.js.map +1 -1
- package/dist/internal/sharing/interface.d.ts +3 -0
- package/dist/internal/sharing/sharingManagement.d.ts +2 -3
- package/dist/internal/sharing/sharingManagement.js +7 -9
- package/dist/internal/sharing/sharingManagement.js.map +1 -1
- package/dist/internal/sharing/sharingManagement.test.js +9 -39
- package/dist/internal/sharing/sharingManagement.test.js.map +1 -1
- package/dist/internal/upload/apiService.d.ts +2 -3
- package/dist/internal/upload/apiService.js +7 -4
- package/dist/internal/upload/apiService.js.map +1 -1
- package/dist/internal/upload/fileUploader.d.ts +49 -53
- package/dist/internal/upload/fileUploader.js +91 -395
- package/dist/internal/upload/fileUploader.js.map +1 -1
- package/dist/internal/upload/fileUploader.test.js +38 -292
- package/dist/internal/upload/fileUploader.test.js.map +1 -1
- package/dist/internal/upload/index.d.ts +5 -5
- package/dist/internal/upload/index.js +23 -44
- package/dist/internal/upload/index.js.map +1 -1
- package/dist/internal/upload/interface.d.ts +2 -0
- package/dist/internal/upload/manager.d.ts +6 -6
- package/dist/internal/upload/manager.js +32 -66
- package/dist/internal/upload/manager.js.map +1 -1
- package/dist/internal/upload/manager.test.js +100 -117
- package/dist/internal/upload/manager.test.js.map +1 -1
- package/dist/internal/upload/streamUploader.d.ts +62 -0
- package/dist/internal/upload/streamUploader.js +440 -0
- package/dist/internal/upload/streamUploader.js.map +1 -0
- package/dist/internal/upload/streamUploader.test.d.ts +1 -0
- package/dist/internal/upload/streamUploader.test.js +358 -0
- package/dist/internal/upload/streamUploader.test.js.map +1 -0
- package/dist/protonDriveClient.d.ts +22 -165
- package/dist/protonDriveClient.js +27 -191
- package/dist/protonDriveClient.js.map +1 -1
- package/dist/protonDrivePhotosClient.js +3 -2
- package/dist/protonDrivePhotosClient.js.map +1 -1
- package/package.json +4 -4
- package/src/cache/index.ts +1 -0
- package/src/cache/memoryCache.ts +1 -1
- package/src/cache/nullCache.ts +38 -0
- package/src/config.ts +17 -2
- package/src/crypto/openPGPCrypto.ts +2 -0
- package/src/diagnostic/eventsGenerator.ts +48 -0
- package/src/diagnostic/httpClient.ts +80 -0
- package/src/diagnostic/index.ts +38 -0
- package/src/diagnostic/integrityVerificationStream.ts +56 -0
- package/src/diagnostic/interface.ts +158 -0
- package/src/diagnostic/sdkDiagnostic.ts +238 -0
- package/src/diagnostic/sdkDiagnosticFull.ts +40 -0
- package/src/diagnostic/telemetry.ts +71 -0
- package/src/diagnostic/zipGenerators.test.ts +177 -0
- package/src/diagnostic/zipGenerators.ts +70 -0
- package/src/errors.ts +13 -4
- package/src/interface/config.ts +28 -0
- package/src/interface/download.ts +2 -2
- package/src/interface/events.ts +66 -21
- package/src/interface/httpClient.ts +0 -16
- package/src/interface/index.ts +9 -5
- package/src/interface/nodes.ts +32 -12
- package/src/interface/sharing.ts +1 -0
- package/src/interface/upload.ts +59 -3
- package/src/internal/apiService/driveTypes.ts +1341 -465
- package/src/internal/apiService/errors.ts +3 -2
- package/src/internal/apiService/transformers.ts +2 -0
- package/src/internal/asyncIteratorMap.test.ts +150 -0
- package/src/internal/asyncIteratorMap.ts +64 -0
- package/src/internal/download/apiService.ts +11 -8
- package/src/internal/download/fileDownloader.ts +2 -2
- package/src/internal/events/apiService.ts +25 -28
- package/src/internal/events/coreEventManager.test.ts +101 -0
- package/src/internal/events/coreEventManager.ts +20 -45
- package/src/internal/events/eventManager.test.ts +201 -88
- package/src/internal/events/eventManager.ts +69 -115
- package/src/internal/events/index.ts +54 -84
- package/src/internal/events/interface.ts +70 -15
- package/src/internal/events/volumeEventManager.test.ts +243 -0
- package/src/internal/events/volumeEventManager.ts +55 -53
- package/src/internal/nodes/apiService.test.ts +36 -7
- package/src/internal/nodes/apiService.ts +19 -7
- package/src/internal/nodes/cache.test.ts +1 -0
- package/src/internal/nodes/cache.ts +21 -2
- package/src/internal/nodes/cryptoService.test.ts +38 -0
- package/src/internal/nodes/cryptoService.ts +1 -1
- package/src/internal/nodes/events.test.ts +29 -335
- package/src/internal/nodes/events.ts +45 -253
- package/src/internal/nodes/index.ts +6 -8
- package/src/internal/nodes/interface.ts +6 -3
- package/src/internal/nodes/nodesAccess.test.ts +133 -91
- package/src/internal/nodes/nodesAccess.ts +70 -8
- package/src/internal/nodes/nodesManagement.test.ts +39 -15
- package/src/internal/nodes/nodesManagement.ts +12 -30
- package/src/internal/shares/cache.ts +4 -2
- package/src/internal/shares/manager.ts +9 -5
- package/src/internal/sharing/apiService.ts +25 -2
- package/src/internal/sharing/cache.ts +1 -1
- package/src/internal/sharing/cryptoService.ts +1 -0
- package/src/internal/sharing/events.test.ts +89 -195
- package/src/internal/sharing/events.ts +42 -156
- package/src/internal/sharing/index.ts +6 -9
- package/src/internal/sharing/interface.ts +6 -2
- package/src/internal/sharing/sharingManagement.test.ts +10 -40
- package/src/internal/sharing/sharingManagement.ts +7 -11
- package/src/internal/upload/apiService.ts +5 -6
- package/src/internal/upload/fileUploader.test.ts +46 -376
- package/src/internal/upload/fileUploader.ts +114 -494
- package/src/internal/upload/index.ts +30 -54
- package/src/internal/upload/interface.ts +2 -0
- package/src/internal/upload/manager.test.ts +107 -124
- package/src/internal/upload/manager.ts +48 -80
- package/src/internal/upload/streamUploader.test.ts +468 -0
- package/src/internal/upload/streamUploader.ts +550 -0
- package/src/protonDriveClient.ts +80 -248
- package/src/protonDrivePhotosClient.ts +4 -3
- package/dist/internal/events/cache.d.ts +0 -28
- package/dist/internal/events/cache.js +0 -67
- package/dist/internal/events/cache.js.map +0 -1
- package/dist/internal/events/cache.test.js +0 -43
- package/dist/internal/events/cache.test.js.map +0 -1
- package/dist/internal/nodes/index.test.js +0 -112
- package/dist/internal/nodes/index.test.js.map +0 -1
- package/src/internal/events/cache.test.ts +0 -47
- package/src/internal/events/cache.ts +0 -80
- package/src/internal/nodes/index.test.ts +0 -135
- /package/dist/{internal/events/cache.test.d.ts → diagnostic/zipGenerators.test.d.ts} +0 -0
- /package/dist/internal/{nodes/index.test.d.ts → asyncIteratorMap.test.d.ts} +0 -0
|
@@ -22,7 +22,7 @@ describe('nodesAccess', () => {
|
|
|
22
22
|
apiService = {
|
|
23
23
|
getNode: jest.fn(),
|
|
24
24
|
iterateNodes: jest.fn().mockImplementation(async function* (uids: string[]) {
|
|
25
|
-
yield* uids.map((uid => ({ uid, parentUid: '
|
|
25
|
+
yield* uids.map((uid => ({ uid, parentUid: 'volumeId~parentNodeId' } as EncryptedNode)));
|
|
26
26
|
}),
|
|
27
27
|
iterateChildrenNodeUids: jest.fn(),
|
|
28
28
|
}
|
|
@@ -46,6 +46,7 @@ describe('nodesAccess', () => {
|
|
|
46
46
|
}
|
|
47
47
|
// @ts-expect-error No need to implement all methods for mocking
|
|
48
48
|
shareService = {
|
|
49
|
+
getMyFilesIDs: jest.fn().mockResolvedValue({ volumeId: 'volumeId' }),
|
|
49
50
|
getSharePrivateKey: jest.fn(),
|
|
50
51
|
};
|
|
51
52
|
|
|
@@ -54,49 +55,51 @@ describe('nodesAccess', () => {
|
|
|
54
55
|
|
|
55
56
|
describe('getNode', () => {
|
|
56
57
|
it('should get node from cache', async () => {
|
|
57
|
-
const node = { uid: 'nodeId', isStale: false } as DecryptedNode;
|
|
58
|
+
const node = { uid: 'volumeId~nodeId', isStale: false } as DecryptedNode;
|
|
58
59
|
cache.getNode = jest.fn(() => Promise.resolve(node));
|
|
59
60
|
|
|
60
|
-
const result = await access.getNode('nodeId');
|
|
61
|
+
const result = await access.getNode('volumeId~nodeId');
|
|
61
62
|
expect(result).toBe(node);
|
|
62
63
|
expect(apiService.getNode).not.toHaveBeenCalled();
|
|
63
64
|
});
|
|
64
65
|
|
|
65
|
-
it('should get node from API when
|
|
66
|
-
const encryptedNode = { uid: 'nodeId', parentUid: '
|
|
67
|
-
const decryptedUnparsedNode = { uid: 'nodeId', parentUid: '
|
|
66
|
+
it('should get node from API when cache is stale', async () => {
|
|
67
|
+
const encryptedNode = { uid: 'volumeId~nodeId', parentUid: 'volumeId~parentNodeid' } as EncryptedNode;
|
|
68
|
+
const decryptedUnparsedNode = { uid: 'volumeId~nodeId', parentUid: 'volumeId~parentNodeid', name: { ok: true, value: 'name' } } as DecryptedUnparsedNode;
|
|
68
69
|
const decryptedNode = {
|
|
69
70
|
...decryptedUnparsedNode,
|
|
70
71
|
name: { ok: true, value: 'name' },
|
|
71
72
|
isStale: false,
|
|
72
73
|
activeRevision: undefined,
|
|
73
74
|
folder: undefined,
|
|
75
|
+
treeEventScopeId: "volumeId",
|
|
74
76
|
} as DecryptedNode;
|
|
75
77
|
const decryptedKeys = { key: 'key' } as any as DecryptedNodeKeys;
|
|
76
78
|
|
|
77
|
-
cache.getNode = jest.fn(() => Promise.resolve({ uid: 'nodeId', isStale: true } as DecryptedNode));
|
|
79
|
+
cache.getNode = jest.fn(() => Promise.resolve({ uid: 'volumeId~nodeId', isStale: true } as DecryptedNode));
|
|
78
80
|
apiService.getNode = jest.fn(() => Promise.resolve(encryptedNode));
|
|
79
81
|
cryptoCache.getNodeKeys = jest.fn(() => Promise.resolve({ key: 'parentKey' } as any as DecryptedNodeKeys));
|
|
80
82
|
cryptoService.decryptNode = jest.fn(() => Promise.resolve({ node: decryptedUnparsedNode, keys: decryptedKeys }));
|
|
81
83
|
|
|
82
|
-
const result = await access.getNode('nodeId');
|
|
84
|
+
const result = await access.getNode('volumeId~nodeId');
|
|
83
85
|
expect(result).toEqual(decryptedNode);
|
|
84
|
-
expect(apiService.getNode).toHaveBeenCalledWith('nodeId');
|
|
85
|
-
expect(cryptoCache.getNodeKeys).toHaveBeenCalledWith('
|
|
86
|
+
expect(apiService.getNode).toHaveBeenCalledWith('volumeId~nodeId', 'volumeId');
|
|
87
|
+
expect(cryptoCache.getNodeKeys).toHaveBeenCalledWith('volumeId~parentNodeid');
|
|
86
88
|
expect(cryptoService.decryptNode).toHaveBeenCalledWith(encryptedNode, 'parentKey');
|
|
87
89
|
expect(cache.setNode).toHaveBeenCalledWith(decryptedNode);
|
|
88
|
-
expect(cryptoCache.setNodeKeys).toHaveBeenCalledWith('nodeId', decryptedKeys);
|
|
90
|
+
expect(cryptoCache.setNodeKeys).toHaveBeenCalledWith('volumeId~nodeId', decryptedKeys);
|
|
89
91
|
});
|
|
90
92
|
|
|
91
93
|
it('should get node from API missing cache', async () => {
|
|
92
|
-
const encryptedNode = { uid: 'nodeId', parentUid: '
|
|
93
|
-
const decryptedUnparsedNode = { uid: 'nodeId', parentUid: '
|
|
94
|
+
const encryptedNode = { uid: 'volumeId~nodeId', parentUid: 'volumeId~parentNodeid' } as EncryptedNode;
|
|
95
|
+
const decryptedUnparsedNode = { uid: 'volumeId~nodeId', parentUid: 'volumeId~parentNodeid', name: { ok: true, value: 'name' } } as DecryptedUnparsedNode;
|
|
94
96
|
const decryptedNode = {
|
|
95
97
|
...decryptedUnparsedNode,
|
|
96
98
|
name: { ok: true, value: 'name' },
|
|
97
99
|
isStale: false,
|
|
98
100
|
activeRevision: undefined,
|
|
99
101
|
folder: undefined,
|
|
102
|
+
treeEventScopeId: 'volumeId',
|
|
100
103
|
} as DecryptedNode;
|
|
101
104
|
const decryptedKeys = { key: 'key' } as any as DecryptedNodeKeys;
|
|
102
105
|
|
|
@@ -105,21 +108,22 @@ describe('nodesAccess', () => {
|
|
|
105
108
|
cryptoCache.getNodeKeys = jest.fn(() => Promise.resolve({ key: 'parentKey' } as any as DecryptedNodeKeys));
|
|
106
109
|
cryptoService.decryptNode = jest.fn(() => Promise.resolve({ node: decryptedUnparsedNode, keys: decryptedKeys }));
|
|
107
110
|
|
|
108
|
-
const result = await access.getNode('nodeId');
|
|
111
|
+
const result = await access.getNode('volumeId~nodeId');
|
|
109
112
|
expect(result).toEqual(decryptedNode);
|
|
110
|
-
expect(apiService.getNode).toHaveBeenCalledWith('nodeId');
|
|
111
|
-
expect(cryptoCache.getNodeKeys).toHaveBeenCalledWith('
|
|
113
|
+
expect(apiService.getNode).toHaveBeenCalledWith('volumeId~nodeId', 'volumeId');
|
|
114
|
+
expect(cryptoCache.getNodeKeys).toHaveBeenCalledWith('volumeId~parentNodeid');
|
|
112
115
|
expect(cryptoService.decryptNode).toHaveBeenCalledWith(encryptedNode, 'parentKey');
|
|
113
116
|
expect(cache.setNode).toHaveBeenCalledWith(decryptedNode);
|
|
114
|
-
expect(cryptoCache.setNodeKeys).toHaveBeenCalledWith('nodeId', decryptedKeys);
|
|
117
|
+
expect(cryptoCache.setNodeKeys).toHaveBeenCalledWith('volumeId~nodeId', decryptedKeys);
|
|
115
118
|
});
|
|
116
119
|
|
|
117
120
|
it('should validate node name', async () => {
|
|
118
|
-
const encryptedNode = { uid: 'nodeId', parentUid: '
|
|
119
|
-
const decryptedUnparsedNode = { uid: 'nodeId', parentUid: '
|
|
121
|
+
const encryptedNode = { uid: 'volumeId~nodeId', parentUid: 'volumeId~parentNodeid' } as EncryptedNode;
|
|
122
|
+
const decryptedUnparsedNode = { uid: 'volumeId~nodeId', parentUid: 'volumeId~parentNodeid', name: { ok: true, value: 'foo/bar' } } as DecryptedUnparsedNode;
|
|
120
123
|
const decryptedNode = {
|
|
121
124
|
...decryptedUnparsedNode,
|
|
122
125
|
name: { ok: false, error: { name: 'foo/bar', error: "Name must not contain the character '/'" } },
|
|
126
|
+
treeEventScopeId: 'volumeId',
|
|
123
127
|
} as DecryptedNode;
|
|
124
128
|
const decryptedKeys = { key: 'key' } as any as DecryptedNodeKeys;
|
|
125
129
|
|
|
@@ -128,7 +132,7 @@ describe('nodesAccess', () => {
|
|
|
128
132
|
cryptoCache.getNodeKeys = jest.fn(() => Promise.resolve({ key: 'parentKey' } as any as DecryptedNodeKeys));
|
|
129
133
|
cryptoService.decryptNode = jest.fn(() => Promise.resolve({ node: decryptedUnparsedNode, keys: decryptedKeys }));
|
|
130
134
|
|
|
131
|
-
const result = await access.getNode('nodeId');
|
|
135
|
+
const result = await access.getNode('volumeId~nodeId');
|
|
132
136
|
expect(result).toMatchObject(decryptedNode);
|
|
133
137
|
});
|
|
134
138
|
});
|
|
@@ -143,11 +147,11 @@ describe('nodesAccess', () => {
|
|
|
143
147
|
});
|
|
144
148
|
|
|
145
149
|
describe('iterateChildren', () => {
|
|
146
|
-
const parentNode = { uid: '
|
|
147
|
-
const node1 = { uid: 'node1', isStale: false } as DecryptedNode;
|
|
148
|
-
const node2 = { uid: 'node2', isStale: false } as DecryptedNode;
|
|
149
|
-
const node3 = { uid: 'node3', isStale: false } as DecryptedNode;
|
|
150
|
-
const node4 = { uid: 'node4', isStale: false } as DecryptedNode;
|
|
150
|
+
const parentNode = { uid: 'volumeId~parentNodeid', isStale: false } as DecryptedNode;
|
|
151
|
+
const node1 = { uid: 'volumeId~node1', isStale: false } as DecryptedNode;
|
|
152
|
+
const node2 = { uid: 'volumeId~node2', isStale: false } as DecryptedNode;
|
|
153
|
+
const node3 = { uid: 'volumeId~node3', isStale: false } as DecryptedNode;
|
|
154
|
+
const node4 = { uid: 'volumeId~node4', isStale: false } as DecryptedNode;
|
|
151
155
|
|
|
152
156
|
beforeEach(() => {
|
|
153
157
|
cache.getNode = jest.fn().mockResolvedValue(parentNode);
|
|
@@ -162,7 +166,7 @@ describe('nodesAccess', () => {
|
|
|
162
166
|
yield { ok: true, node: node4 };
|
|
163
167
|
});
|
|
164
168
|
|
|
165
|
-
const result = await Array.fromAsync(access.iterateFolderChildren('
|
|
169
|
+
const result = await Array.fromAsync(access.iterateFolderChildren('volumeId~parentNodeid'));
|
|
166
170
|
expect(result).toMatchObject([node1, node2, node3, node4]);
|
|
167
171
|
expect(apiService.iterateChildrenNodeUids).not.toHaveBeenCalled();
|
|
168
172
|
expect(apiService.iterateNodes).not.toHaveBeenCalled();
|
|
@@ -177,9 +181,9 @@ describe('nodesAccess', () => {
|
|
|
177
181
|
yield { ok: true, uid: node4.uid, node: node4 };
|
|
178
182
|
});
|
|
179
183
|
|
|
180
|
-
const result = await Array.fromAsync(access.iterateFolderChildren('
|
|
184
|
+
const result = await Array.fromAsync(access.iterateFolderChildren('volumeId~parentNodeid'));
|
|
181
185
|
expect(result).toMatchObject([node1, node4, node2, node3]);
|
|
182
|
-
expect(apiService.iterateNodes).toHaveBeenCalledWith([
|
|
186
|
+
expect(apiService.iterateNodes).toHaveBeenCalledWith([node2.uid, node3.uid], 'volumeId', undefined);
|
|
183
187
|
expect(cryptoService.decryptNode).toHaveBeenCalledTimes(2);
|
|
184
188
|
expect(cache.setNode).toHaveBeenCalledTimes(2);
|
|
185
189
|
expect(cryptoCache.setNodeKeys).toHaveBeenCalledTimes(2);
|
|
@@ -187,26 +191,26 @@ describe('nodesAccess', () => {
|
|
|
187
191
|
|
|
188
192
|
it('should load children uids and serve nodes from cache', async () => {
|
|
189
193
|
apiService.iterateChildrenNodeUids = jest.fn().mockImplementation(async function* () {
|
|
190
|
-
yield
|
|
191
|
-
yield
|
|
192
|
-
yield
|
|
193
|
-
yield
|
|
194
|
+
yield node1.uid;
|
|
195
|
+
yield node2.uid;
|
|
196
|
+
yield node3.uid;
|
|
197
|
+
yield node4.uid;
|
|
194
198
|
});
|
|
195
199
|
cache.getNode = jest.fn().mockImplementation((uid: string) => ({ uid, isStale: false }));
|
|
196
200
|
|
|
197
|
-
const result = await Array.fromAsync(access.iterateFolderChildren('
|
|
201
|
+
const result = await Array.fromAsync(access.iterateFolderChildren('volumeId~parentNodeid'));
|
|
198
202
|
expect(result).toMatchObject([node1, node2, node3, node4]);
|
|
199
|
-
expect(apiService.iterateChildrenNodeUids).toHaveBeenCalledWith('
|
|
203
|
+
expect(apiService.iterateChildrenNodeUids).toHaveBeenCalledWith('volumeId~parentNodeid', undefined);
|
|
200
204
|
expect(apiService.iterateNodes).not.toHaveBeenCalled();
|
|
201
|
-
expect(cache.setFolderChildrenLoaded).toHaveBeenCalledWith('
|
|
205
|
+
expect(cache.setFolderChildrenLoaded).toHaveBeenCalledWith('volumeId~parentNodeid');
|
|
202
206
|
});
|
|
203
207
|
|
|
204
208
|
it('should load from API', async () => {
|
|
205
209
|
apiService.iterateChildrenNodeUids = jest.fn().mockImplementation(async function* () {
|
|
206
|
-
yield
|
|
207
|
-
yield
|
|
208
|
-
yield
|
|
209
|
-
yield
|
|
210
|
+
yield node1.uid;
|
|
211
|
+
yield node2.uid;
|
|
212
|
+
yield node3.uid;
|
|
213
|
+
yield node4.uid;
|
|
210
214
|
});
|
|
211
215
|
cache.getNode = jest.fn().mockImplementation((uid: string) => {
|
|
212
216
|
if (uid === parentNode.uid) {
|
|
@@ -215,21 +219,21 @@ describe('nodesAccess', () => {
|
|
|
215
219
|
throw new Error('Entity not found');
|
|
216
220
|
});
|
|
217
221
|
|
|
218
|
-
const result = await Array.fromAsync(access.iterateFolderChildren('
|
|
222
|
+
const result = await Array.fromAsync(access.iterateFolderChildren('volumeId~parentNodeid'));
|
|
219
223
|
expect(result).toMatchObject([node1, node2, node3, node4]);
|
|
220
|
-
expect(apiService.iterateChildrenNodeUids).toHaveBeenCalledWith('
|
|
221
|
-
expect(apiService.iterateNodes).toHaveBeenCalledWith(['node1', 'node2', 'node3', 'node4'], undefined);
|
|
224
|
+
expect(apiService.iterateChildrenNodeUids).toHaveBeenCalledWith('volumeId~parentNodeid', undefined);
|
|
225
|
+
expect(apiService.iterateNodes).toHaveBeenCalledWith(['volumeId~node1', 'volumeId~node2', 'volumeId~node3', 'volumeId~node4'], 'volumeId', undefined);
|
|
222
226
|
expect(cryptoService.decryptNode).toHaveBeenCalledTimes(4);
|
|
223
227
|
expect(cache.setNode).toHaveBeenCalledTimes(4);
|
|
224
228
|
expect(cryptoCache.setNodeKeys).toHaveBeenCalledTimes(4);
|
|
225
|
-
expect(cache.setFolderChildrenLoaded).toHaveBeenCalledWith('
|
|
229
|
+
expect(cache.setFolderChildrenLoaded).toHaveBeenCalledWith('volumeId~parentNodeid');
|
|
226
230
|
});
|
|
227
231
|
|
|
228
232
|
it('should remove from cache if missing on API', async () => {
|
|
229
233
|
apiService.iterateChildrenNodeUids = jest.fn().mockImplementation(async function* () {
|
|
230
|
-
yield
|
|
231
|
-
yield
|
|
232
|
-
yield
|
|
234
|
+
yield node1.uid;
|
|
235
|
+
yield node2.uid;
|
|
236
|
+
yield node3.uid;
|
|
233
237
|
});
|
|
234
238
|
cache.getNode = jest.fn().mockImplementation((uid: string) => {
|
|
235
239
|
if (uid === parentNode.uid) {
|
|
@@ -242,16 +246,16 @@ describe('nodesAccess', () => {
|
|
|
242
246
|
yield* uids.slice(1).map((uid) => ({ uid, parentUid: parentNode.uid } as EncryptedNode));
|
|
243
247
|
});
|
|
244
248
|
|
|
245
|
-
const result = await Array.fromAsync(access.iterateFolderChildren('
|
|
249
|
+
const result = await Array.fromAsync(access.iterateFolderChildren('volumeId~parentNodeid'));
|
|
246
250
|
expect(result).toMatchObject([node2, node3]);
|
|
247
|
-
expect(cache.removeNodes).toHaveBeenCalledWith([
|
|
251
|
+
expect(cache.removeNodes).toHaveBeenCalledWith([node1.uid]);
|
|
248
252
|
});
|
|
249
253
|
|
|
250
254
|
it('should yield all decryptable children before throwing error', async () => {
|
|
251
255
|
apiService.iterateChildrenNodeUids = jest.fn().mockImplementation(async function* () {
|
|
252
|
-
yield 'node1';
|
|
253
|
-
yield 'node2';
|
|
254
|
-
yield 'node3';
|
|
256
|
+
yield 'volumeId~node1';
|
|
257
|
+
yield 'volumeId~node2';
|
|
258
|
+
yield 'volumeId~node3';
|
|
255
259
|
});
|
|
256
260
|
cache.getNode = jest.fn().mockImplementation((uid: string) => {
|
|
257
261
|
if (uid === parentNode.uid) {
|
|
@@ -260,7 +264,7 @@ describe('nodesAccess', () => {
|
|
|
260
264
|
throw new Error('Entity not found');
|
|
261
265
|
});
|
|
262
266
|
cryptoService.decryptNode = jest.fn().mockImplementation((encryptedNode: EncryptedNode) => {
|
|
263
|
-
if (encryptedNode.uid === 'node2') {
|
|
267
|
+
if (encryptedNode.uid === 'volumeId~node2') {
|
|
264
268
|
throw new DecryptionError('Decryption failed');
|
|
265
269
|
}
|
|
266
270
|
return Promise.resolve({
|
|
@@ -269,11 +273,11 @@ describe('nodesAccess', () => {
|
|
|
269
273
|
});
|
|
270
274
|
});
|
|
271
275
|
|
|
272
|
-
const generator = access.iterateFolderChildren('
|
|
276
|
+
const generator = access.iterateFolderChildren('volumeId~parentNodeid');
|
|
273
277
|
const node1 = await generator.next();
|
|
274
|
-
expect(node1.value).toMatchObject({ uid: 'node1' });
|
|
278
|
+
expect(node1.value).toMatchObject({ uid: 'volumeId~node1' });
|
|
275
279
|
const node2 = await generator.next();
|
|
276
|
-
expect(node2.value).toMatchObject({ uid: 'node3' });
|
|
280
|
+
expect(node2.value).toMatchObject({ uid: 'volumeId~node3' });
|
|
277
281
|
const node3 = generator.next();
|
|
278
282
|
await expect(node3).rejects.toThrow('Failed to decrypt some nodes');
|
|
279
283
|
try {
|
|
@@ -288,10 +292,10 @@ describe('nodesAccess', () => {
|
|
|
288
292
|
|
|
289
293
|
describe('iterateTrashedNodes', () => {
|
|
290
294
|
const volumeId = 'volumeId';
|
|
291
|
-
const node1 = { uid: 'node1', isStale: false } as DecryptedNode;
|
|
292
|
-
const node2 = { uid: 'node2', isStale: false } as DecryptedNode;
|
|
293
|
-
const node3 = { uid: 'node3', isStale: false } as DecryptedNode;
|
|
294
|
-
const node4 = { uid: 'node4', isStale: false } as DecryptedNode;
|
|
295
|
+
const node1 = { uid: 'volumeId~node1', isStale: false } as DecryptedNode;
|
|
296
|
+
const node2 = { uid: 'volumeId~node2', isStale: false } as DecryptedNode;
|
|
297
|
+
const node3 = { uid: 'volumeId~node3', isStale: false } as DecryptedNode;
|
|
298
|
+
const node4 = { uid: 'volumeId~node4', isStale: false } as DecryptedNode;
|
|
295
299
|
|
|
296
300
|
beforeEach(() => {
|
|
297
301
|
shareService.getMyFilesIDs = jest.fn().mockResolvedValue({ volumeId });
|
|
@@ -320,7 +324,7 @@ describe('nodesAccess', () => {
|
|
|
320
324
|
const result = await Array.fromAsync(access.iterateTrashedNodes());
|
|
321
325
|
expect(result).toMatchObject([node1, node2, node3, node4]);
|
|
322
326
|
expect(apiService.iterateTrashedNodeUids).toHaveBeenCalledWith(volumeId, undefined);
|
|
323
|
-
expect(apiService.iterateNodes).toHaveBeenCalledWith(['node1', 'node2', 'node3', 'node4'], undefined);
|
|
327
|
+
expect(apiService.iterateNodes).toHaveBeenCalledWith(['volumeId~node1', 'volumeId~node2', 'volumeId~node3', 'volumeId~node4'], volumeId, undefined);
|
|
324
328
|
expect(cryptoService.decryptNode).toHaveBeenCalledTimes(4);
|
|
325
329
|
expect(cache.setNode).toHaveBeenCalledTimes(4);
|
|
326
330
|
expect(cryptoCache.setNodeKeys).toHaveBeenCalledTimes(4);
|
|
@@ -332,20 +336,20 @@ describe('nodesAccess', () => {
|
|
|
332
336
|
});
|
|
333
337
|
apiService.iterateNodes = jest.fn().mockImplementation(async function* (uids: string[]) {
|
|
334
338
|
// Skip first node - make it missing.
|
|
335
|
-
yield* uids.slice(1).map((uid) => ({ uid, parentUid: '
|
|
339
|
+
yield* uids.slice(1).map((uid) => ({ uid, parentUid: 'volumeId~parentNodeid' } as EncryptedNode));
|
|
336
340
|
});
|
|
337
341
|
|
|
338
342
|
const result = await Array.fromAsync(access.iterateTrashedNodes());
|
|
339
343
|
expect(result).toMatchObject([node2, node3, node4]);
|
|
340
|
-
expect(cache.removeNodes).toHaveBeenCalledWith(['node1']);
|
|
344
|
+
expect(cache.removeNodes).toHaveBeenCalledWith(['volumeId~node1']);
|
|
341
345
|
});
|
|
342
346
|
});
|
|
343
347
|
|
|
344
348
|
describe('iterateNodes', () => {
|
|
345
|
-
const node1 = { uid: 'node1', isStale: false } as DecryptedNode;
|
|
346
|
-
const node2 = { uid: 'node2', isStale: false } as DecryptedNode;
|
|
347
|
-
const node3 = { uid: 'node3', isStale: false } as DecryptedNode;
|
|
348
|
-
const node4 = { uid: 'node4', isStale: false } as DecryptedNode;
|
|
349
|
+
const node1 = { uid: 'volumeId~node1', isStale: false, treeEventScopeId: 'volumeId' } as DecryptedNode;
|
|
350
|
+
const node2 = { uid: 'volumeId~node2', isStale: false, treeEventScopeId: 'volumeId' } as DecryptedNode;
|
|
351
|
+
const node3 = { uid: 'volumeId~node3', isStale: false, treeEventScopeId: 'volumeId' } as DecryptedNode;
|
|
352
|
+
const node4 = { uid: 'volume~node4', isStale: false, treeEventScopeId: 'volumeId' } as DecryptedNode;
|
|
349
353
|
|
|
350
354
|
it('should serve fully from cache', async () => {
|
|
351
355
|
cache.iterateNodes = jest.fn().mockImplementation(async function* () {
|
|
@@ -355,7 +359,7 @@ describe('nodesAccess', () => {
|
|
|
355
359
|
yield { ok: true, node: node4 };
|
|
356
360
|
});
|
|
357
361
|
|
|
358
|
-
const result = await Array.fromAsync(access.iterateNodes(['node1', 'node2', 'node3', 'node4']));
|
|
362
|
+
const result = await Array.fromAsync(access.iterateNodes(['volumeId~node1', 'volumeId~node2', 'volumeId~node3', 'volumeId~node4']));
|
|
359
363
|
expect(result).toMatchObject([node1, node2, node3, node4]);
|
|
360
364
|
expect(apiService.iterateNodes).not.toHaveBeenCalled();
|
|
361
365
|
});
|
|
@@ -363,52 +367,55 @@ describe('nodesAccess', () => {
|
|
|
363
367
|
it('should load from API', async () => {
|
|
364
368
|
cache.iterateNodes = jest.fn().mockImplementation(async function* () {
|
|
365
369
|
yield { ok: true, node: node1 };
|
|
366
|
-
yield { ok: false, uid: 'node2' };
|
|
367
|
-
yield { ok: false, uid: 'node3' };
|
|
370
|
+
yield { ok: false, uid: 'volumeId~node2' };
|
|
371
|
+
yield { ok: false, uid: 'volumeId~node3' };
|
|
368
372
|
yield { ok: true, node: node4 };
|
|
369
373
|
});
|
|
370
374
|
|
|
371
|
-
const result = await Array.fromAsync(access.iterateNodes(['node1', 'node2', 'node3', 'node4']));
|
|
375
|
+
const result = await Array.fromAsync(access.iterateNodes(['volumeId~node1', 'volumeId~node2', 'volumeId~node3', 'volumeId~node4']));
|
|
372
376
|
expect(result).toMatchObject([node1, node4, node2, node3]);
|
|
373
|
-
expect(apiService.iterateNodes).toHaveBeenCalledWith(['node2', 'node3'], undefined);
|
|
377
|
+
expect(apiService.iterateNodes).toHaveBeenCalledWith(['volumeId~node2', 'volumeId~node3'], 'volumeId', undefined);
|
|
374
378
|
});
|
|
375
379
|
|
|
376
380
|
it('should remove from cache if missing on API and return back to caller', async () => {
|
|
377
381
|
cache.iterateNodes = jest.fn().mockImplementation(async function* () {
|
|
378
|
-
yield { ok: false, uid: 'node1' };
|
|
379
|
-
yield { ok: false, uid: 'node2' };
|
|
380
|
-
yield { ok: false, uid: 'node3' };
|
|
382
|
+
yield { ok: false, uid: 'volumeId~node1' };
|
|
383
|
+
yield { ok: false, uid: 'volumeId~node2' };
|
|
384
|
+
yield { ok: false, uid: 'volumeId~node3' };
|
|
381
385
|
});
|
|
382
386
|
apiService.iterateNodes = jest.fn().mockImplementation(async function* (uids: string[]) {
|
|
383
387
|
// Skip first node - make it missing.
|
|
384
|
-
yield* uids.slice(1).map((uid) => ({ uid, parentUid: '
|
|
388
|
+
yield* uids.slice(1).map((uid) => ({ uid, parentUid: 'volumeId~parentNodeid' } as EncryptedNode));
|
|
385
389
|
});
|
|
386
390
|
|
|
387
|
-
const result = await Array.fromAsync(access.iterateNodes(['node1', 'node2', 'node3']));
|
|
388
|
-
expect(result).toMatchObject([node2, node3, {missingUid: 'node1'}]);
|
|
389
|
-
expect(cache.removeNodes).toHaveBeenCalledWith(['node1']);
|
|
391
|
+
const result = await Array.fromAsync(access.iterateNodes(['volumeId~node1', 'volumeId~node2', 'volumeId~node3']));
|
|
392
|
+
expect(result).toMatchObject([node2, node3, {missingUid: 'volumeId~node1'}]);
|
|
393
|
+
expect(cache.removeNodes).toHaveBeenCalledWith(['volumeId~node1']);
|
|
390
394
|
});
|
|
391
395
|
|
|
392
396
|
it('should return degraded node if parent cannot be decrypted', async () => {
|
|
393
397
|
cache.iterateNodes = jest.fn().mockImplementation(async function* () {
|
|
394
|
-
yield { ok: false, uid: 'node1' };
|
|
395
|
-
yield { ok: false, uid: 'node2' };
|
|
396
|
-
yield { ok: false, uid: 'node3' };
|
|
398
|
+
yield { ok: false, uid: 'volumeId~node1' };
|
|
399
|
+
yield { ok: false, uid: 'volumeId~node2' };
|
|
400
|
+
yield { ok: false, uid: 'volumeId~node3' };
|
|
397
401
|
});
|
|
398
402
|
const encryptedCrypto = {
|
|
399
403
|
signatureEmail: 'signatureEmail',
|
|
400
404
|
nameSignatureEmail: 'nameSignatureEmail',
|
|
401
405
|
};
|
|
402
406
|
apiService.iterateNodes = jest.fn().mockImplementation(async function* (uids: string[]) {
|
|
403
|
-
yield* uids.map((uid) =>
|
|
407
|
+
yield* uids.map((uid) => {
|
|
408
|
+
const parentUid = uid.replace('node', 'parentOfNode');
|
|
409
|
+
return {
|
|
404
410
|
uid,
|
|
405
|
-
parentUid
|
|
411
|
+
parentUid,
|
|
406
412
|
encryptedCrypto,
|
|
407
|
-
|
|
413
|
+
} as EncryptedNode
|
|
414
|
+
});
|
|
408
415
|
});
|
|
409
416
|
const decryptionError = new DecryptionError('Parent cannot be decrypted');
|
|
410
417
|
jest.spyOn(access, 'getParentKeys').mockImplementation(async ({ parentUid }) => {
|
|
411
|
-
if (parentUid === '
|
|
418
|
+
if (parentUid === 'volumeId~parentOfNode1') {
|
|
412
419
|
throw decryptionError;
|
|
413
420
|
}
|
|
414
421
|
return {
|
|
@@ -416,12 +423,12 @@ describe('nodesAccess', () => {
|
|
|
416
423
|
} as any;
|
|
417
424
|
} );
|
|
418
425
|
|
|
419
|
-
const result = await Array.fromAsync(access.iterateNodes(['node1', 'node2', 'node3']));
|
|
426
|
+
const result = await Array.fromAsync(access.iterateNodes(['volumeId~node1', 'volumeId~node2', 'volumeId~node3']));
|
|
420
427
|
expect(result).toEqual([
|
|
421
428
|
{
|
|
422
429
|
...node1,
|
|
423
430
|
encryptedCrypto,
|
|
424
|
-
parentUid: '
|
|
431
|
+
parentUid: 'volumeId~parentOfNode1',
|
|
425
432
|
name: { ok: false, error: decryptionError },
|
|
426
433
|
keyAuthor: { ok: false, error: { claimedAuthor: 'signatureEmail', error: decryptionError.message } },
|
|
427
434
|
nameAuthor: { ok: false, error: { claimedAuthor: 'nameSignatureEmail', error: decryptionError.message } },
|
|
@@ -456,7 +463,7 @@ describe('nodesAccess', () => {
|
|
|
456
463
|
it('should get node parent keys', async () => {
|
|
457
464
|
cryptoCache.getNodeKeys = jest.fn(() => Promise.resolve({ key: 'parentKey' } as any as DecryptedNodeKeys));
|
|
458
465
|
|
|
459
|
-
const result = await access.getParentKeys({ shareId: undefined, parentUid: '
|
|
466
|
+
const result = await access.getParentKeys({ shareId: undefined, parentUid: 'volumeId~parentNodeid' });
|
|
460
467
|
expect(result).toEqual({ key: 'parentKey' });
|
|
461
468
|
expect(shareService.getSharePrivateKey).not.toHaveBeenCalled();
|
|
462
469
|
});
|
|
@@ -464,7 +471,7 @@ describe('nodesAccess', () => {
|
|
|
464
471
|
it('should get node parent keys even if share is set', async () => {
|
|
465
472
|
cryptoCache.getNodeKeys = jest.fn(() => Promise.resolve({ key: 'parentKey' } as any as DecryptedNodeKeys));
|
|
466
473
|
|
|
467
|
-
const result = await access.getParentKeys({ shareId: 'shareId', parentUid: '
|
|
474
|
+
const result = await access.getParentKeys({ shareId: 'shareId', parentUid: 'volume1~parentNodeid' });
|
|
468
475
|
expect(result).toEqual({ key: 'parentKey' });
|
|
469
476
|
expect(shareService.getSharePrivateKey).not.toHaveBeenCalled();
|
|
470
477
|
});
|
|
@@ -476,7 +483,7 @@ describe('nodesAccess', () => {
|
|
|
476
483
|
apiService.getNode = jest.fn(() => Promise.reject(new Error('API called')));
|
|
477
484
|
|
|
478
485
|
try {
|
|
479
|
-
await access.getNodeKeys('nodeId');
|
|
486
|
+
await access.getNodeKeys('volumeId~nodeId');
|
|
480
487
|
throw new Error('Expected error');
|
|
481
488
|
} catch (error: unknown) {
|
|
482
489
|
expect(`${error}`).toBe('Error: API called');
|
|
@@ -489,7 +496,7 @@ describe('nodesAccess', () => {
|
|
|
489
496
|
const nodeUid = 'nodeUid';
|
|
490
497
|
const node = {
|
|
491
498
|
uid: nodeUid,
|
|
492
|
-
parentUid: '
|
|
499
|
+
parentUid: 'volume1~parentNodeid',
|
|
493
500
|
encryptedName: 'encryptedName',
|
|
494
501
|
} as DecryptedNode;
|
|
495
502
|
|
|
@@ -552,4 +559,39 @@ describe('nodesAccess', () => {
|
|
|
552
559
|
expect(result).toBe('https://drive.proton.me/shareId/folder/nodeId');
|
|
553
560
|
});
|
|
554
561
|
});
|
|
562
|
+
|
|
563
|
+
describe('notifyNodeChanged', () => {
|
|
564
|
+
it('should mark node as stale', async () => {
|
|
565
|
+
const node = { uid: 'volumeId~nodeId', isStale: false } as DecryptedNode;
|
|
566
|
+
cache.getNode = jest.fn(() => Promise.resolve(node));
|
|
567
|
+
cache.setNode = jest.fn();
|
|
568
|
+
await access.notifyNodeChanged(node.uid);
|
|
569
|
+
expect(cache.getNode).toHaveBeenCalledWith(node.uid);
|
|
570
|
+
expect(cache.setNode).toHaveBeenCalledWith({...node, isStale: true});
|
|
571
|
+
});
|
|
572
|
+
it('should update parent if needed', async () => {
|
|
573
|
+
const node = { uid: 'volumeId~nodeId', parentUid: 'v1~pn1', isStale: false } as DecryptedNode;
|
|
574
|
+
cache.getNode = jest.fn(() => Promise.resolve(node));
|
|
575
|
+
cache.setNode = jest.fn();
|
|
576
|
+
await access.notifyNodeChanged(node.uid, 'v1~pn2');
|
|
577
|
+
expect(cache.getNode).toHaveBeenCalledWith(node.uid);
|
|
578
|
+
expect(cache.setNode).toHaveBeenCalledWith({...node, parentUid: 'v1~pn2', isStale: true});
|
|
579
|
+
});
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
describe('notifyChildCreated', () => {
|
|
583
|
+
it('should reset parent listing', async () => {
|
|
584
|
+
const nodeUid = 'VolumeId1~NodeId1';
|
|
585
|
+
cache.resetFolderChildrenLoaded = jest.fn();
|
|
586
|
+
await access.notifyChildCreated(nodeUid);
|
|
587
|
+
expect(cache.resetFolderChildrenLoaded).toHaveBeenCalledWith(nodeUid);
|
|
588
|
+
});
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
describe('notifyNodeDeleted', () => {
|
|
592
|
+
it('should reset parent listing', async () => {
|
|
593
|
+
await access.notifyNodeDeleted('v1~n1');
|
|
594
|
+
expect(cache.removeNodes).toHaveBeenCalledWith(['v1~n1']);
|
|
595
|
+
});
|
|
596
|
+
});
|
|
555
597
|
});
|
|
@@ -3,6 +3,7 @@ import { c } from 'ttag';
|
|
|
3
3
|
import { PrivateKey, SessionKey } from "../../crypto";
|
|
4
4
|
import { InvalidNameError, Logger, MissingNode, NodeType, Result, resultError, resultOk } from "../../interface";
|
|
5
5
|
import { DecryptionError, ProtonDriveError } from "../../errors";
|
|
6
|
+
import { asyncIteratorMap } from '../asyncIteratorMap';
|
|
6
7
|
import { getErrorMessage } from '../errors';
|
|
7
8
|
import { BatchLoading } from "../batchLoading";
|
|
8
9
|
import { makeNodeUid, splitNodeUid } from "../uids";
|
|
@@ -15,9 +16,19 @@ import { SharesService, EncryptedNode, DecryptedUnparsedNode, DecryptedNode, Dec
|
|
|
15
16
|
import { validateNodeName } from "./validations";
|
|
16
17
|
import { isProtonDocument, isProtonSheet } from './mediaTypes';
|
|
17
18
|
|
|
19
|
+
// This is the number of nodes that are loaded in parallel.
|
|
20
|
+
// It is a trade-off between initial wait time and overhead of API calls.
|
|
21
|
+
const BATCH_LOADING_SIZE = 30;
|
|
22
|
+
|
|
23
|
+
// This is the number of nodes that are decrypted in parallel.
|
|
24
|
+
// It is a trade-off between performance and memory usage.
|
|
25
|
+
// Higher number means more memory usage, but faster decryption.
|
|
26
|
+
// Lower number means less memory usage, but slower decryption.
|
|
27
|
+
const DECRYPTION_CONCURRENCY = 15;
|
|
28
|
+
|
|
18
29
|
/**
|
|
19
30
|
* Provides access to node metadata.
|
|
20
|
-
*
|
|
31
|
+
*
|
|
21
32
|
* The node access module is responsible for fetching, decrypting and caching
|
|
22
33
|
* nodes metadata.
|
|
23
34
|
*/
|
|
@@ -64,7 +75,7 @@ export class NodesAccess {
|
|
|
64
75
|
// Ensure the parent is loaded and up-to-date.
|
|
65
76
|
const parentNode = await this.getNode(parentNodeUid);
|
|
66
77
|
|
|
67
|
-
const batchLoading = new BatchLoading<string, DecryptedNode>({ iterateItems: (nodeUids) => this.loadNodes(nodeUids, signal) });
|
|
78
|
+
const batchLoading = new BatchLoading<string, DecryptedNode>({ iterateItems: (nodeUids) => this.loadNodes(nodeUids, signal), batchSize: BATCH_LOADING_SIZE });
|
|
68
79
|
|
|
69
80
|
const areChildrenCached = await this.cache.isFolderChildrenLoaded(parentNodeUid);
|
|
70
81
|
if (areChildrenCached) {
|
|
@@ -100,7 +111,7 @@ export class NodesAccess {
|
|
|
100
111
|
// Improvement requested: keep status of loaded trash and leverage cache.
|
|
101
112
|
async *iterateTrashedNodes(signal?: AbortSignal): AsyncGenerator<DecryptedNode> {
|
|
102
113
|
const { volumeId } = await this.shareService.getMyFilesIDs();
|
|
103
|
-
const batchLoading = new BatchLoading<string, DecryptedNode>({ iterateItems: (nodeUids) => this.loadNodes(nodeUids, signal) });
|
|
114
|
+
const batchLoading = new BatchLoading<string, DecryptedNode>({ iterateItems: (nodeUids) => this.loadNodes(nodeUids, signal), batchSize: BATCH_LOADING_SIZE });
|
|
104
115
|
for await (const nodeUid of this.apiService.iterateTrashedNodeUids(volumeId, signal)) {
|
|
105
116
|
let node;
|
|
106
117
|
try {
|
|
@@ -118,7 +129,7 @@ export class NodesAccess {
|
|
|
118
129
|
}
|
|
119
130
|
|
|
120
131
|
async *iterateNodes(nodeUids: string[], signal?: AbortSignal): AsyncGenerator<DecryptedNode | MissingNode> {
|
|
121
|
-
const batchLoading = new BatchLoading<string, DecryptedNode | MissingNode>({ iterateItems: (nodeUids) => this.loadNodesWithMissingReport(nodeUids, signal) });
|
|
132
|
+
const batchLoading = new BatchLoading<string, DecryptedNode | MissingNode>({ iterateItems: (nodeUids) => this.loadNodesWithMissingReport(nodeUids, signal), batchSize: BATCH_LOADING_SIZE });
|
|
122
133
|
for await (const result of this.cache.iterateNodes(nodeUids)) {
|
|
123
134
|
if (result.ok && !result.node.isStale) {
|
|
124
135
|
yield result.node;
|
|
@@ -129,8 +140,45 @@ export class NodesAccess {
|
|
|
129
140
|
yield* batchLoading.loadRest();
|
|
130
141
|
}
|
|
131
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Call to invalidate the folder listing cache. This should be refactored into a clean
|
|
145
|
+
* cache layer once the cache is split off.
|
|
146
|
+
*/
|
|
147
|
+
async notifyChildCreated(nodeUid: string): Promise<void> {
|
|
148
|
+
await this.cache.resetFolderChildrenLoaded(nodeUid);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Call to invalidate the node cache when a node changes. Parent can be set after a move
|
|
153
|
+
* to ensure parent listing of new parent is up to date if cached.
|
|
154
|
+
* This should be refactored into a clean cache layer once the cache is split off.
|
|
155
|
+
*/
|
|
156
|
+
async notifyNodeChanged(nodeUid: string, newParentUid?: string): Promise<void> {
|
|
157
|
+
try {
|
|
158
|
+
const node = await this.cache.getNode(nodeUid);
|
|
159
|
+
if (node.isStale && newParentUid === null) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
node.isStale = true;
|
|
163
|
+
if (newParentUid) {
|
|
164
|
+
node.parentUid = newParentUid;
|
|
165
|
+
}
|
|
166
|
+
await this.cache.setNode(node);
|
|
167
|
+
} catch (error: unknown) {
|
|
168
|
+
this.logger.warn(`Failed to set node ${nodeUid} as stale after sharing: ${error}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Call to remove a node from cache. This should be refactored when the cache is split off.
|
|
174
|
+
*/
|
|
175
|
+
async notifyNodeDeleted(nodeUid: string): Promise<void> {
|
|
176
|
+
await this.cache.removeNodes([nodeUid]);
|
|
177
|
+
}
|
|
178
|
+
|
|
132
179
|
private async loadNode(nodeUid: string): Promise<{ node: DecryptedNode, keys?: DecryptedNodeKeys }> {
|
|
133
|
-
const
|
|
180
|
+
const { volumeId: ownVolumeId } = await this.shareService.getMyFilesIDs();
|
|
181
|
+
const encryptedNode = await this.apiService.getNode(nodeUid, ownVolumeId);
|
|
134
182
|
return this.decryptNode(encryptedNode);
|
|
135
183
|
}
|
|
136
184
|
|
|
@@ -147,13 +195,24 @@ export class NodesAccess {
|
|
|
147
195
|
const returnedNodeUids: string[] = [];
|
|
148
196
|
const errors = [];
|
|
149
197
|
|
|
150
|
-
|
|
198
|
+
const { volumeId: ownVolumeId } = await this.shareService.getMyFilesIDs();
|
|
199
|
+
|
|
200
|
+
const encryptedNodesIterator = this.apiService.iterateNodes(nodeUids, ownVolumeId, signal);
|
|
201
|
+
const decryptNodeMapper = async (encryptedNode: EncryptedNode): Promise<Result<DecryptedNode, unknown>> => {
|
|
151
202
|
returnedNodeUids.push(encryptedNode.uid);
|
|
152
203
|
try {
|
|
153
204
|
const { node } = await this.decryptNode(encryptedNode);
|
|
154
|
-
|
|
205
|
+
return resultOk(node);
|
|
155
206
|
} catch (error: unknown) {
|
|
156
|
-
|
|
207
|
+
return resultError(error);
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
const decryptedNodesIterator = asyncIteratorMap(encryptedNodesIterator, decryptNodeMapper, DECRYPTION_CONCURRENCY);
|
|
211
|
+
for await (const node of decryptedNodesIterator) {
|
|
212
|
+
if (node.ok) {
|
|
213
|
+
yield node.value;
|
|
214
|
+
} else {
|
|
215
|
+
errors.push(node.error);
|
|
157
216
|
}
|
|
158
217
|
}
|
|
159
218
|
|
|
@@ -194,6 +253,7 @@ export class NodesAccess {
|
|
|
194
253
|
error: getErrorMessage(error),
|
|
195
254
|
}),
|
|
196
255
|
errors: [error],
|
|
256
|
+
treeEventScopeId: splitNodeUid(encryptedNode.uid).volumeId,
|
|
197
257
|
},
|
|
198
258
|
};
|
|
199
259
|
}
|
|
@@ -249,6 +309,7 @@ export class NodesAccess {
|
|
|
249
309
|
...extendedAttributes,
|
|
250
310
|
}),
|
|
251
311
|
folder: undefined,
|
|
312
|
+
treeEventScopeId: splitNodeUid(unparsedNode.uid).volumeId,
|
|
252
313
|
}
|
|
253
314
|
}
|
|
254
315
|
|
|
@@ -263,6 +324,7 @@ export class NodesAccess {
|
|
|
263
324
|
folder: extendedAttributes ? {
|
|
264
325
|
...extendedAttributes,
|
|
265
326
|
} : undefined,
|
|
327
|
+
treeEventScopeId: splitNodeUid(unparsedNode.uid).volumeId,
|
|
266
328
|
}
|
|
267
329
|
}
|
|
268
330
|
|