@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.
Files changed (176) hide show
  1. package/dist/diagnostic/sdkDiagnostic.js +1 -1
  2. package/dist/diagnostic/sdkDiagnostic.js.map +1 -1
  3. package/dist/interface/download.d.ts +4 -4
  4. package/dist/interface/nodes.d.ts +4 -0
  5. package/dist/interface/nodes.js.map +1 -1
  6. package/dist/interface/upload.d.ts +6 -3
  7. package/dist/internal/apiService/apiService.d.ts +3 -0
  8. package/dist/internal/apiService/apiService.js +25 -2
  9. package/dist/internal/apiService/apiService.js.map +1 -1
  10. package/dist/internal/apiService/apiService.test.js +38 -0
  11. package/dist/internal/apiService/apiService.test.js.map +1 -1
  12. package/dist/internal/apiService/driveTypes.d.ts +31 -48
  13. package/dist/internal/apiService/errors.js +3 -0
  14. package/dist/internal/apiService/errors.js.map +1 -1
  15. package/dist/internal/apiService/errors.test.js +15 -7
  16. package/dist/internal/apiService/errors.test.js.map +1 -1
  17. package/dist/internal/asyncIteratorMap.d.ts +1 -1
  18. package/dist/internal/asyncIteratorMap.js +6 -1
  19. package/dist/internal/asyncIteratorMap.js.map +1 -1
  20. package/dist/internal/asyncIteratorMap.test.js +9 -0
  21. package/dist/internal/asyncIteratorMap.test.js.map +1 -1
  22. package/dist/internal/download/fileDownloader.d.ts +3 -3
  23. package/dist/internal/download/fileDownloader.js +5 -5
  24. package/dist/internal/download/fileDownloader.js.map +1 -1
  25. package/dist/internal/download/fileDownloader.test.js +8 -8
  26. package/dist/internal/download/fileDownloader.test.js.map +1 -1
  27. package/dist/internal/nodes/apiService.d.ts +6 -1
  28. package/dist/internal/nodes/apiService.js +45 -32
  29. package/dist/internal/nodes/apiService.js.map +1 -1
  30. package/dist/internal/nodes/apiService.test.js +164 -17
  31. package/dist/internal/nodes/apiService.test.js.map +1 -1
  32. package/dist/internal/nodes/cache.test.js +1 -0
  33. package/dist/internal/nodes/cache.test.js.map +1 -1
  34. package/dist/internal/nodes/debouncer.d.ts +23 -0
  35. package/dist/internal/nodes/debouncer.js +80 -0
  36. package/dist/internal/nodes/debouncer.js.map +1 -0
  37. package/dist/internal/nodes/debouncer.test.d.ts +1 -0
  38. package/dist/internal/nodes/debouncer.test.js +100 -0
  39. package/dist/internal/nodes/debouncer.test.js.map +1 -0
  40. package/dist/internal/nodes/extendedAttributes.d.ts +2 -2
  41. package/dist/internal/nodes/extendedAttributes.js +15 -11
  42. package/dist/internal/nodes/extendedAttributes.js.map +1 -1
  43. package/dist/internal/nodes/extendedAttributes.test.js +19 -1
  44. package/dist/internal/nodes/extendedAttributes.test.js.map +1 -1
  45. package/dist/internal/nodes/index.test.js +1 -0
  46. package/dist/internal/nodes/index.test.js.map +1 -1
  47. package/dist/internal/nodes/interface.d.ts +1 -0
  48. package/dist/internal/nodes/nodesAccess.d.ts +2 -1
  49. package/dist/internal/nodes/nodesAccess.js +24 -5
  50. package/dist/internal/nodes/nodesAccess.js.map +1 -1
  51. package/dist/internal/nodes/nodesAccess.test.js +2 -2
  52. package/dist/internal/nodes/nodesAccess.test.js.map +1 -1
  53. package/dist/internal/nodes/nodesManagement.js +1 -0
  54. package/dist/internal/nodes/nodesManagement.js.map +1 -1
  55. package/dist/internal/photos/index.d.ts +11 -0
  56. package/dist/internal/photos/index.js +27 -0
  57. package/dist/internal/photos/index.js.map +1 -1
  58. package/dist/internal/photos/upload.d.ts +60 -0
  59. package/dist/internal/photos/upload.js +104 -0
  60. package/dist/internal/photos/upload.js.map +1 -0
  61. package/dist/internal/sharingPublic/apiService.d.ts +2 -2
  62. package/dist/internal/sharingPublic/apiService.js +2 -62
  63. package/dist/internal/sharingPublic/apiService.js.map +1 -1
  64. package/dist/internal/sharingPublic/cryptoCache.d.ts +0 -4
  65. package/dist/internal/sharingPublic/cryptoCache.js +0 -28
  66. package/dist/internal/sharingPublic/cryptoCache.js.map +1 -1
  67. package/dist/internal/sharingPublic/cryptoReporter.d.ts +16 -0
  68. package/dist/internal/sharingPublic/cryptoReporter.js +44 -0
  69. package/dist/internal/sharingPublic/cryptoReporter.js.map +1 -0
  70. package/dist/internal/sharingPublic/cryptoService.d.ts +3 -4
  71. package/dist/internal/sharingPublic/cryptoService.js +5 -43
  72. package/dist/internal/sharingPublic/cryptoService.js.map +1 -1
  73. package/dist/internal/sharingPublic/index.d.ts +21 -3
  74. package/dist/internal/sharingPublic/index.js +43 -12
  75. package/dist/internal/sharingPublic/index.js.map +1 -1
  76. package/dist/internal/sharingPublic/interface.d.ts +0 -1
  77. package/dist/internal/sharingPublic/nodes.d.ts +13 -0
  78. package/dist/internal/sharingPublic/nodes.js +28 -0
  79. package/dist/internal/sharingPublic/nodes.js.map +1 -0
  80. package/dist/internal/sharingPublic/session/session.d.ts +3 -3
  81. package/dist/internal/sharingPublic/session/url.test.js +3 -3
  82. package/dist/internal/sharingPublic/shares.d.ts +34 -0
  83. package/dist/internal/sharingPublic/shares.js +69 -0
  84. package/dist/internal/sharingPublic/shares.js.map +1 -0
  85. package/dist/internal/upload/apiService.d.ts +2 -2
  86. package/dist/internal/upload/apiService.js +11 -2
  87. package/dist/internal/upload/apiService.js.map +1 -1
  88. package/dist/internal/upload/controller.d.ts +8 -2
  89. package/dist/internal/upload/controller.js.map +1 -1
  90. package/dist/internal/upload/cryptoService.d.ts +2 -2
  91. package/dist/internal/upload/cryptoService.js.map +1 -1
  92. package/dist/internal/upload/fileUploader.d.ts +7 -3
  93. package/dist/internal/upload/fileUploader.js +6 -3
  94. package/dist/internal/upload/fileUploader.js.map +1 -1
  95. package/dist/internal/upload/fileUploader.test.js +23 -11
  96. package/dist/internal/upload/fileUploader.test.js.map +1 -1
  97. package/dist/internal/upload/interface.d.ts +3 -0
  98. package/dist/internal/upload/manager.d.ts +12 -11
  99. package/dist/internal/upload/manager.js +8 -2
  100. package/dist/internal/upload/manager.js.map +1 -1
  101. package/dist/internal/upload/manager.test.js +8 -0
  102. package/dist/internal/upload/manager.test.js.map +1 -1
  103. package/dist/internal/upload/streamUploader.d.ts +40 -26
  104. package/dist/internal/upload/streamUploader.js +15 -8
  105. package/dist/internal/upload/streamUploader.js.map +1 -1
  106. package/dist/internal/upload/streamUploader.test.js +11 -7
  107. package/dist/internal/upload/streamUploader.test.js.map +1 -1
  108. package/dist/protonDriveClient.d.ts +3 -3
  109. package/dist/protonDriveClient.js +4 -4
  110. package/dist/protonDriveClient.js.map +1 -1
  111. package/dist/protonDrivePhotosClient.d.ts +18 -2
  112. package/dist/protonDrivePhotosClient.js +19 -2
  113. package/dist/protonDrivePhotosClient.js.map +1 -1
  114. package/dist/protonDrivePublicLinkClient.d.ts +31 -4
  115. package/dist/protonDrivePublicLinkClient.js +52 -9
  116. package/dist/protonDrivePublicLinkClient.js.map +1 -1
  117. package/dist/transformers.d.ts +1 -1
  118. package/dist/transformers.js +1 -0
  119. package/dist/transformers.js.map +1 -1
  120. package/package.json +1 -1
  121. package/src/diagnostic/sdkDiagnostic.ts +1 -1
  122. package/src/interface/download.ts +4 -4
  123. package/src/interface/nodes.ts +4 -0
  124. package/src/interface/upload.ts +3 -3
  125. package/src/internal/apiService/apiService.test.ts +50 -0
  126. package/src/internal/apiService/apiService.ts +33 -2
  127. package/src/internal/apiService/driveTypes.ts +31 -48
  128. package/src/internal/apiService/errors.test.ts +10 -0
  129. package/src/internal/apiService/errors.ts +5 -1
  130. package/src/internal/asyncIteratorMap.test.ts +12 -0
  131. package/src/internal/asyncIteratorMap.ts +8 -0
  132. package/src/internal/download/fileDownloader.test.ts +8 -8
  133. package/src/internal/download/fileDownloader.ts +5 -5
  134. package/src/internal/nodes/apiService.test.ts +222 -16
  135. package/src/internal/nodes/apiService.ts +63 -49
  136. package/src/internal/nodes/cache.test.ts +1 -0
  137. package/src/internal/nodes/debouncer.test.ts +129 -0
  138. package/src/internal/nodes/debouncer.ts +93 -0
  139. package/src/internal/nodes/extendedAttributes.test.ts +23 -1
  140. package/src/internal/nodes/extendedAttributes.ts +26 -18
  141. package/src/internal/nodes/index.test.ts +1 -0
  142. package/src/internal/nodes/interface.ts +1 -0
  143. package/src/internal/nodes/nodesAccess.test.ts +2 -2
  144. package/src/internal/nodes/nodesAccess.ts +30 -5
  145. package/src/internal/nodes/nodesManagement.ts +1 -0
  146. package/src/internal/photos/index.ts +62 -0
  147. package/src/internal/photos/upload.ts +212 -0
  148. package/src/internal/sharingPublic/apiService.ts +5 -86
  149. package/src/internal/sharingPublic/cryptoCache.ts +0 -34
  150. package/src/internal/sharingPublic/cryptoReporter.ts +73 -0
  151. package/src/internal/sharingPublic/cryptoService.ts +4 -80
  152. package/src/internal/sharingPublic/index.ts +68 -6
  153. package/src/internal/sharingPublic/interface.ts +0 -9
  154. package/src/internal/sharingPublic/nodes.ts +37 -0
  155. package/src/internal/sharingPublic/session/apiService.ts +1 -1
  156. package/src/internal/sharingPublic/session/session.ts +3 -3
  157. package/src/internal/sharingPublic/session/url.test.ts +3 -3
  158. package/src/internal/sharingPublic/shares.ts +86 -0
  159. package/src/internal/upload/apiService.ts +15 -4
  160. package/src/internal/upload/controller.ts +2 -2
  161. package/src/internal/upload/cryptoService.ts +2 -2
  162. package/src/internal/upload/fileUploader.test.ts +25 -11
  163. package/src/internal/upload/fileUploader.ts +16 -3
  164. package/src/internal/upload/interface.ts +3 -0
  165. package/src/internal/upload/manager.test.ts +8 -0
  166. package/src/internal/upload/manager.ts +20 -10
  167. package/src/internal/upload/streamUploader.test.ts +32 -15
  168. package/src/internal/upload/streamUploader.ts +43 -30
  169. package/src/protonDriveClient.ts +4 -4
  170. package/src/protonDrivePhotosClient.ts +46 -6
  171. package/src/protonDrivePublicLinkClient.ts +93 -12
  172. package/src/transformers.ts +2 -0
  173. package/dist/internal/sharingPublic/manager.d.ts +0 -19
  174. package/dist/internal/sharingPublic/manager.js +0 -81
  175. package/dist/internal/sharingPublic/manager.js.map +0 -1
  176. 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
- private driveCrypto: DriveCrypto,
11
- private nodesService: NodesService,
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('revisionUid'));
135
+ startUploadSpy = jest.spyOn(uploader as any, 'startUpload').mockReturnValue(Promise.resolve({
136
+ nodeRevisionUid: 'revisionUid',
137
+ nodeUid: 'nodeUid'
138
+ }));
135
139
  });
136
140
 
137
- describe('writeFile', () => {
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.writeFile(file, thumbnails, onProgress);
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.writeFile(file, thumbnails, onProgress);
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.writeFile(file, thumbnails, onProgress);
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.writeFile(file, thumbnails, onProgress);
174
+ await uploader.uploadFromFile(file, thumbnails, onProgress);
171
175
 
172
- await expect(uploader.writeFile(file, thumbnails, onProgress)).rejects.toThrow('Upload already started');
176
+ await expect(uploader.uploadFromFile(file, thumbnails, onProgress)).rejects.toThrow('Upload already started');
173
177
  });
174
178
  });
175
179
 
176
- describe('writeStream', () => {
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.writeStream(stream, thumbnails, onProgress);
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.writeStream(stream, thumbnails, onProgress);
192
+ await uploader.uploadFromStream(stream, thumbnails, onProgress);
189
193
 
190
- await expect(uploader.writeStream(stream, thumbnails, onProgress)).rejects.toThrow(
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 writeFile(
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 writeStream(
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
- private logger: Logger;
18
+ protected logger: Logger;
19
19
 
20
20
  constructor(
21
21
  telemetry: ProtonDriveTelemetry,
22
- private apiService: UploadAPIService,
23
- private cryptoService: UploadCryptoService,
24
- private nodesService: NodesService,
25
- private clientUid: string | undefined,
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?: number;
264
- blockSizes?: number[];
265
- digests?: {
266
- sha1?: string;
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(extendedAttributes);
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(revisionDraft, expect.anything(), {
151
- size: metadata.expectedSize,
152
- blockSizes: metadata.expectedSize
153
- ? [
154
- ...Array(numberOfExpectedBlocks - 1).fill(FILE_CHUNK_SIZE),
155
- metadata.expectedSize % FILE_CHUNK_SIZE,
156
- ]
157
- : [],
158
- modificationTime: undefined,
159
- digests: {
160
- sha1: expect.anything(),
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
- mediaType: '',
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
- private logger: Logger;
58
+ protected logger: Logger;
59
59
 
60
- private digests: UploadDigests;
61
- private controller: UploadController;
62
- private abortController: AbortController;
60
+ protected digests: UploadDigests;
61
+ protected controller: UploadController;
62
+ protected abortController: AbortController;
63
63
 
64
- private encryptedThumbnails = new Map<ThumbnailType, EncryptedThumbnail>();
65
- private encryptedBlocks = new Map<number, EncryptedBlock>();
66
- private encryptionFinished = false;
64
+ protected encryptedThumbnails = new Map<ThumbnailType, EncryptedThumbnail>();
65
+ protected encryptedBlocks = new Map<number, EncryptedBlock>();
66
+ protected encryptionFinished = false;
67
67
 
68
- private ongoingUploads = new Map<
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
- private uploadedThumbnails: ({ type: ThumbnailType } & EncryptedBlockMetadata)[] = [];
76
- private uploadedBlocks: ({ index: number } & EncryptedBlockMetadata)[] = [];
75
+ protected uploadedThumbnails: ({ type: ThumbnailType } & EncryptedBlockMetadata)[] = [];
76
+ protected uploadedBlocks: ({ index: number } & EncryptedBlockMetadata)[] = [];
77
77
 
78
78
  constructor(
79
- private telemetry: UploadTelemetry,
80
- private apiService: UploadAPIService,
81
- private cryptoService: UploadCryptoService,
82
- private uploadManager: UploadManager,
83
- private blockVerifier: BlockVerifier,
84
- private revisionDraft: NodeRevisionDraft,
85
- private metadata: UploadMetadata,
86
- private onFinish: (failure: boolean) => Promise<void>,
87
- private signal?: AbortSignal,
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 = new UploadController();
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 this.revisionDraft.nodeRevisionUid;
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
- private async commitFile(thumbnails: Thumbnail[]) {
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: uploadedBlocks.map((block) => block.originalSize),
219
+ blockSizes: this.uploadedBlockSizes,
218
220
  digests: this.digests.digests(),
219
221
  };
220
- await this.uploadManager.commitDraft(this.revisionDraft, this.manifest, extendedAttributes);
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
- private verifyIntegrity(thumbnails: Thumbnail[]) {
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
- private get manifest(): Uint8Array {
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 = [
@@ -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.writeToStream(stream, (downloadedBytes) => { ... });
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.writeStream(stream, thumbnails, (uploadedBytes) => { ... });
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 { convertInternalMissingNodeIterator, convertInternalNodeIterator, getUid, getUids } from './transformers';
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, initPhotosModule } from './internal/photos';
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 initUploadModule>;
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 = initUploadModule(
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(name: string, metadata: UploadMetadata, signal?: AbortSignal): Promise<FileUploader> {
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);