@protontech/drive-sdk 0.3.2 → 0.4.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/interface/nodes.d.ts +4 -0
- package/dist/interface/nodes.js.map +1 -1
- package/dist/internal/apiService/errorCodes.d.ts +1 -0
- package/dist/internal/apiService/errors.d.ts +3 -0
- package/dist/internal/apiService/errors.js +7 -1
- package/dist/internal/apiService/errors.js.map +1 -1
- package/dist/internal/devices/interface.d.ts +1 -1
- package/dist/internal/devices/manager.js +1 -1
- package/dist/internal/devices/manager.js.map +1 -1
- package/dist/internal/devices/manager.test.js +3 -3
- package/dist/internal/devices/manager.test.js.map +1 -1
- package/dist/internal/events/apiService.js +1 -1
- package/dist/internal/events/apiService.js.map +1 -1
- package/dist/internal/events/coreEventManager.js +1 -1
- package/dist/internal/events/coreEventManager.js.map +1 -1
- package/dist/internal/events/coreEventManager.test.js +18 -24
- package/dist/internal/events/coreEventManager.test.js.map +1 -1
- package/dist/internal/events/index.d.ts +3 -4
- package/dist/internal/events/index.js +4 -4
- package/dist/internal/events/index.js.map +1 -1
- package/dist/internal/events/interface.d.ts +3 -0
- package/dist/internal/nodes/apiService.d.ts +12 -3
- package/dist/internal/nodes/apiService.js +54 -13
- package/dist/internal/nodes/apiService.js.map +1 -1
- package/dist/internal/nodes/apiService.test.js +35 -2
- package/dist/internal/nodes/apiService.test.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 +1 -1
- package/dist/internal/nodes/cryptoService.js.map +1 -1
- package/dist/internal/nodes/cryptoService.test.js +4 -4
- package/dist/internal/nodes/cryptoService.test.js.map +1 -1
- package/dist/internal/nodes/errors.d.ts +4 -0
- package/dist/internal/nodes/errors.js +9 -0
- package/dist/internal/nodes/errors.js.map +1 -0
- package/dist/internal/nodes/extendedAttributes.d.ts +2 -2
- package/dist/internal/nodes/extendedAttributes.js +15 -11
- package/dist/internal/nodes/extendedAttributes.js.map +1 -1
- package/dist/internal/nodes/extendedAttributes.test.js +19 -1
- package/dist/internal/nodes/extendedAttributes.test.js.map +1 -1
- package/dist/internal/nodes/index.test.js +2 -1
- package/dist/internal/nodes/index.test.js.map +1 -1
- package/dist/internal/nodes/interface.d.ts +5 -1
- package/dist/internal/nodes/nodesAccess.d.ts +3 -3
- package/dist/internal/nodes/nodesAccess.js +25 -15
- package/dist/internal/nodes/nodesAccess.js.map +1 -1
- package/dist/internal/nodes/nodesAccess.test.js +48 -8
- package/dist/internal/nodes/nodesAccess.test.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.d.ts +2 -0
- package/dist/internal/nodes/nodesManagement.js +87 -9
- package/dist/internal/nodes/nodesManagement.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.test.js +81 -5
- package/dist/internal/nodes/nodesManagement.test.js.map +1 -1
- package/dist/internal/photos/albums.d.ts +9 -7
- package/dist/internal/photos/albums.js +26 -13
- package/dist/internal/photos/albums.js.map +1 -1
- package/dist/internal/photos/apiService.d.ts +34 -3
- package/dist/internal/photos/apiService.js +96 -3
- package/dist/internal/photos/apiService.js.map +1 -1
- package/dist/internal/photos/index.d.ts +31 -4
- package/dist/internal/photos/index.js +57 -7
- package/dist/internal/photos/index.js.map +1 -1
- package/dist/internal/photos/interface.d.ts +25 -1
- package/dist/internal/photos/shares.d.ts +43 -0
- package/dist/internal/photos/shares.js +112 -0
- package/dist/internal/photos/shares.js.map +1 -0
- package/dist/internal/photos/timeline.d.ts +15 -0
- package/dist/internal/photos/timeline.js +22 -0
- package/dist/internal/photos/timeline.js.map +1 -0
- package/dist/internal/photos/upload.d.ts +59 -0
- package/dist/internal/photos/upload.js +104 -0
- package/dist/internal/photos/upload.js.map +1 -0
- package/dist/internal/shares/manager.d.ts +1 -1
- package/dist/internal/shares/manager.js +4 -4
- package/dist/internal/shares/manager.js.map +1 -1
- package/dist/internal/shares/manager.test.js +7 -7
- package/dist/internal/shares/manager.test.js.map +1 -1
- package/dist/internal/sharing/interface.d.ts +1 -1
- package/dist/internal/sharing/sharingAccess.js +1 -1
- package/dist/internal/sharing/sharingAccess.js.map +1 -1
- package/dist/internal/sharing/sharingAccess.test.js +1 -1
- package/dist/internal/sharing/sharingAccess.test.js.map +1 -1
- package/dist/internal/sharingPublic/apiService.js +2 -0
- package/dist/internal/sharingPublic/apiService.js.map +1 -1
- package/dist/internal/upload/apiService.d.ts +2 -2
- package/dist/internal/upload/apiService.js +1 -1
- package/dist/internal/upload/apiService.js.map +1 -1
- package/dist/internal/upload/cryptoService.d.ts +2 -2
- package/dist/internal/upload/cryptoService.js.map +1 -1
- package/dist/internal/upload/fileUploader.d.ts +1 -0
- package/dist/internal/upload/fileUploader.js +3 -0
- package/dist/internal/upload/fileUploader.js.map +1 -1
- package/dist/internal/upload/interface.d.ts +3 -0
- package/dist/internal/upload/manager.d.ts +12 -11
- package/dist/internal/upload/manager.js +8 -2
- package/dist/internal/upload/manager.js.map +1 -1
- package/dist/internal/upload/manager.test.js +8 -0
- package/dist/internal/upload/manager.test.js.map +1 -1
- package/dist/internal/upload/streamUploader.d.ts +34 -24
- package/dist/internal/upload/streamUploader.js +7 -4
- package/dist/internal/upload/streamUploader.js.map +1 -1
- package/dist/internal/upload/streamUploader.test.js +1 -1
- package/dist/internal/upload/streamUploader.test.js.map +1 -1
- package/dist/protonDriveClient.d.ts +20 -3
- package/dist/protonDriveClient.js +23 -4
- package/dist/protonDriveClient.js.map +1 -1
- package/dist/protonDrivePhotosClient.d.ts +102 -12
- package/dist/protonDrivePhotosClient.js +149 -29
- package/dist/protonDrivePhotosClient.js.map +1 -1
- 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/interface/nodes.ts +4 -0
- package/src/internal/apiService/errorCodes.ts +1 -0
- package/src/internal/apiService/errors.ts +6 -0
- package/src/internal/devices/interface.ts +1 -1
- package/src/internal/devices/manager.test.ts +3 -3
- package/src/internal/devices/manager.ts +1 -1
- package/src/internal/events/apiService.ts +1 -1
- package/src/internal/events/coreEventManager.test.ts +21 -27
- package/src/internal/events/coreEventManager.ts +1 -1
- package/src/internal/events/index.ts +3 -4
- package/src/internal/events/interface.ts +4 -0
- package/src/internal/nodes/apiService.test.ts +58 -1
- package/src/internal/nodes/apiService.ts +104 -17
- package/src/internal/nodes/cache.test.ts +1 -0
- package/src/internal/nodes/cryptoService.test.ts +8 -8
- package/src/internal/nodes/cryptoService.ts +1 -1
- package/src/internal/nodes/errors.ts +5 -0
- package/src/internal/nodes/extendedAttributes.test.ts +23 -1
- package/src/internal/nodes/extendedAttributes.ts +26 -18
- package/src/internal/nodes/index.test.ts +2 -1
- package/src/internal/nodes/interface.ts +6 -1
- package/src/internal/nodes/nodesAccess.test.ts +68 -8
- package/src/internal/nodes/nodesAccess.ts +42 -15
- package/src/internal/nodes/nodesManagement.test.ts +100 -5
- package/src/internal/nodes/nodesManagement.ts +101 -13
- package/src/internal/photos/albums.ts +31 -12
- package/src/internal/photos/apiService.ts +159 -4
- package/src/internal/photos/index.ts +116 -9
- package/src/internal/photos/interface.ts +23 -1
- package/src/internal/photos/shares.ts +134 -0
- package/src/internal/photos/timeline.ts +24 -0
- package/src/internal/photos/upload.ts +209 -0
- package/src/internal/shares/manager.test.ts +7 -7
- package/src/internal/shares/manager.ts +4 -4
- package/src/internal/sharing/interface.ts +1 -1
- package/src/internal/sharing/sharingAccess.test.ts +1 -1
- package/src/internal/sharing/sharingAccess.ts +1 -1
- package/src/internal/sharingPublic/apiService.ts +2 -0
- package/src/internal/upload/apiService.ts +3 -3
- package/src/internal/upload/cryptoService.ts +2 -2
- package/src/internal/upload/fileUploader.ts +12 -0
- package/src/internal/upload/interface.ts +3 -0
- package/src/internal/upload/manager.test.ts +8 -0
- package/src/internal/upload/manager.ts +20 -10
- package/src/internal/upload/streamUploader.test.ts +17 -12
- package/src/internal/upload/streamUploader.ts +35 -27
- package/src/protonDriveClient.ts +33 -4
- package/src/protonDrivePhotosClient.ts +251 -32
- package/src/transformers.ts +2 -0
- package/dist/internal/photos/cache.d.ts +0 -6
- package/dist/internal/photos/cache.js +0 -15
- package/dist/internal/photos/cache.js.map +0 -1
- package/dist/internal/photos/photosTimeline.d.ts +0 -10
- package/dist/internal/photos/photosTimeline.js +0 -19
- package/dist/internal/photos/photosTimeline.js.map +0 -1
- package/src/internal/photos/cache.ts +0 -11
- package/src/internal/photos/photosTimeline.ts +0 -17
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { DriveCrypto } from '../../crypto';
|
|
2
|
+
import { ProtonDriveTelemetry, UploadMetadata, Thumbnail } from '../../interface';
|
|
3
|
+
import { DriveAPIService, drivePaths } from '../apiService';
|
|
4
|
+
import { generateFileExtendedAttributes } from '../nodes';
|
|
5
|
+
import { splitNodeRevisionUid } from '../uids';
|
|
6
|
+
import { UploadAPIService } from '../upload/apiService';
|
|
7
|
+
import { BlockVerifier } from '../upload/blockVerifier';
|
|
8
|
+
import { UploadCryptoService } from '../upload/cryptoService';
|
|
9
|
+
import { FileUploader } from '../upload/fileUploader';
|
|
10
|
+
import { NodeRevisionDraft, NodesService } from '../upload/interface';
|
|
11
|
+
import { UploadManager } from '../upload/manager';
|
|
12
|
+
import { StreamUploader } from '../upload/streamUploader';
|
|
13
|
+
import { UploadTelemetry } from '../upload/telemetry';
|
|
14
|
+
|
|
15
|
+
type PostCommitRevisionRequest = Extract<
|
|
16
|
+
drivePaths['/drive/v2/volumes/{volumeID}/files/{linkID}/revisions/{revisionID}']['put']['requestBody'],
|
|
17
|
+
{ content: object }
|
|
18
|
+
>['content']['application/json'];
|
|
19
|
+
type PostCommitRevisionResponse =
|
|
20
|
+
drivePaths['/drive/v2/volumes/{volumeID}/files/{linkID}/revisions/{revisionID}']['put']['responses']['200']['content']['application/json'];
|
|
21
|
+
|
|
22
|
+
export type PhotoUploadMetadata = UploadMetadata & {
|
|
23
|
+
captureTime?: Date;
|
|
24
|
+
mainPhotoLinkID?: string;
|
|
25
|
+
// TODO: handle tags enum in the SDK
|
|
26
|
+
tags?: (0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9)[];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export class PhotoFileUploader extends FileUploader {
|
|
30
|
+
private photoApiService: PhotoUploadAPIService;
|
|
31
|
+
private photoManager: PhotoUploadManager;
|
|
32
|
+
private photoMetadata: PhotoUploadMetadata;
|
|
33
|
+
|
|
34
|
+
constructor(
|
|
35
|
+
telemetry: UploadTelemetry,
|
|
36
|
+
apiService: PhotoUploadAPIService,
|
|
37
|
+
cryptoService: UploadCryptoService,
|
|
38
|
+
manager: PhotoUploadManager,
|
|
39
|
+
parentFolderUid: string,
|
|
40
|
+
name: string,
|
|
41
|
+
metadata: PhotoUploadMetadata,
|
|
42
|
+
onFinish: () => void,
|
|
43
|
+
signal?: AbortSignal,
|
|
44
|
+
) {
|
|
45
|
+
super(telemetry, apiService, cryptoService, manager, parentFolderUid, name, metadata, onFinish, signal);
|
|
46
|
+
this.photoApiService = apiService;
|
|
47
|
+
this.photoManager = manager;
|
|
48
|
+
this.photoMetadata = metadata;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
protected async newStreamUploader(
|
|
52
|
+
blockVerifier: BlockVerifier,
|
|
53
|
+
revisionDraft: NodeRevisionDraft,
|
|
54
|
+
onFinish: (failure: boolean) => Promise<void>,
|
|
55
|
+
): Promise<StreamUploader> {
|
|
56
|
+
return new PhotoStreamUploader(
|
|
57
|
+
this.telemetry,
|
|
58
|
+
this.photoApiService,
|
|
59
|
+
this.cryptoService,
|
|
60
|
+
this.photoManager,
|
|
61
|
+
blockVerifier,
|
|
62
|
+
revisionDraft,
|
|
63
|
+
this.photoMetadata,
|
|
64
|
+
onFinish,
|
|
65
|
+
this.signal,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class PhotoStreamUploader extends StreamUploader {
|
|
71
|
+
private photoUploadManager: PhotoUploadManager;
|
|
72
|
+
private photoMetadata: PhotoUploadMetadata;
|
|
73
|
+
|
|
74
|
+
constructor(
|
|
75
|
+
telemetry: UploadTelemetry,
|
|
76
|
+
apiService: PhotoUploadAPIService,
|
|
77
|
+
cryptoService: UploadCryptoService,
|
|
78
|
+
uploadManager: PhotoUploadManager,
|
|
79
|
+
blockVerifier: BlockVerifier,
|
|
80
|
+
revisionDraft: NodeRevisionDraft,
|
|
81
|
+
metadata: PhotoUploadMetadata,
|
|
82
|
+
onFinish: (failure: boolean) => Promise<void>,
|
|
83
|
+
signal?: AbortSignal,
|
|
84
|
+
) {
|
|
85
|
+
super(telemetry, apiService, cryptoService, uploadManager, blockVerifier, revisionDraft, metadata, onFinish, signal);
|
|
86
|
+
this.photoUploadManager = uploadManager;
|
|
87
|
+
this.photoMetadata = metadata;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async commitFile(thumbnails: Thumbnail[]) {
|
|
91
|
+
this.verifyIntegrity(thumbnails);
|
|
92
|
+
|
|
93
|
+
const extendedAttributes = {
|
|
94
|
+
modificationTime: this.metadata.modificationTime,
|
|
95
|
+
size: this.metadata.expectedSize,
|
|
96
|
+
blockSizes: this.uploadedBlockSizes,
|
|
97
|
+
digests: this.digests.digests(),
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
await this.photoUploadManager.commitDraftPhoto(this.revisionDraft, this.manifest, extendedAttributes, this.photoMetadata);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export class PhotoUploadManager extends UploadManager {
|
|
105
|
+
private photoApiService: PhotoUploadAPIService;
|
|
106
|
+
private photoCryptoService: PhotoUploadCryptoService;
|
|
107
|
+
|
|
108
|
+
constructor(
|
|
109
|
+
telemetry: ProtonDriveTelemetry,
|
|
110
|
+
apiService: PhotoUploadAPIService,
|
|
111
|
+
cryptoService: PhotoUploadCryptoService,
|
|
112
|
+
nodesService: NodesService,
|
|
113
|
+
clientUid: string | undefined,
|
|
114
|
+
) {
|
|
115
|
+
super(telemetry, apiService, cryptoService, nodesService, clientUid);
|
|
116
|
+
this.photoApiService = apiService;
|
|
117
|
+
this.photoCryptoService = cryptoService;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async commitDraftPhoto(
|
|
121
|
+
nodeRevisionDraft: NodeRevisionDraft,
|
|
122
|
+
manifest: Uint8Array,
|
|
123
|
+
extendedAttributes: {
|
|
124
|
+
modificationTime?: Date;
|
|
125
|
+
size: number;
|
|
126
|
+
blockSizes: number[];
|
|
127
|
+
digests: {
|
|
128
|
+
sha1: string;
|
|
129
|
+
};
|
|
130
|
+
},
|
|
131
|
+
uploadMetadata: PhotoUploadMetadata,
|
|
132
|
+
): Promise<void> {
|
|
133
|
+
if (!nodeRevisionDraft.parentNodeKeys) {
|
|
134
|
+
throw new Error('Parent node keys are required for photo upload');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// TODO: handle photo extended attributes in the SDK - now it must be passed from the client
|
|
138
|
+
const generatedExtendedAttributes = generateFileExtendedAttributes(extendedAttributes, uploadMetadata.additionalMetadata);
|
|
139
|
+
const nodeCommitCrypto = await this.cryptoService.commitFile(
|
|
140
|
+
nodeRevisionDraft.nodeKeys,
|
|
141
|
+
manifest,
|
|
142
|
+
generatedExtendedAttributes,
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const sha1 = extendedAttributes.digests.sha1;
|
|
146
|
+
const contentHash = await this.photoCryptoService.generateContentHash(sha1, nodeRevisionDraft.parentNodeKeys?.hashKey);
|
|
147
|
+
const photo = {
|
|
148
|
+
contentHash,
|
|
149
|
+
captureTime: uploadMetadata.captureTime || extendedAttributes.modificationTime,
|
|
150
|
+
mainPhotoLinkID: uploadMetadata.mainPhotoLinkID,
|
|
151
|
+
tags: uploadMetadata.tags,
|
|
152
|
+
}
|
|
153
|
+
await this.photoApiService.commitDraftPhoto(nodeRevisionDraft.nodeRevisionUid, nodeCommitCrypto, photo);
|
|
154
|
+
await this.notifyNodeUploaded(nodeRevisionDraft);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export class PhotoUploadCryptoService extends UploadCryptoService {
|
|
159
|
+
constructor(
|
|
160
|
+
driveCrypto: DriveCrypto,
|
|
161
|
+
nodesService: NodesService,
|
|
162
|
+
) {
|
|
163
|
+
super(driveCrypto, nodesService);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async generateContentHash(sha1: string, parentHashKey: Uint8Array): Promise<string> {
|
|
167
|
+
return this.driveCrypto.generateLookupHash(sha1, parentHashKey);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export class PhotoUploadAPIService extends UploadAPIService {
|
|
172
|
+
constructor(apiService: DriveAPIService, clientUid: string | undefined) {
|
|
173
|
+
super(apiService, clientUid);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async commitDraftPhoto(
|
|
177
|
+
draftNodeRevisionUid: string,
|
|
178
|
+
options: {
|
|
179
|
+
armoredManifestSignature: string;
|
|
180
|
+
signatureEmail: string;
|
|
181
|
+
armoredExtendedAttributes?: string;
|
|
182
|
+
},
|
|
183
|
+
photo: {
|
|
184
|
+
contentHash: string;
|
|
185
|
+
captureTime?: Date;
|
|
186
|
+
mainPhotoLinkID?: string;
|
|
187
|
+
// TODO: handle tags enum in the SDK
|
|
188
|
+
tags?: (0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9)[];
|
|
189
|
+
},
|
|
190
|
+
): Promise<void> {
|
|
191
|
+
const { volumeId, nodeId, revisionId } = splitNodeRevisionUid(draftNodeRevisionUid);
|
|
192
|
+
await this.apiService.put<
|
|
193
|
+
// TODO: Deprected fields but not properly marked in the types.
|
|
194
|
+
Omit<PostCommitRevisionRequest, 'BlockNumber' | 'BlockList' | 'ThumbnailToken' | 'State'>,
|
|
195
|
+
PostCommitRevisionResponse
|
|
196
|
+
>(`drive/v2/volumes/${volumeId}/files/${nodeId}/revisions/${revisionId}`, {
|
|
197
|
+
ManifestSignature: options.armoredManifestSignature,
|
|
198
|
+
SignatureAddress: options.signatureEmail,
|
|
199
|
+
XAttr: options.armoredExtendedAttributes || null,
|
|
200
|
+
Photo: {
|
|
201
|
+
ContentHash: photo.contentHash,
|
|
202
|
+
CaptureTime: photo.captureTime?.getTime() || 0,
|
|
203
|
+
MainPhotoLinkID: photo.mainPhotoLinkID || null,
|
|
204
|
+
Tags: photo.tags || [],
|
|
205
|
+
Exif: null, // Deprecated field, not used.
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -50,7 +50,7 @@ describe('SharesManager', () => {
|
|
|
50
50
|
manager = new SharesManager(getMockLogger(), apiService, cache, cryptoCache, cryptoService, account);
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
-
describe('
|
|
53
|
+
describe('getOwnVolumeIDs', () => {
|
|
54
54
|
const myFilesShare = {
|
|
55
55
|
shareId: 'myFilesShareId',
|
|
56
56
|
volumeId: 'myFilesVolumeId',
|
|
@@ -71,8 +71,8 @@ describe('SharesManager', () => {
|
|
|
71
71
|
cryptoService.decryptRootShare = jest.fn().mockResolvedValue({ share: myFilesShare, key });
|
|
72
72
|
|
|
73
73
|
// Calling twice to check if it loads only once.
|
|
74
|
-
await manager.
|
|
75
|
-
const result = await manager.
|
|
74
|
+
await manager.getOwnVolumeIDs();
|
|
75
|
+
const result = await manager.getOwnVolumeIDs();
|
|
76
76
|
|
|
77
77
|
expect(result).toStrictEqual(myFilesShare);
|
|
78
78
|
expect(apiService.getMyFiles).toHaveBeenCalledTimes(1);
|
|
@@ -103,7 +103,7 @@ describe('SharesManager', () => {
|
|
|
103
103
|
});
|
|
104
104
|
apiService.createVolume = jest.fn().mockResolvedValue(myFilesShare);
|
|
105
105
|
|
|
106
|
-
const result = await manager.
|
|
106
|
+
const result = await manager.getOwnVolumeIDs();
|
|
107
107
|
|
|
108
108
|
expect(result).toStrictEqual(myFilesShare);
|
|
109
109
|
expect(cryptoService.decryptRootShare).not.toHaveBeenCalled();
|
|
@@ -113,7 +113,7 @@ describe('SharesManager', () => {
|
|
|
113
113
|
it('should throw on unknown error', async () => {
|
|
114
114
|
apiService.getMyFiles = jest.fn().mockRejectedValue(new Error('Some error'));
|
|
115
115
|
|
|
116
|
-
await expect(manager.
|
|
116
|
+
await expect(manager.getOwnVolumeIDs()).rejects.toThrow('Some error');
|
|
117
117
|
expect(cryptoService.decryptRootShare).not.toHaveBeenCalled();
|
|
118
118
|
expect(apiService.createVolume).not.toHaveBeenCalled();
|
|
119
119
|
});
|
|
@@ -142,7 +142,7 @@ describe('SharesManager', () => {
|
|
|
142
142
|
|
|
143
143
|
describe('getMyFilesShareMemberEmailKey', () => {
|
|
144
144
|
it('should return cached volume email key', async () => {
|
|
145
|
-
jest.spyOn(manager, '
|
|
145
|
+
jest.spyOn(manager, 'getOwnVolumeIDs').mockResolvedValue({ volumeId: 'volumeId' } as VolumeShareNodeIDs);
|
|
146
146
|
cache.getVolume = jest.fn().mockResolvedValue({ addressId: 'addressId' });
|
|
147
147
|
account.getOwnAddress = jest
|
|
148
148
|
.fn()
|
|
@@ -158,7 +158,7 @@ describe('SharesManager', () => {
|
|
|
158
158
|
});
|
|
159
159
|
|
|
160
160
|
it('should load volume email key if not in cache', async () => {
|
|
161
|
-
jest.spyOn(manager, '
|
|
161
|
+
jest.spyOn(manager, 'getOwnVolumeIDs').mockResolvedValue({ volumeId: 'volumeId' } as VolumeShareNodeIDs);
|
|
162
162
|
const share = {
|
|
163
163
|
volumeId: 'volumeId',
|
|
164
164
|
shareId: 'shareId',
|
|
@@ -46,7 +46,7 @@ export class SharesManager {
|
|
|
46
46
|
*
|
|
47
47
|
* If the default volume or My files section doesn't exist, it creates it.
|
|
48
48
|
*/
|
|
49
|
-
async
|
|
49
|
+
async getOwnVolumeIDs(): Promise<VolumeShareNodeIDs> {
|
|
50
50
|
if (this.myFilesIds) {
|
|
51
51
|
return this.myFilesIds;
|
|
52
52
|
}
|
|
@@ -140,7 +140,7 @@ export class SharesManager {
|
|
|
140
140
|
addressKey: PrivateKey;
|
|
141
141
|
addressKeyId: string;
|
|
142
142
|
}> {
|
|
143
|
-
const { volumeId } = await this.
|
|
143
|
+
const { volumeId } = await this.getOwnVolumeIDs();
|
|
144
144
|
|
|
145
145
|
try {
|
|
146
146
|
const { addressId } = await this.cache.getVolume(volumeId);
|
|
@@ -196,11 +196,11 @@ export class SharesManager {
|
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
async isOwnVolume(volumeId: string): Promise<boolean> {
|
|
199
|
-
return (await this.
|
|
199
|
+
return (await this.getOwnVolumeIDs()).volumeId === volumeId;
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
async getVolumeMetricContext(volumeId: string): Promise<MetricVolumeType> {
|
|
203
|
-
const { volumeId: myVolumeId } = await this.
|
|
203
|
+
const { volumeId: myVolumeId } = await this.getOwnVolumeIDs();
|
|
204
204
|
|
|
205
205
|
// SDK doesn't support public sharing yet, also public sharing
|
|
206
206
|
// doesn't use a volume but shareURL, thus we can simplify and
|
|
@@ -142,7 +142,7 @@ export interface PublicLinkWithCreatorEmail extends PublicLink {
|
|
|
142
142
|
* Interface describing the dependencies to the shares module.
|
|
143
143
|
*/
|
|
144
144
|
export interface SharesService {
|
|
145
|
-
|
|
145
|
+
getOwnVolumeIDs(): Promise<{ volumeId: string }>;
|
|
146
146
|
loadEncryptedShare(shareId: string): Promise<EncryptedShare>;
|
|
147
147
|
getMyFilesShareMemberEmailKey(): Promise<{
|
|
148
148
|
email: string;
|
|
@@ -93,7 +93,7 @@ describe('SharingAccess', () => {
|
|
|
93
93
|
|
|
94
94
|
// @ts-expect-error No need to implement all methods for mocking
|
|
95
95
|
sharesService = {
|
|
96
|
-
|
|
96
|
+
getOwnVolumeIDs: jest.fn().mockResolvedValue({ volumeId: 'volumeId' }),
|
|
97
97
|
loadEncryptedShare: jest.fn().mockResolvedValue({
|
|
98
98
|
id: 'shareId',
|
|
99
99
|
membership: { memberUid: 'memberUid' },
|
|
@@ -40,7 +40,7 @@ export class SharingAccess {
|
|
|
40
40
|
const nodeUids = await this.cache.getSharedByMeNodeUids();
|
|
41
41
|
yield* this.iterateSharedNodesFromCache(nodeUids, signal);
|
|
42
42
|
} catch {
|
|
43
|
-
const { volumeId } = await this.sharesService.
|
|
43
|
+
const { volumeId } = await this.sharesService.getOwnVolumeIDs();
|
|
44
44
|
const nodeUidsIterator = this.apiService.iterateSharedNodeUids(volumeId, signal);
|
|
45
45
|
yield* this.iterateSharedNodesFromAPI(
|
|
46
46
|
nodeUidsIterator,
|
|
@@ -76,6 +76,7 @@ function tokenToEncryptedNode(logger: Logger, token: GetTokenInfoResponse['Token
|
|
|
76
76
|
creationTime: new Date(), // TODO
|
|
77
77
|
|
|
78
78
|
isShared: false,
|
|
79
|
+
isSharedPublicly: false,
|
|
79
80
|
directRole: MemberRole.Viewer, // TODO
|
|
80
81
|
};
|
|
81
82
|
|
|
@@ -133,6 +134,7 @@ function linkToEncryptedNode(
|
|
|
133
134
|
totalStorageSize: link.TotalSize,
|
|
134
135
|
|
|
135
136
|
isShared: false,
|
|
137
|
+
isSharedPublicly: false,
|
|
136
138
|
directRole: MemberRole.Viewer, // TODO
|
|
137
139
|
};
|
|
138
140
|
|
|
@@ -53,8 +53,8 @@ type PostDeleteNodesResponse =
|
|
|
53
53
|
|
|
54
54
|
export class UploadAPIService {
|
|
55
55
|
constructor(
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
protected apiService: DriveAPIService,
|
|
57
|
+
protected clientUid: string | undefined,
|
|
58
58
|
) {
|
|
59
59
|
this.apiService = apiService;
|
|
60
60
|
this.clientUid = clientUid;
|
|
@@ -252,7 +252,7 @@ export class UploadAPIService {
|
|
|
252
252
|
ManifestSignature: options.armoredManifestSignature,
|
|
253
253
|
SignatureAddress: options.signatureEmail,
|
|
254
254
|
XAttr: options.armoredExtendedAttributes || null,
|
|
255
|
-
Photo: null, //
|
|
255
|
+
Photo: null, // Only used for photos in the Photo volume.
|
|
256
256
|
});
|
|
257
257
|
}
|
|
258
258
|
|
|
@@ -7,8 +7,8 @@ import { EncryptedBlock, EncryptedThumbnail, NodeCrypto, NodeRevisionDraftKeys,
|
|
|
7
7
|
|
|
8
8
|
export class UploadCryptoService {
|
|
9
9
|
constructor(
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
protected driveCrypto: DriveCrypto,
|
|
11
|
+
protected nodesService: NodesService,
|
|
12
12
|
) {
|
|
13
13
|
this.driveCrypto = driveCrypto;
|
|
14
14
|
this.nodesService = nodesService;
|
|
@@ -98,6 +98,18 @@ class Uploader {
|
|
|
98
98
|
}
|
|
99
99
|
};
|
|
100
100
|
|
|
101
|
+
return this.newStreamUploader(
|
|
102
|
+
blockVerifier,
|
|
103
|
+
revisionDraft,
|
|
104
|
+
onFinish,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
protected async newStreamUploader(
|
|
109
|
+
blockVerifier: BlockVerifier,
|
|
110
|
+
revisionDraft: NodeRevisionDraft,
|
|
111
|
+
onFinish: (failure: boolean) => Promise<void>,
|
|
112
|
+
): Promise<StreamUploader> {
|
|
101
113
|
return new StreamUploader(
|
|
102
114
|
this.telemetry,
|
|
103
115
|
this.apiService,
|
|
@@ -7,6 +7,9 @@ export type NodeRevisionDraft = {
|
|
|
7
7
|
nodeUid: string;
|
|
8
8
|
nodeRevisionUid: string;
|
|
9
9
|
nodeKeys: NodeRevisionDraftKeys;
|
|
10
|
+
parentNodeKeys?: {
|
|
11
|
+
hashKey: Uint8Array;
|
|
12
|
+
};
|
|
10
13
|
// newNodeInfo is set only when revision is created with the new node.
|
|
11
14
|
newNodeInfo?: {
|
|
12
15
|
parentUid: string;
|
|
@@ -122,6 +122,9 @@ describe('UploadManager', () => {
|
|
|
122
122
|
email: 'signatureEmail',
|
|
123
123
|
},
|
|
124
124
|
},
|
|
125
|
+
parentNodeKeys: {
|
|
126
|
+
hashKey: 'parentNode:hashKey',
|
|
127
|
+
},
|
|
125
128
|
newNodeInfo: {
|
|
126
129
|
parentUid: 'parentUid',
|
|
127
130
|
name: 'name',
|
|
@@ -172,6 +175,9 @@ describe('UploadManager', () => {
|
|
|
172
175
|
email: 'signatureEmail',
|
|
173
176
|
},
|
|
174
177
|
},
|
|
178
|
+
parentNodeKeys: {
|
|
179
|
+
hashKey: 'parentNode:hashKey',
|
|
180
|
+
},
|
|
175
181
|
newNodeInfo: {
|
|
176
182
|
parentUid: 'volumeId~parentUid',
|
|
177
183
|
name: 'name',
|
|
@@ -338,6 +344,8 @@ describe('UploadManager', () => {
|
|
|
338
344
|
const manifest = new Uint8Array([1, 2, 3]);
|
|
339
345
|
const extendedAttributes = {
|
|
340
346
|
modificationTime: new Date(),
|
|
347
|
+
size: 123,
|
|
348
|
+
blockSizes: [100, 20, 3],
|
|
341
349
|
digests: {
|
|
342
350
|
sha1: 'sha1',
|
|
343
351
|
},
|
|
@@ -15,14 +15,14 @@ import { makeNodeUid, splitNodeUid } from '../uids';
|
|
|
15
15
|
* generating the necessary cryptographic keys and metadata.
|
|
16
16
|
*/
|
|
17
17
|
export class UploadManager {
|
|
18
|
-
|
|
18
|
+
protected logger: Logger;
|
|
19
19
|
|
|
20
20
|
constructor(
|
|
21
21
|
telemetry: ProtonDriveTelemetry,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
protected apiService: UploadAPIService,
|
|
23
|
+
protected cryptoService: UploadCryptoService,
|
|
24
|
+
protected nodesService: NodesService,
|
|
25
|
+
protected clientUid: string | undefined,
|
|
26
26
|
) {
|
|
27
27
|
this.logger = telemetry.getLogger('upload');
|
|
28
28
|
this.apiService = apiService;
|
|
@@ -59,6 +59,9 @@ export class UploadManager {
|
|
|
59
59
|
contentKeyPacketSessionKey: generatedNodeCrypto.contentKey.decrypted.contentKeyPacketSessionKey,
|
|
60
60
|
signatureAddress: generatedNodeCrypto.signatureAddress,
|
|
61
61
|
},
|
|
62
|
+
parentNodeKeys: {
|
|
63
|
+
hashKey: parentKeys.hashKey,
|
|
64
|
+
},
|
|
62
65
|
newNodeInfo: {
|
|
63
66
|
parentUid: parentFolderUid,
|
|
64
67
|
name,
|
|
@@ -260,21 +263,28 @@ export class UploadManager {
|
|
|
260
263
|
manifest: Uint8Array,
|
|
261
264
|
extendedAttributes: {
|
|
262
265
|
modificationTime?: Date;
|
|
263
|
-
size
|
|
264
|
-
blockSizes
|
|
265
|
-
digests
|
|
266
|
-
sha1
|
|
266
|
+
size: number;
|
|
267
|
+
blockSizes: number[];
|
|
268
|
+
digests: {
|
|
269
|
+
sha1: string;
|
|
267
270
|
};
|
|
268
271
|
},
|
|
272
|
+
additionalExtendedAttributes?: object,
|
|
269
273
|
): Promise<void> {
|
|
270
|
-
const generatedExtendedAttributes = generateFileExtendedAttributes(
|
|
274
|
+
const generatedExtendedAttributes = generateFileExtendedAttributes(
|
|
275
|
+
extendedAttributes,
|
|
276
|
+
additionalExtendedAttributes,
|
|
277
|
+
);
|
|
271
278
|
const nodeCommitCrypto = await this.cryptoService.commitFile(
|
|
272
279
|
nodeRevisionDraft.nodeKeys,
|
|
273
280
|
manifest,
|
|
274
281
|
generatedExtendedAttributes,
|
|
275
282
|
);
|
|
276
283
|
await this.apiService.commitDraftRevision(nodeRevisionDraft.nodeRevisionUid, nodeCommitCrypto);
|
|
284
|
+
await this.notifyNodeUploaded(nodeRevisionDraft);
|
|
285
|
+
}
|
|
277
286
|
|
|
287
|
+
protected async notifyNodeUploaded(nodeRevisionDraft: NodeRevisionDraft): Promise<void> {
|
|
278
288
|
// If new revision to existing node was created, invalidate the node.
|
|
279
289
|
// Otherwise notify about the new child in the parent.
|
|
280
290
|
if (nodeRevisionDraft.newNodeInfo) {
|
|
@@ -147,19 +147,24 @@ describe('StreamUploader', () => {
|
|
|
147
147
|
|
|
148
148
|
const numberOfExpectedBlocks = Math.ceil(metadata.expectedSize / FILE_CHUNK_SIZE);
|
|
149
149
|
expect(uploadManager.commitDraft).toHaveBeenCalledTimes(1);
|
|
150
|
-
expect(uploadManager.commitDraft).toHaveBeenCalledWith(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
150
|
+
expect(uploadManager.commitDraft).toHaveBeenCalledWith(
|
|
151
|
+
revisionDraft,
|
|
152
|
+
expect.anything(),
|
|
153
|
+
{
|
|
154
|
+
size: metadata.expectedSize,
|
|
155
|
+
blockSizes: metadata.expectedSize
|
|
156
|
+
? [
|
|
157
|
+
...Array(numberOfExpectedBlocks - 1).fill(FILE_CHUNK_SIZE),
|
|
158
|
+
metadata.expectedSize % FILE_CHUNK_SIZE,
|
|
159
|
+
]
|
|
160
|
+
: [],
|
|
161
|
+
modificationTime: undefined,
|
|
162
|
+
digests: {
|
|
163
|
+
sha1: expect.anything(),
|
|
164
|
+
},
|
|
161
165
|
},
|
|
162
|
-
|
|
166
|
+
metadata.additionalMetadata,
|
|
167
|
+
);
|
|
163
168
|
expect(telemetry.uploadFinished).toHaveBeenCalledTimes(1);
|
|
164
169
|
expect(telemetry.uploadFinished).toHaveBeenCalledWith('revisionUid', metadata.expectedSize + thumbnailSize);
|
|
165
170
|
expect(telemetry.uploadFailed).not.toHaveBeenCalled();
|
|
@@ -55,36 +55,36 @@ const MAX_BLOCK_UPLOAD_RETRIES = 3;
|
|
|
55
55
|
* that the upload process is efficient and does not overload the server.
|
|
56
56
|
*/
|
|
57
57
|
export class StreamUploader {
|
|
58
|
-
|
|
58
|
+
protected logger: Logger;
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
protected digests: UploadDigests;
|
|
61
|
+
protected controller: UploadController;
|
|
62
|
+
protected abortController: AbortController;
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
protected encryptedThumbnails = new Map<ThumbnailType, EncryptedThumbnail>();
|
|
65
|
+
protected encryptedBlocks = new Map<number, EncryptedBlock>();
|
|
66
|
+
protected encryptionFinished = false;
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
protected ongoingUploads = new Map<
|
|
69
69
|
string,
|
|
70
70
|
{
|
|
71
71
|
uploadPromise: Promise<void>;
|
|
72
72
|
encryptedBlock: EncryptedBlock | EncryptedThumbnail;
|
|
73
73
|
}
|
|
74
74
|
>();
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
protected uploadedThumbnails: ({ type: ThumbnailType } & EncryptedBlockMetadata)[] = [];
|
|
76
|
+
protected uploadedBlocks: ({ index: number } & EncryptedBlockMetadata)[] = [];
|
|
77
77
|
|
|
78
78
|
constructor(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
79
|
+
protected telemetry: UploadTelemetry,
|
|
80
|
+
protected apiService: UploadAPIService,
|
|
81
|
+
protected cryptoService: UploadCryptoService,
|
|
82
|
+
protected uploadManager: UploadManager,
|
|
83
|
+
protected blockVerifier: BlockVerifier,
|
|
84
|
+
protected revisionDraft: NodeRevisionDraft,
|
|
85
|
+
protected metadata: UploadMetadata,
|
|
86
|
+
protected onFinish: (failure: boolean) => Promise<void>,
|
|
87
|
+
protected signal?: AbortSignal,
|
|
88
88
|
) {
|
|
89
89
|
this.telemetry = telemetry;
|
|
90
90
|
this.logger = telemetry.getLoggerForRevision(revisionDraft.nodeRevisionUid);
|
|
@@ -205,19 +205,21 @@ export class StreamUploader {
|
|
|
205
205
|
await Promise.all(this.ongoingUploads.values().map(({ uploadPromise }) => uploadPromise));
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
-
|
|
208
|
+
protected async commitFile(thumbnails: Thumbnail[]) {
|
|
209
209
|
this.verifyIntegrity(thumbnails);
|
|
210
210
|
|
|
211
|
-
const uploadedBlocks = Array.from(this.uploadedBlocks.values());
|
|
212
|
-
uploadedBlocks.sort((a, b) => a.index - b.index);
|
|
213
|
-
|
|
214
211
|
const extendedAttributes = {
|
|
215
212
|
modificationTime: this.metadata.modificationTime,
|
|
216
213
|
size: this.metadata.expectedSize,
|
|
217
|
-
blockSizes:
|
|
214
|
+
blockSizes: this.uploadedBlockSizes,
|
|
218
215
|
digests: this.digests.digests(),
|
|
219
216
|
};
|
|
220
|
-
await this.uploadManager.commitDraft(
|
|
217
|
+
await this.uploadManager.commitDraft(
|
|
218
|
+
this.revisionDraft,
|
|
219
|
+
this.manifest,
|
|
220
|
+
extendedAttributes,
|
|
221
|
+
this.metadata.additionalMetadata,
|
|
222
|
+
);
|
|
221
223
|
}
|
|
222
224
|
|
|
223
225
|
private async encryptThumbnails(thumbnails: Thumbnail[]) {
|
|
@@ -514,7 +516,7 @@ export class StreamUploader {
|
|
|
514
516
|
await waitForCondition(() => this.encryptedBlocks.size > 0 || this.encryptionFinished);
|
|
515
517
|
}
|
|
516
518
|
|
|
517
|
-
|
|
519
|
+
protected verifyIntegrity(thumbnails: Thumbnail[]) {
|
|
518
520
|
const expectedBlockCount =
|
|
519
521
|
Math.ceil(this.metadata.expectedSize / FILE_CHUNK_SIZE) + (thumbnails ? thumbnails?.length : 0);
|
|
520
522
|
if (this.uploadedBlockCount !== expectedBlockCount) {
|
|
@@ -549,7 +551,13 @@ export class StreamUploader {
|
|
|
549
551
|
return this.uploadedBlocks.reduce((sum, { originalSize }) => sum + originalSize, 0);
|
|
550
552
|
}
|
|
551
553
|
|
|
552
|
-
|
|
554
|
+
protected get uploadedBlockSizes(): number[] {
|
|
555
|
+
const uploadedBlocks = Array.from(this.uploadedBlocks.values());
|
|
556
|
+
uploadedBlocks.sort((a, b) => a.index - b.index);
|
|
557
|
+
return uploadedBlocks.map((block) => block.originalSize);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
protected get manifest(): Uint8Array {
|
|
553
561
|
this.uploadedThumbnails.sort((a, b) => a.type - b.type);
|
|
554
562
|
this.uploadedBlocks.sort((a, b) => a.index - b.index);
|
|
555
563
|
const hashes = [
|