@protontech/drive-sdk 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/diagnostic/sdkDiagnostic.js +1 -1
- package/dist/diagnostic/sdkDiagnostic.js.map +1 -1
- package/dist/interface/download.d.ts +4 -4
- package/dist/interface/nodes.d.ts +4 -0
- package/dist/interface/nodes.js.map +1 -1
- package/dist/interface/upload.d.ts +6 -3
- package/dist/internal/apiService/apiService.d.ts +3 -0
- package/dist/internal/apiService/apiService.js +25 -2
- package/dist/internal/apiService/apiService.js.map +1 -1
- package/dist/internal/apiService/apiService.test.js +38 -0
- package/dist/internal/apiService/apiService.test.js.map +1 -1
- package/dist/internal/apiService/driveTypes.d.ts +31 -48
- package/dist/internal/apiService/errors.js +3 -0
- package/dist/internal/apiService/errors.js.map +1 -1
- package/dist/internal/apiService/errors.test.js +15 -7
- package/dist/internal/apiService/errors.test.js.map +1 -1
- package/dist/internal/asyncIteratorMap.d.ts +1 -1
- package/dist/internal/asyncIteratorMap.js +6 -1
- package/dist/internal/asyncIteratorMap.js.map +1 -1
- package/dist/internal/asyncIteratorMap.test.js +9 -0
- package/dist/internal/asyncIteratorMap.test.js.map +1 -1
- package/dist/internal/download/fileDownloader.d.ts +3 -3
- package/dist/internal/download/fileDownloader.js +5 -5
- package/dist/internal/download/fileDownloader.js.map +1 -1
- package/dist/internal/download/fileDownloader.test.js +8 -8
- package/dist/internal/download/fileDownloader.test.js.map +1 -1
- package/dist/internal/nodes/apiService.d.ts +6 -1
- package/dist/internal/nodes/apiService.js +45 -32
- package/dist/internal/nodes/apiService.js.map +1 -1
- package/dist/internal/nodes/apiService.test.js +164 -17
- 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/debouncer.d.ts +23 -0
- package/dist/internal/nodes/debouncer.js +80 -0
- package/dist/internal/nodes/debouncer.js.map +1 -0
- package/dist/internal/nodes/debouncer.test.d.ts +1 -0
- package/dist/internal/nodes/debouncer.test.js +100 -0
- package/dist/internal/nodes/debouncer.test.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 +1 -0
- package/dist/internal/nodes/index.test.js.map +1 -1
- package/dist/internal/nodes/interface.d.ts +1 -0
- package/dist/internal/nodes/nodesAccess.d.ts +2 -1
- package/dist/internal/nodes/nodesAccess.js +24 -5
- package/dist/internal/nodes/nodesAccess.js.map +1 -1
- package/dist/internal/nodes/nodesAccess.test.js +2 -2
- package/dist/internal/nodes/nodesAccess.test.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.js +1 -0
- package/dist/internal/nodes/nodesManagement.js.map +1 -1
- package/dist/internal/photos/index.d.ts +11 -0
- package/dist/internal/photos/index.js +27 -0
- package/dist/internal/photos/index.js.map +1 -1
- package/dist/internal/photos/upload.d.ts +60 -0
- package/dist/internal/photos/upload.js +104 -0
- package/dist/internal/photos/upload.js.map +1 -0
- package/dist/internal/sharingPublic/apiService.d.ts +2 -2
- package/dist/internal/sharingPublic/apiService.js +2 -62
- package/dist/internal/sharingPublic/apiService.js.map +1 -1
- package/dist/internal/sharingPublic/cryptoCache.d.ts +0 -4
- package/dist/internal/sharingPublic/cryptoCache.js +0 -28
- package/dist/internal/sharingPublic/cryptoCache.js.map +1 -1
- package/dist/internal/sharingPublic/cryptoReporter.d.ts +16 -0
- package/dist/internal/sharingPublic/cryptoReporter.js +44 -0
- package/dist/internal/sharingPublic/cryptoReporter.js.map +1 -0
- package/dist/internal/sharingPublic/cryptoService.d.ts +3 -4
- package/dist/internal/sharingPublic/cryptoService.js +5 -43
- package/dist/internal/sharingPublic/cryptoService.js.map +1 -1
- package/dist/internal/sharingPublic/index.d.ts +21 -3
- package/dist/internal/sharingPublic/index.js +43 -12
- package/dist/internal/sharingPublic/index.js.map +1 -1
- package/dist/internal/sharingPublic/interface.d.ts +0 -1
- package/dist/internal/sharingPublic/nodes.d.ts +13 -0
- package/dist/internal/sharingPublic/nodes.js +28 -0
- package/dist/internal/sharingPublic/nodes.js.map +1 -0
- package/dist/internal/sharingPublic/session/session.d.ts +3 -3
- package/dist/internal/sharingPublic/session/url.test.js +3 -3
- package/dist/internal/sharingPublic/shares.d.ts +34 -0
- package/dist/internal/sharingPublic/shares.js +69 -0
- package/dist/internal/sharingPublic/shares.js.map +1 -0
- package/dist/internal/upload/apiService.d.ts +2 -2
- package/dist/internal/upload/apiService.js +11 -2
- package/dist/internal/upload/apiService.js.map +1 -1
- package/dist/internal/upload/controller.d.ts +8 -2
- package/dist/internal/upload/controller.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 +7 -3
- package/dist/internal/upload/fileUploader.js +6 -3
- package/dist/internal/upload/fileUploader.js.map +1 -1
- package/dist/internal/upload/fileUploader.test.js +23 -11
- package/dist/internal/upload/fileUploader.test.js.map +1 -1
- package/dist/internal/upload/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 +40 -26
- package/dist/internal/upload/streamUploader.js +15 -8
- package/dist/internal/upload/streamUploader.js.map +1 -1
- package/dist/internal/upload/streamUploader.test.js +11 -7
- package/dist/internal/upload/streamUploader.test.js.map +1 -1
- package/dist/protonDriveClient.d.ts +3 -3
- package/dist/protonDriveClient.js +4 -4
- package/dist/protonDriveClient.js.map +1 -1
- package/dist/protonDrivePhotosClient.d.ts +18 -2
- package/dist/protonDrivePhotosClient.js +19 -2
- package/dist/protonDrivePhotosClient.js.map +1 -1
- package/dist/protonDrivePublicLinkClient.d.ts +31 -4
- package/dist/protonDrivePublicLinkClient.js +52 -9
- package/dist/protonDrivePublicLinkClient.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/diagnostic/sdkDiagnostic.ts +1 -1
- package/src/interface/download.ts +4 -4
- package/src/interface/nodes.ts +4 -0
- package/src/interface/upload.ts +3 -3
- package/src/internal/apiService/apiService.test.ts +50 -0
- package/src/internal/apiService/apiService.ts +33 -2
- package/src/internal/apiService/driveTypes.ts +31 -48
- package/src/internal/apiService/errors.test.ts +10 -0
- package/src/internal/apiService/errors.ts +5 -1
- package/src/internal/asyncIteratorMap.test.ts +12 -0
- package/src/internal/asyncIteratorMap.ts +8 -0
- package/src/internal/download/fileDownloader.test.ts +8 -8
- package/src/internal/download/fileDownloader.ts +5 -5
- package/src/internal/nodes/apiService.test.ts +222 -16
- package/src/internal/nodes/apiService.ts +63 -49
- package/src/internal/nodes/cache.test.ts +1 -0
- package/src/internal/nodes/debouncer.test.ts +129 -0
- package/src/internal/nodes/debouncer.ts +93 -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 +1 -0
- package/src/internal/nodes/interface.ts +1 -0
- package/src/internal/nodes/nodesAccess.test.ts +2 -2
- package/src/internal/nodes/nodesAccess.ts +30 -5
- package/src/internal/nodes/nodesManagement.ts +1 -0
- package/src/internal/photos/index.ts +62 -0
- package/src/internal/photos/upload.ts +212 -0
- package/src/internal/sharingPublic/apiService.ts +5 -86
- package/src/internal/sharingPublic/cryptoCache.ts +0 -34
- package/src/internal/sharingPublic/cryptoReporter.ts +73 -0
- package/src/internal/sharingPublic/cryptoService.ts +4 -80
- package/src/internal/sharingPublic/index.ts +68 -6
- package/src/internal/sharingPublic/interface.ts +0 -9
- package/src/internal/sharingPublic/nodes.ts +37 -0
- package/src/internal/sharingPublic/session/apiService.ts +1 -1
- package/src/internal/sharingPublic/session/session.ts +3 -3
- package/src/internal/sharingPublic/session/url.test.ts +3 -3
- package/src/internal/sharingPublic/shares.ts +86 -0
- package/src/internal/upload/apiService.ts +15 -4
- package/src/internal/upload/controller.ts +2 -2
- package/src/internal/upload/cryptoService.ts +2 -2
- package/src/internal/upload/fileUploader.test.ts +25 -11
- package/src/internal/upload/fileUploader.ts +16 -3
- 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 +32 -15
- package/src/internal/upload/streamUploader.ts +43 -30
- package/src/protonDriveClient.ts +4 -4
- package/src/protonDrivePhotosClient.ts +46 -6
- package/src/protonDrivePublicLinkClient.ts +93 -12
- package/src/transformers.ts +2 -0
- package/dist/internal/sharingPublic/manager.d.ts +0 -19
- package/dist/internal/sharingPublic/manager.js +0 -81
- package/dist/internal/sharingPublic/manager.js.map +0 -1
- package/src/internal/sharingPublic/manager.ts +0 -86
|
@@ -2,7 +2,7 @@ import { waitForCondition } from '../wait';
|
|
|
2
2
|
|
|
3
3
|
export class UploadController {
|
|
4
4
|
private paused = false;
|
|
5
|
-
public promise?: Promise<string>;
|
|
5
|
+
public promise?: Promise<{ nodeRevisionUid: string, nodeUid: string }>;
|
|
6
6
|
|
|
7
7
|
async waitIfPaused(): Promise<void> {
|
|
8
8
|
await waitForCondition(() => !this.paused);
|
|
@@ -16,7 +16,7 @@ export class UploadController {
|
|
|
16
16
|
this.paused = false;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
async completion(): Promise<string> {
|
|
19
|
+
async completion(): Promise<{ nodeRevisionUid: string, nodeUid: string }> {
|
|
20
20
|
if (!this.promise) {
|
|
21
21
|
throw new Error('UploadController.completion() called before upload started');
|
|
22
22
|
}
|
|
@@ -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;
|
|
@@ -108,6 +108,7 @@ describe('FileUploader', () => {
|
|
|
108
108
|
|
|
109
109
|
revisionDraft = {
|
|
110
110
|
nodeRevisionUid: 'revisionUid',
|
|
111
|
+
nodeUid: 'nodeUid',
|
|
111
112
|
nodeKeys: {
|
|
112
113
|
signatureAddress: { addressId: 'addressId' },
|
|
113
114
|
},
|
|
@@ -131,10 +132,13 @@ describe('FileUploader', () => {
|
|
|
131
132
|
abortController.signal,
|
|
132
133
|
);
|
|
133
134
|
|
|
134
|
-
startUploadSpy = jest.spyOn(uploader as any, 'startUpload').mockReturnValue(Promise.resolve(
|
|
135
|
+
startUploadSpy = jest.spyOn(uploader as any, 'startUpload').mockReturnValue(Promise.resolve({
|
|
136
|
+
nodeRevisionUid: 'revisionUid',
|
|
137
|
+
nodeUid: 'nodeUid'
|
|
138
|
+
}));
|
|
135
139
|
});
|
|
136
140
|
|
|
137
|
-
describe('
|
|
141
|
+
describe('uploadFromFile', () => {
|
|
138
142
|
// @ts-expect-error Ignore mocking File
|
|
139
143
|
const file = {
|
|
140
144
|
type: 'image/png',
|
|
@@ -146,50 +150,60 @@ describe('FileUploader', () => {
|
|
|
146
150
|
const onProgress = jest.fn();
|
|
147
151
|
|
|
148
152
|
it('should set media type if not set', async () => {
|
|
149
|
-
await uploader.
|
|
153
|
+
await uploader.uploadFromFile(file, thumbnails, onProgress);
|
|
150
154
|
|
|
151
155
|
expect(metadata.mediaType).toEqual('image/png');
|
|
152
156
|
expect(startUploadSpy).toHaveBeenCalledWith('stream', thumbnails, onProgress);
|
|
153
157
|
});
|
|
154
158
|
|
|
155
159
|
it('should set expected size if not set', async () => {
|
|
156
|
-
await uploader.
|
|
160
|
+
await uploader.uploadFromFile(file, thumbnails, onProgress);
|
|
157
161
|
|
|
158
162
|
expect(metadata.expectedSize).toEqual(file.size);
|
|
159
163
|
expect(startUploadSpy).toHaveBeenCalledWith('stream', thumbnails, onProgress);
|
|
160
164
|
});
|
|
161
165
|
|
|
162
166
|
it('should set modification time if not set', async () => {
|
|
163
|
-
await uploader.
|
|
167
|
+
await uploader.uploadFromFile(file, thumbnails, onProgress);
|
|
164
168
|
|
|
165
169
|
expect(metadata.modificationTime).toEqual(new Date(123456789));
|
|
166
170
|
expect(startUploadSpy).toHaveBeenCalledWith('stream', thumbnails, onProgress);
|
|
167
171
|
});
|
|
168
172
|
|
|
169
173
|
it('should throw an error if upload already started', async () => {
|
|
170
|
-
await uploader.
|
|
174
|
+
await uploader.uploadFromFile(file, thumbnails, onProgress);
|
|
171
175
|
|
|
172
|
-
await expect(uploader.
|
|
176
|
+
await expect(uploader.uploadFromFile(file, thumbnails, onProgress)).rejects.toThrow('Upload already started');
|
|
173
177
|
});
|
|
174
178
|
});
|
|
175
179
|
|
|
176
|
-
describe('
|
|
180
|
+
describe('uploadFromStream', () => {
|
|
177
181
|
const stream = new ReadableStream();
|
|
178
182
|
const thumbnails: Thumbnail[] = [];
|
|
179
183
|
const onProgress = jest.fn();
|
|
180
184
|
|
|
181
185
|
it('should start the upload process', async () => {
|
|
182
|
-
await uploader.
|
|
186
|
+
await uploader.uploadFromStream(stream, thumbnails, onProgress);
|
|
183
187
|
|
|
184
188
|
expect(startUploadSpy).toHaveBeenCalledWith(stream, thumbnails, onProgress);
|
|
185
189
|
});
|
|
186
190
|
|
|
187
191
|
it('should throw an error if upload already started', async () => {
|
|
188
|
-
await uploader.
|
|
192
|
+
await uploader.uploadFromStream(stream, thumbnails, onProgress);
|
|
189
193
|
|
|
190
|
-
await expect(uploader.
|
|
194
|
+
await expect(uploader.uploadFromStream(stream, thumbnails, onProgress)).rejects.toThrow(
|
|
191
195
|
'Upload already started',
|
|
192
196
|
);
|
|
193
197
|
});
|
|
198
|
+
|
|
199
|
+
it('should return correct nodeUid and nodeRevisionUid via controller completion', async () => {
|
|
200
|
+
const controller = await uploader.uploadFromStream(stream, thumbnails, onProgress);
|
|
201
|
+
const result = await controller.completion();
|
|
202
|
+
|
|
203
|
+
expect(result).toEqual({
|
|
204
|
+
nodeRevisionUid: 'revisionUid',
|
|
205
|
+
nodeUid: 'nodeUid'
|
|
206
|
+
});
|
|
207
|
+
});
|
|
194
208
|
});
|
|
195
209
|
});
|
|
@@ -46,7 +46,7 @@ class Uploader {
|
|
|
46
46
|
this.controller = new UploadController();
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
async
|
|
49
|
+
async uploadFromFile(
|
|
50
50
|
fileObject: File,
|
|
51
51
|
thumbnails: Thumbnail[],
|
|
52
52
|
onProgress?: (uploadedBytes: number) => void,
|
|
@@ -67,7 +67,7 @@ class Uploader {
|
|
|
67
67
|
return this.controller;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
async
|
|
70
|
+
async uploadFromStream(
|
|
71
71
|
stream: ReadableStream,
|
|
72
72
|
thumbnails: Thumbnail[],
|
|
73
73
|
onProgress?: (uploadedBytes: number) => void,
|
|
@@ -83,7 +83,7 @@ class Uploader {
|
|
|
83
83
|
stream: ReadableStream,
|
|
84
84
|
thumbnails: Thumbnail[],
|
|
85
85
|
onProgress?: (uploadedBytes: number) => void,
|
|
86
|
-
): Promise<string> {
|
|
86
|
+
): Promise<{ nodeRevisionUid: string, nodeUid: string }> {
|
|
87
87
|
const uploader = await this.initStreamUploader();
|
|
88
88
|
return uploader.start(stream, thumbnails, onProgress);
|
|
89
89
|
}
|
|
@@ -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,
|
|
@@ -107,6 +119,7 @@ class Uploader {
|
|
|
107
119
|
revisionDraft,
|
|
108
120
|
this.metadata,
|
|
109
121
|
onFinish,
|
|
122
|
+
this.controller,
|
|
110
123
|
this.signal,
|
|
111
124
|
);
|
|
112
125
|
}
|
|
@@ -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) {
|
|
@@ -108,6 +108,7 @@ describe('StreamUploader', () => {
|
|
|
108
108
|
|
|
109
109
|
revisionDraft = {
|
|
110
110
|
nodeRevisionUid: 'revisionUid',
|
|
111
|
+
nodeUid: 'nodeUid',
|
|
111
112
|
nodeKeys: {
|
|
112
113
|
signatureAddress: { addressId: 'addressId' },
|
|
113
114
|
},
|
|
@@ -131,6 +132,7 @@ describe('StreamUploader', () => {
|
|
|
131
132
|
revisionDraft,
|
|
132
133
|
metadata,
|
|
133
134
|
onFinish,
|
|
135
|
+
controller,
|
|
134
136
|
abortController.signal,
|
|
135
137
|
);
|
|
136
138
|
});
|
|
@@ -143,23 +145,33 @@ describe('StreamUploader', () => {
|
|
|
143
145
|
let stream: ReadableStream<Uint8Array>;
|
|
144
146
|
|
|
145
147
|
const verifySuccess = async () => {
|
|
146
|
-
await uploader.start(stream, thumbnails, onProgress);
|
|
148
|
+
const result = await uploader.start(stream, thumbnails, onProgress);
|
|
149
|
+
|
|
150
|
+
expect(result).toEqual({
|
|
151
|
+
nodeRevisionUid: 'revisionUid',
|
|
152
|
+
nodeUid: 'nodeUid'
|
|
153
|
+
});
|
|
147
154
|
|
|
148
155
|
const numberOfExpectedBlocks = Math.ceil(metadata.expectedSize / FILE_CHUNK_SIZE);
|
|
149
156
|
expect(uploadManager.commitDraft).toHaveBeenCalledTimes(1);
|
|
150
|
-
expect(uploadManager.commitDraft).toHaveBeenCalledWith(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
157
|
+
expect(uploadManager.commitDraft).toHaveBeenCalledWith(
|
|
158
|
+
revisionDraft,
|
|
159
|
+
expect.anything(),
|
|
160
|
+
{
|
|
161
|
+
size: metadata.expectedSize,
|
|
162
|
+
blockSizes: metadata.expectedSize
|
|
163
|
+
? [
|
|
164
|
+
...Array(numberOfExpectedBlocks - 1).fill(FILE_CHUNK_SIZE),
|
|
165
|
+
metadata.expectedSize % FILE_CHUNK_SIZE,
|
|
166
|
+
]
|
|
167
|
+
: [],
|
|
168
|
+
modificationTime: undefined,
|
|
169
|
+
digests: {
|
|
170
|
+
sha1: expect.anything(),
|
|
171
|
+
},
|
|
161
172
|
},
|
|
162
|
-
|
|
173
|
+
metadata.additionalMetadata,
|
|
174
|
+
);
|
|
163
175
|
expect(telemetry.uploadFinished).toHaveBeenCalledTimes(1);
|
|
164
176
|
expect(telemetry.uploadFinished).toHaveBeenCalledWith('revisionUid', metadata.expectedSize + thumbnailSize);
|
|
165
177
|
expect(telemetry.uploadFailed).not.toHaveBeenCalled();
|
|
@@ -246,6 +258,8 @@ describe('StreamUploader', () => {
|
|
|
246
258
|
revisionDraft,
|
|
247
259
|
metadata,
|
|
248
260
|
onFinish,
|
|
261
|
+
controller,
|
|
262
|
+
abortController.signal,
|
|
249
263
|
);
|
|
250
264
|
|
|
251
265
|
await verifySuccess();
|
|
@@ -273,6 +287,8 @@ describe('StreamUploader', () => {
|
|
|
273
287
|
revisionDraft,
|
|
274
288
|
metadata,
|
|
275
289
|
onFinish,
|
|
290
|
+
controller,
|
|
291
|
+
abortController.signal,
|
|
276
292
|
);
|
|
277
293
|
|
|
278
294
|
await verifySuccess();
|
|
@@ -454,9 +470,10 @@ describe('StreamUploader', () => {
|
|
|
454
470
|
{
|
|
455
471
|
// Fake expected size to break verification
|
|
456
472
|
expectedSize: 1 * 1024 * 1024 + 1024,
|
|
457
|
-
|
|
458
|
-
},
|
|
473
|
+
} as UploadMetadata,
|
|
459
474
|
onFinish,
|
|
475
|
+
controller,
|
|
476
|
+
abortController.signal,
|
|
460
477
|
);
|
|
461
478
|
|
|
462
479
|
await verifyFailure(
|
|
@@ -55,36 +55,37 @@ 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 uploadController: UploadController,
|
|
88
|
+
protected signal?: AbortSignal,
|
|
88
89
|
) {
|
|
89
90
|
this.telemetry = telemetry;
|
|
90
91
|
this.logger = telemetry.getLoggerForRevision(revisionDraft.nodeRevisionUid);
|
|
@@ -104,14 +105,14 @@ export class StreamUploader {
|
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
this.digests = new UploadDigests();
|
|
107
|
-
this.controller =
|
|
108
|
+
this.controller = uploadController;
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
async start(
|
|
111
112
|
stream: ReadableStream,
|
|
112
113
|
thumbnails: Thumbnail[],
|
|
113
114
|
onProgress?: (uploadedBytes: number) => void,
|
|
114
|
-
): Promise<string> {
|
|
115
|
+
): Promise<{ nodeRevisionUid: string, nodeUid: string }> {
|
|
115
116
|
let failure = false;
|
|
116
117
|
|
|
117
118
|
// File progress is tracked for telemetry - to track at what
|
|
@@ -154,7 +155,11 @@ export class StreamUploader {
|
|
|
154
155
|
await this.onFinish(failure);
|
|
155
156
|
}
|
|
156
157
|
|
|
157
|
-
return
|
|
158
|
+
return {
|
|
159
|
+
nodeRevisionUid: this.revisionDraft.nodeRevisionUid,
|
|
160
|
+
nodeUid: this.revisionDraft.nodeUid
|
|
161
|
+
}
|
|
162
|
+
|
|
158
163
|
}
|
|
159
164
|
|
|
160
165
|
private async encryptAndUploadBlocks(
|
|
@@ -205,19 +210,21 @@ export class StreamUploader {
|
|
|
205
210
|
await Promise.all(this.ongoingUploads.values().map(({ uploadPromise }) => uploadPromise));
|
|
206
211
|
}
|
|
207
212
|
|
|
208
|
-
|
|
213
|
+
protected async commitFile(thumbnails: Thumbnail[]) {
|
|
209
214
|
this.verifyIntegrity(thumbnails);
|
|
210
215
|
|
|
211
|
-
const uploadedBlocks = Array.from(this.uploadedBlocks.values());
|
|
212
|
-
uploadedBlocks.sort((a, b) => a.index - b.index);
|
|
213
|
-
|
|
214
216
|
const extendedAttributes = {
|
|
215
217
|
modificationTime: this.metadata.modificationTime,
|
|
216
218
|
size: this.metadata.expectedSize,
|
|
217
|
-
blockSizes:
|
|
219
|
+
blockSizes: this.uploadedBlockSizes,
|
|
218
220
|
digests: this.digests.digests(),
|
|
219
221
|
};
|
|
220
|
-
await this.uploadManager.commitDraft(
|
|
222
|
+
await this.uploadManager.commitDraft(
|
|
223
|
+
this.revisionDraft,
|
|
224
|
+
this.manifest,
|
|
225
|
+
extendedAttributes,
|
|
226
|
+
this.metadata.additionalMetadata,
|
|
227
|
+
);
|
|
221
228
|
}
|
|
222
229
|
|
|
223
230
|
private async encryptThumbnails(thumbnails: Thumbnail[]) {
|
|
@@ -514,7 +521,7 @@ export class StreamUploader {
|
|
|
514
521
|
await waitForCondition(() => this.encryptedBlocks.size > 0 || this.encryptionFinished);
|
|
515
522
|
}
|
|
516
523
|
|
|
517
|
-
|
|
524
|
+
protected verifyIntegrity(thumbnails: Thumbnail[]) {
|
|
518
525
|
const expectedBlockCount =
|
|
519
526
|
Math.ceil(this.metadata.expectedSize / FILE_CHUNK_SIZE) + (thumbnails ? thumbnails?.length : 0);
|
|
520
527
|
if (this.uploadedBlockCount !== expectedBlockCount) {
|
|
@@ -549,7 +556,13 @@ export class StreamUploader {
|
|
|
549
556
|
return this.uploadedBlocks.reduce((sum, { originalSize }) => sum + originalSize, 0);
|
|
550
557
|
}
|
|
551
558
|
|
|
552
|
-
|
|
559
|
+
protected get uploadedBlockSizes(): number[] {
|
|
560
|
+
const uploadedBlocks = Array.from(this.uploadedBlocks.values());
|
|
561
|
+
uploadedBlocks.sort((a, b) => a.index - b.index);
|
|
562
|
+
return uploadedBlocks.map((block) => block.originalSize);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
protected get manifest(): Uint8Array {
|
|
553
566
|
this.uploadedThumbnails.sort((a, b) => a.type - b.type);
|
|
554
567
|
this.uploadedBlocks.sort((a, b) => a.index - b.index);
|
|
555
568
|
const hashes = [
|
package/src/protonDriveClient.ts
CHANGED
|
@@ -208,12 +208,12 @@ export class ProtonDriveClient {
|
|
|
208
208
|
const { httpClient, token, password } = await this.sessionManager.auth(url, customPassword);
|
|
209
209
|
return new ProtonDrivePublicLinkClient({
|
|
210
210
|
httpClient,
|
|
211
|
-
cryptoCache,
|
|
212
211
|
account,
|
|
213
212
|
openPGPCryptoModule,
|
|
214
213
|
srpModule,
|
|
215
214
|
config,
|
|
216
215
|
telemetry,
|
|
216
|
+
url,
|
|
217
217
|
token,
|
|
218
218
|
password,
|
|
219
219
|
});
|
|
@@ -726,7 +726,7 @@ export class ProtonDriveClient {
|
|
|
726
726
|
* ```typescript
|
|
727
727
|
* const downloader = await client.getFileDownloader(nodeUid, signal);
|
|
728
728
|
* const claimedSize = fileDownloader.getClaimedSizeInBytes();
|
|
729
|
-
* const downloadController = fileDownloader.
|
|
729
|
+
* const downloadController = fileDownloader.downloadToStream(stream, (downloadedBytes) => { ... });
|
|
730
730
|
*
|
|
731
731
|
* signalController.abort(); // to cancel
|
|
732
732
|
* downloadController.pause(); // to pause
|
|
@@ -786,12 +786,12 @@ export class ProtonDriveClient {
|
|
|
786
786
|
*
|
|
787
787
|
* ```typescript
|
|
788
788
|
* const uploader = await client.getFileUploader(parentFolderUid, name, metadata, signal);
|
|
789
|
-
* const uploadController = await uploader.
|
|
789
|
+
* const uploadController = await uploader.uploadFromStream(stream, thumbnails, (uploadedBytes) => { ... });
|
|
790
790
|
*
|
|
791
791
|
* signalController.abort(); // to cancel
|
|
792
792
|
* uploadController.pause(); // to pause
|
|
793
793
|
* uploadController.resume(); // to resume
|
|
794
|
-
* const nodeUid = await uploadController.completion(); // to await completion
|
|
794
|
+
* const { nodeUid, nodeRevisionUid } = await uploadController.completion(); // to await completion
|
|
795
795
|
* ```
|
|
796
796
|
*/
|
|
797
797
|
async getFileUploader(
|
|
@@ -8,20 +8,27 @@ import {
|
|
|
8
8
|
FileUploader,
|
|
9
9
|
SDKEvent,
|
|
10
10
|
MaybeNode,
|
|
11
|
+
ThumbnailType,
|
|
12
|
+
ThumbnailResult,
|
|
11
13
|
} from './interface';
|
|
12
14
|
import { getConfig } from './config';
|
|
13
15
|
import { DriveCrypto } from './crypto';
|
|
14
16
|
import { Telemetry } from './telemetry';
|
|
15
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
convertInternalMissingNodeIterator,
|
|
19
|
+
convertInternalNodeIterator,
|
|
20
|
+
convertInternalNodePromise,
|
|
21
|
+
getUid,
|
|
22
|
+
getUids,
|
|
23
|
+
} from './transformers';
|
|
16
24
|
import { DriveAPIService } from './internal/apiService';
|
|
17
25
|
import { initDownloadModule } from './internal/download';
|
|
18
26
|
import { DriveEventsService, DriveListener, EventSubscription } from './internal/events';
|
|
19
27
|
import { initNodesModule } from './internal/nodes';
|
|
20
|
-
import { initPhotoSharesModule,
|
|
28
|
+
import { initPhotosModule, initPhotoSharesModule, initPhotoUploadModule } from './internal/photos';
|
|
21
29
|
import { SDKEvents } from './internal/sdkEvents';
|
|
22
30
|
import { initSharesModule } from './internal/shares';
|
|
23
31
|
import { initSharingModule } from './internal/sharing';
|
|
24
|
-
import { initUploadModule } from './internal/upload';
|
|
25
32
|
|
|
26
33
|
/**
|
|
27
34
|
* ProtonDrivePhotosClient is the interface to access Photos functionality.
|
|
@@ -39,7 +46,7 @@ export class ProtonDrivePhotosClient {
|
|
|
39
46
|
private nodes: ReturnType<typeof initNodesModule>;
|
|
40
47
|
private sharing: ReturnType<typeof initSharingModule>;
|
|
41
48
|
private download: ReturnType<typeof initDownloadModule>;
|
|
42
|
-
private upload: ReturnType<typeof
|
|
49
|
+
private upload: ReturnType<typeof initPhotoUploadModule>;
|
|
43
50
|
private photos: ReturnType<typeof initPhotosModule>;
|
|
44
51
|
|
|
45
52
|
public experimental: {
|
|
@@ -115,7 +122,7 @@ export class ProtonDrivePhotosClient {
|
|
|
115
122
|
this.nodes.access,
|
|
116
123
|
this.nodes.revisions,
|
|
117
124
|
);
|
|
118
|
-
this.upload =
|
|
125
|
+
this.upload = initPhotoUploadModule(
|
|
119
126
|
telemetry,
|
|
120
127
|
apiService,
|
|
121
128
|
cryptoModule,
|
|
@@ -208,6 +215,16 @@ export class ProtonDrivePhotosClient {
|
|
|
208
215
|
yield* convertInternalMissingNodeIterator(this.nodes.access.iterateNodes(getUids(nodeUids), signal));
|
|
209
216
|
}
|
|
210
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Get the node by its UID.
|
|
220
|
+
*
|
|
221
|
+
* See `ProtonDriveClient.getNode` for more information.
|
|
222
|
+
*/
|
|
223
|
+
async getNode(nodeUid: NodeOrUid): Promise<MaybeNode> {
|
|
224
|
+
this.logger.info(`Getting node ${getUid(nodeUid)}`);
|
|
225
|
+
return convertInternalNodePromise(this.nodes.access.getNode(getUid(nodeUid)));
|
|
226
|
+
}
|
|
227
|
+
|
|
211
228
|
/**
|
|
212
229
|
* Iterates the albums.
|
|
213
230
|
*
|
|
@@ -229,12 +246,35 @@ export class ProtonDrivePhotosClient {
|
|
|
229
246
|
return this.download.getFileDownloader(getUid(nodeUid), signal);
|
|
230
247
|
}
|
|
231
248
|
|
|
249
|
+
/**
|
|
250
|
+
* Iterates the thumbnails of the given nodes.
|
|
251
|
+
*
|
|
252
|
+
* See `ProtonDriveClient.iterateThumbnails` for more information.
|
|
253
|
+
*/
|
|
254
|
+
async *iterateThumbnails(
|
|
255
|
+
nodeUids: NodeOrUid[],
|
|
256
|
+
thumbnailType?: ThumbnailType,
|
|
257
|
+
signal?: AbortSignal,
|
|
258
|
+
): AsyncGenerator<ThumbnailResult> {
|
|
259
|
+
this.logger.info(`Iterating ${nodeUids.length} thumbnails`);
|
|
260
|
+
yield* this.download.iterateThumbnails(getUids(nodeUids), thumbnailType, signal);
|
|
261
|
+
}
|
|
262
|
+
|
|
232
263
|
/**
|
|
233
264
|
* Get the file uploader to upload a new file.
|
|
234
265
|
*
|
|
235
266
|
* See `ProtonDriveClient.getFileUploader` for more information.
|
|
236
267
|
*/
|
|
237
|
-
async getFileUploader(
|
|
268
|
+
async getFileUploader(
|
|
269
|
+
name: string,
|
|
270
|
+
metadata: UploadMetadata & {
|
|
271
|
+
captureTime?: Date;
|
|
272
|
+
mainPhotoLinkID?: string;
|
|
273
|
+
// TODO: handle tags enum in the SDK
|
|
274
|
+
tags?: (0 | 3 | 1 | 2 | 7 | 4 | 5 | 6 | 8 | 9)[];
|
|
275
|
+
},
|
|
276
|
+
signal?: AbortSignal,
|
|
277
|
+
): Promise<FileUploader> {
|
|
238
278
|
this.logger.info(`Getting file uploader`);
|
|
239
279
|
const parentFolderUid = await this.nodes.access.getVolumeRootFolder();
|
|
240
280
|
return this.upload.getFileUploader(getUid(parentFolderUid), name, metadata, signal);
|