@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.
Files changed (171) hide show
  1. package/dist/interface/nodes.d.ts +4 -0
  2. package/dist/interface/nodes.js.map +1 -1
  3. package/dist/internal/apiService/errorCodes.d.ts +1 -0
  4. package/dist/internal/apiService/errors.d.ts +3 -0
  5. package/dist/internal/apiService/errors.js +7 -1
  6. package/dist/internal/apiService/errors.js.map +1 -1
  7. package/dist/internal/devices/interface.d.ts +1 -1
  8. package/dist/internal/devices/manager.js +1 -1
  9. package/dist/internal/devices/manager.js.map +1 -1
  10. package/dist/internal/devices/manager.test.js +3 -3
  11. package/dist/internal/devices/manager.test.js.map +1 -1
  12. package/dist/internal/events/apiService.js +1 -1
  13. package/dist/internal/events/apiService.js.map +1 -1
  14. package/dist/internal/events/coreEventManager.js +1 -1
  15. package/dist/internal/events/coreEventManager.js.map +1 -1
  16. package/dist/internal/events/coreEventManager.test.js +18 -24
  17. package/dist/internal/events/coreEventManager.test.js.map +1 -1
  18. package/dist/internal/events/index.d.ts +3 -4
  19. package/dist/internal/events/index.js +4 -4
  20. package/dist/internal/events/index.js.map +1 -1
  21. package/dist/internal/events/interface.d.ts +3 -0
  22. package/dist/internal/nodes/apiService.d.ts +12 -3
  23. package/dist/internal/nodes/apiService.js +54 -13
  24. package/dist/internal/nodes/apiService.js.map +1 -1
  25. package/dist/internal/nodes/apiService.test.js +35 -2
  26. package/dist/internal/nodes/apiService.test.js.map +1 -1
  27. package/dist/internal/nodes/cache.test.js +1 -0
  28. package/dist/internal/nodes/cache.test.js.map +1 -1
  29. package/dist/internal/nodes/cryptoService.d.ts +1 -1
  30. package/dist/internal/nodes/cryptoService.js +1 -1
  31. package/dist/internal/nodes/cryptoService.js.map +1 -1
  32. package/dist/internal/nodes/cryptoService.test.js +4 -4
  33. package/dist/internal/nodes/cryptoService.test.js.map +1 -1
  34. package/dist/internal/nodes/errors.d.ts +4 -0
  35. package/dist/internal/nodes/errors.js +9 -0
  36. package/dist/internal/nodes/errors.js.map +1 -0
  37. package/dist/internal/nodes/extendedAttributes.d.ts +2 -2
  38. package/dist/internal/nodes/extendedAttributes.js +15 -11
  39. package/dist/internal/nodes/extendedAttributes.js.map +1 -1
  40. package/dist/internal/nodes/extendedAttributes.test.js +19 -1
  41. package/dist/internal/nodes/extendedAttributes.test.js.map +1 -1
  42. package/dist/internal/nodes/index.test.js +2 -1
  43. package/dist/internal/nodes/index.test.js.map +1 -1
  44. package/dist/internal/nodes/interface.d.ts +5 -1
  45. package/dist/internal/nodes/nodesAccess.d.ts +3 -3
  46. package/dist/internal/nodes/nodesAccess.js +25 -15
  47. package/dist/internal/nodes/nodesAccess.js.map +1 -1
  48. package/dist/internal/nodes/nodesAccess.test.js +48 -8
  49. package/dist/internal/nodes/nodesAccess.test.js.map +1 -1
  50. package/dist/internal/nodes/nodesManagement.d.ts +2 -0
  51. package/dist/internal/nodes/nodesManagement.js +87 -9
  52. package/dist/internal/nodes/nodesManagement.js.map +1 -1
  53. package/dist/internal/nodes/nodesManagement.test.js +81 -5
  54. package/dist/internal/nodes/nodesManagement.test.js.map +1 -1
  55. package/dist/internal/photos/albums.d.ts +9 -7
  56. package/dist/internal/photos/albums.js +26 -13
  57. package/dist/internal/photos/albums.js.map +1 -1
  58. package/dist/internal/photos/apiService.d.ts +34 -3
  59. package/dist/internal/photos/apiService.js +96 -3
  60. package/dist/internal/photos/apiService.js.map +1 -1
  61. package/dist/internal/photos/index.d.ts +31 -4
  62. package/dist/internal/photos/index.js +57 -7
  63. package/dist/internal/photos/index.js.map +1 -1
  64. package/dist/internal/photos/interface.d.ts +25 -1
  65. package/dist/internal/photos/shares.d.ts +43 -0
  66. package/dist/internal/photos/shares.js +112 -0
  67. package/dist/internal/photos/shares.js.map +1 -0
  68. package/dist/internal/photos/timeline.d.ts +15 -0
  69. package/dist/internal/photos/timeline.js +22 -0
  70. package/dist/internal/photos/timeline.js.map +1 -0
  71. package/dist/internal/photos/upload.d.ts +59 -0
  72. package/dist/internal/photos/upload.js +104 -0
  73. package/dist/internal/photos/upload.js.map +1 -0
  74. package/dist/internal/shares/manager.d.ts +1 -1
  75. package/dist/internal/shares/manager.js +4 -4
  76. package/dist/internal/shares/manager.js.map +1 -1
  77. package/dist/internal/shares/manager.test.js +7 -7
  78. package/dist/internal/shares/manager.test.js.map +1 -1
  79. package/dist/internal/sharing/interface.d.ts +1 -1
  80. package/dist/internal/sharing/sharingAccess.js +1 -1
  81. package/dist/internal/sharing/sharingAccess.js.map +1 -1
  82. package/dist/internal/sharing/sharingAccess.test.js +1 -1
  83. package/dist/internal/sharing/sharingAccess.test.js.map +1 -1
  84. package/dist/internal/sharingPublic/apiService.js +2 -0
  85. package/dist/internal/sharingPublic/apiService.js.map +1 -1
  86. package/dist/internal/upload/apiService.d.ts +2 -2
  87. package/dist/internal/upload/apiService.js +1 -1
  88. package/dist/internal/upload/apiService.js.map +1 -1
  89. package/dist/internal/upload/cryptoService.d.ts +2 -2
  90. package/dist/internal/upload/cryptoService.js.map +1 -1
  91. package/dist/internal/upload/fileUploader.d.ts +1 -0
  92. package/dist/internal/upload/fileUploader.js +3 -0
  93. package/dist/internal/upload/fileUploader.js.map +1 -1
  94. package/dist/internal/upload/interface.d.ts +3 -0
  95. package/dist/internal/upload/manager.d.ts +12 -11
  96. package/dist/internal/upload/manager.js +8 -2
  97. package/dist/internal/upload/manager.js.map +1 -1
  98. package/dist/internal/upload/manager.test.js +8 -0
  99. package/dist/internal/upload/manager.test.js.map +1 -1
  100. package/dist/internal/upload/streamUploader.d.ts +34 -24
  101. package/dist/internal/upload/streamUploader.js +7 -4
  102. package/dist/internal/upload/streamUploader.js.map +1 -1
  103. package/dist/internal/upload/streamUploader.test.js +1 -1
  104. package/dist/internal/upload/streamUploader.test.js.map +1 -1
  105. package/dist/protonDriveClient.d.ts +20 -3
  106. package/dist/protonDriveClient.js +23 -4
  107. package/dist/protonDriveClient.js.map +1 -1
  108. package/dist/protonDrivePhotosClient.d.ts +102 -12
  109. package/dist/protonDrivePhotosClient.js +149 -29
  110. package/dist/protonDrivePhotosClient.js.map +1 -1
  111. package/dist/transformers.d.ts +1 -1
  112. package/dist/transformers.js +1 -0
  113. package/dist/transformers.js.map +1 -1
  114. package/package.json +1 -1
  115. package/src/interface/nodes.ts +4 -0
  116. package/src/internal/apiService/errorCodes.ts +1 -0
  117. package/src/internal/apiService/errors.ts +6 -0
  118. package/src/internal/devices/interface.ts +1 -1
  119. package/src/internal/devices/manager.test.ts +3 -3
  120. package/src/internal/devices/manager.ts +1 -1
  121. package/src/internal/events/apiService.ts +1 -1
  122. package/src/internal/events/coreEventManager.test.ts +21 -27
  123. package/src/internal/events/coreEventManager.ts +1 -1
  124. package/src/internal/events/index.ts +3 -4
  125. package/src/internal/events/interface.ts +4 -0
  126. package/src/internal/nodes/apiService.test.ts +58 -1
  127. package/src/internal/nodes/apiService.ts +104 -17
  128. package/src/internal/nodes/cache.test.ts +1 -0
  129. package/src/internal/nodes/cryptoService.test.ts +8 -8
  130. package/src/internal/nodes/cryptoService.ts +1 -1
  131. package/src/internal/nodes/errors.ts +5 -0
  132. package/src/internal/nodes/extendedAttributes.test.ts +23 -1
  133. package/src/internal/nodes/extendedAttributes.ts +26 -18
  134. package/src/internal/nodes/index.test.ts +2 -1
  135. package/src/internal/nodes/interface.ts +6 -1
  136. package/src/internal/nodes/nodesAccess.test.ts +68 -8
  137. package/src/internal/nodes/nodesAccess.ts +42 -15
  138. package/src/internal/nodes/nodesManagement.test.ts +100 -5
  139. package/src/internal/nodes/nodesManagement.ts +101 -13
  140. package/src/internal/photos/albums.ts +31 -12
  141. package/src/internal/photos/apiService.ts +159 -4
  142. package/src/internal/photos/index.ts +116 -9
  143. package/src/internal/photos/interface.ts +23 -1
  144. package/src/internal/photos/shares.ts +134 -0
  145. package/src/internal/photos/timeline.ts +24 -0
  146. package/src/internal/photos/upload.ts +209 -0
  147. package/src/internal/shares/manager.test.ts +7 -7
  148. package/src/internal/shares/manager.ts +4 -4
  149. package/src/internal/sharing/interface.ts +1 -1
  150. package/src/internal/sharing/sharingAccess.test.ts +1 -1
  151. package/src/internal/sharing/sharingAccess.ts +1 -1
  152. package/src/internal/sharingPublic/apiService.ts +2 -0
  153. package/src/internal/upload/apiService.ts +3 -3
  154. package/src/internal/upload/cryptoService.ts +2 -2
  155. package/src/internal/upload/fileUploader.ts +12 -0
  156. package/src/internal/upload/interface.ts +3 -0
  157. package/src/internal/upload/manager.test.ts +8 -0
  158. package/src/internal/upload/manager.ts +20 -10
  159. package/src/internal/upload/streamUploader.test.ts +17 -12
  160. package/src/internal/upload/streamUploader.ts +35 -27
  161. package/src/protonDriveClient.ts +33 -4
  162. package/src/protonDrivePhotosClient.ts +251 -32
  163. package/src/transformers.ts +2 -0
  164. package/dist/internal/photos/cache.d.ts +0 -6
  165. package/dist/internal/photos/cache.js +0 -15
  166. package/dist/internal/photos/cache.js.map +0 -1
  167. package/dist/internal/photos/photosTimeline.d.ts +0 -10
  168. package/dist/internal/photos/photosTimeline.js +0 -19
  169. package/dist/internal/photos/photosTimeline.js.map +0 -1
  170. package/src/internal/photos/cache.ts +0 -11
  171. 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('getMyFilesIDs', () => {
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.getMyFilesIDs();
75
- const result = await manager.getMyFilesIDs();
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.getMyFilesIDs();
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.getMyFilesIDs()).rejects.toThrow('Some error');
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, 'getMyFilesIDs').mockResolvedValue({ volumeId: 'volumeId' } as VolumeShareNodeIDs);
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, 'getMyFilesIDs').mockResolvedValue({ volumeId: 'volumeId' } as VolumeShareNodeIDs);
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 getMyFilesIDs(): Promise<VolumeShareNodeIDs> {
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.getMyFilesIDs();
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.getMyFilesIDs()).volumeId === volumeId;
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.getMyFilesIDs();
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
- getMyFilesIDs(): Promise<{ volumeId: string }>;
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
- getMyFilesIDs: jest.fn().mockResolvedValue({ volumeId: 'volumeId' }),
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.getMyFilesIDs();
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
- private apiService: DriveAPIService,
57
- private clientUid: string | undefined,
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, // TODO
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
- 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;
@@ -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
- 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) {
@@ -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(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(),
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
- 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 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
- private async commitFile(thumbnails: Thumbnail[]) {
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: uploadedBlocks.map((block) => block.originalSize),
214
+ blockSizes: this.uploadedBlockSizes,
218
215
  digests: this.digests.digests(),
219
216
  };
220
- await this.uploadManager.commitDraft(this.revisionDraft, this.manifest, extendedAttributes);
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
- private verifyIntegrity(thumbnails: Thumbnail[]) {
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
- private get manifest(): Uint8Array {
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 = [