@protontech/drive-sdk 0.12.1 → 0.13.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 (154) hide show
  1. package/dist/crypto/driveCrypto.d.ts +1 -0
  2. package/dist/crypto/driveCrypto.js +1 -0
  3. package/dist/crypto/driveCrypto.js.map +1 -1
  4. package/dist/interface/events.d.ts +1 -1
  5. package/dist/interface/featureFlags.d.ts +2 -1
  6. package/dist/interface/featureFlags.js +1 -0
  7. package/dist/interface/featureFlags.js.map +1 -1
  8. package/dist/interface/httpClient.d.ts +1 -0
  9. package/dist/interface/nodes.d.ts +7 -0
  10. package/dist/interface/nodes.js.map +1 -1
  11. package/dist/interface/telemetry.d.ts +4 -0
  12. package/dist/interface/telemetry.js.map +1 -1
  13. package/dist/internal/apiService/apiService.d.ts +1 -0
  14. package/dist/internal/apiService/apiService.js +16 -7
  15. package/dist/internal/apiService/apiService.js.map +1 -1
  16. package/dist/internal/apiService/apiService.test.js +24 -0
  17. package/dist/internal/apiService/apiService.test.js.map +1 -1
  18. package/dist/internal/apiService/driveTypes.d.ts +162 -101
  19. package/dist/internal/download/telemetry.js +4 -0
  20. package/dist/internal/download/telemetry.js.map +1 -1
  21. package/dist/internal/download/telemetry.test.js +5 -0
  22. package/dist/internal/download/telemetry.test.js.map +1 -1
  23. package/dist/internal/events/index.js +2 -2
  24. package/dist/internal/events/index.js.map +1 -1
  25. package/dist/internal/events/interface.d.ts +1 -1
  26. package/dist/internal/nodes/apiService.d.ts +4 -0
  27. package/dist/internal/nodes/apiService.js +4 -0
  28. package/dist/internal/nodes/apiService.js.map +1 -1
  29. package/dist/internal/nodes/apiService.test.js +8 -0
  30. package/dist/internal/nodes/apiService.test.js.map +1 -1
  31. package/dist/internal/nodes/cryptoService.js +3 -0
  32. package/dist/internal/nodes/cryptoService.js.map +1 -1
  33. package/dist/internal/nodes/extendedAttributes.d.ts +5 -5
  34. package/dist/internal/nodes/extendedAttributes.js +5 -14
  35. package/dist/internal/nodes/extendedAttributes.js.map +1 -1
  36. package/dist/internal/nodes/extendedAttributes.test.js +16 -22
  37. package/dist/internal/nodes/extendedAttributes.test.js.map +1 -1
  38. package/dist/internal/nodes/interface.d.ts +5 -0
  39. package/dist/internal/nodes/nodesManagement.d.ts +3 -3
  40. package/dist/internal/nodes/nodesManagement.js +7 -5
  41. package/dist/internal/nodes/nodesManagement.js.map +1 -1
  42. package/dist/internal/photos/albumsManager.js +1 -0
  43. package/dist/internal/photos/albumsManager.js.map +1 -1
  44. package/dist/internal/photos/nodes.d.ts +1 -1
  45. package/dist/internal/photos/nodes.js +2 -2
  46. package/dist/internal/photos/nodes.js.map +1 -1
  47. package/dist/internal/photos/upload.d.ts +5 -5
  48. package/dist/internal/photos/upload.js +8 -2
  49. package/dist/internal/photos/upload.js.map +1 -1
  50. package/dist/internal/sharing/apiService.js +1 -1
  51. package/dist/internal/sharing/apiService.js.map +1 -1
  52. package/dist/internal/sharingPublic/nodes.d.ts +1 -0
  53. package/dist/internal/upload/apiService.d.ts +45 -1
  54. package/dist/internal/upload/apiService.js +69 -1
  55. package/dist/internal/upload/apiService.js.map +1 -1
  56. package/dist/internal/upload/blockVerifier.d.ts +4 -1
  57. package/dist/internal/upload/blockVerifier.js +5 -0
  58. package/dist/internal/upload/blockVerifier.js.map +1 -1
  59. package/dist/internal/upload/cryptoService.d.ts +2 -2
  60. package/dist/internal/upload/cryptoService.js +1 -3
  61. package/dist/internal/upload/cryptoService.js.map +1 -1
  62. package/dist/internal/upload/fileUploader.d.ts +4 -3
  63. package/dist/internal/upload/fileUploader.js +17 -7
  64. package/dist/internal/upload/fileUploader.js.map +1 -1
  65. package/dist/internal/upload/index.d.ts +3 -3
  66. package/dist/internal/upload/index.js +17 -1
  67. package/dist/internal/upload/index.js.map +1 -1
  68. package/dist/internal/upload/index.test.d.ts +1 -0
  69. package/dist/internal/upload/index.test.js +71 -0
  70. package/dist/internal/upload/index.test.js.map +1 -0
  71. package/dist/internal/upload/interface.d.ts +2 -0
  72. package/dist/internal/upload/manager.d.ts +41 -2
  73. package/dist/internal/upload/manager.js +126 -44
  74. package/dist/internal/upload/manager.js.map +1 -1
  75. package/dist/internal/upload/manager.test.js +268 -1
  76. package/dist/internal/upload/manager.test.js.map +1 -1
  77. package/dist/internal/upload/smallFileUploader.d.ts +83 -0
  78. package/dist/internal/upload/smallFileUploader.js +197 -0
  79. package/dist/internal/upload/smallFileUploader.js.map +1 -0
  80. package/dist/internal/upload/smallFileUploader.test.d.ts +1 -0
  81. package/dist/internal/upload/smallFileUploader.test.js +358 -0
  82. package/dist/internal/upload/smallFileUploader.test.js.map +1 -0
  83. package/dist/internal/upload/streamReader.d.ts +4 -0
  84. package/dist/internal/upload/streamReader.js +37 -0
  85. package/dist/internal/upload/streamReader.js.map +1 -0
  86. package/dist/internal/upload/streamReader.test.d.ts +1 -0
  87. package/dist/internal/upload/streamReader.test.js +90 -0
  88. package/dist/internal/upload/streamReader.test.js.map +1 -0
  89. package/dist/internal/upload/streamUploader.d.ts +6 -0
  90. package/dist/internal/upload/streamUploader.js +3 -3
  91. package/dist/internal/upload/streamUploader.js.map +1 -1
  92. package/dist/internal/upload/telemetry.d.ts +3 -2
  93. package/dist/internal/upload/telemetry.js +5 -0
  94. package/dist/internal/upload/telemetry.js.map +1 -1
  95. package/dist/internal/upload/telemetry.test.js +6 -0
  96. package/dist/internal/upload/telemetry.test.js.map +1 -1
  97. package/dist/protonDrivePhotosClient.d.ts +1 -1
  98. package/dist/protonDrivePublicLinkClient.js +3 -1
  99. package/dist/protonDrivePublicLinkClient.js.map +1 -1
  100. package/dist/telemetry.d.ts +1 -0
  101. package/dist/telemetry.js +21 -0
  102. package/dist/telemetry.js.map +1 -1
  103. package/dist/telemetry.test.d.ts +1 -0
  104. package/dist/telemetry.test.js +37 -0
  105. package/dist/telemetry.test.js.map +1 -0
  106. package/dist/transformers.d.ts +1 -1
  107. package/dist/transformers.js +1 -0
  108. package/dist/transformers.js.map +1 -1
  109. package/package.json +1 -1
  110. package/src/crypto/driveCrypto.ts +2 -0
  111. package/src/interface/events.ts +1 -1
  112. package/src/interface/featureFlags.ts +1 -0
  113. package/src/interface/httpClient.ts +1 -0
  114. package/src/interface/nodes.ts +7 -0
  115. package/src/interface/telemetry.ts +4 -0
  116. package/src/internal/apiService/apiService.test.ts +30 -0
  117. package/src/internal/apiService/apiService.ts +23 -7
  118. package/src/internal/apiService/driveTypes.ts +162 -101
  119. package/src/internal/download/telemetry.test.ts +5 -0
  120. package/src/internal/download/telemetry.ts +5 -1
  121. package/src/internal/events/index.ts +2 -2
  122. package/src/internal/events/interface.ts +1 -1
  123. package/src/internal/nodes/apiService.test.ts +9 -0
  124. package/src/internal/nodes/apiService.ts +4 -0
  125. package/src/internal/nodes/cryptoService.ts +11 -1
  126. package/src/internal/nodes/extendedAttributes.test.ts +25 -25
  127. package/src/internal/nodes/extendedAttributes.ts +10 -19
  128. package/src/internal/nodes/interface.ts +5 -0
  129. package/src/internal/nodes/nodesManagement.ts +8 -6
  130. package/src/internal/photos/albumsManager.ts +1 -0
  131. package/src/internal/photos/nodes.ts +2 -2
  132. package/src/internal/photos/upload.ts +23 -10
  133. package/src/internal/sharing/apiService.ts +5 -5
  134. package/src/internal/upload/apiService.ts +167 -2
  135. package/src/internal/upload/blockVerifier.ts +12 -0
  136. package/src/internal/upload/cryptoService.ts +10 -10
  137. package/src/internal/upload/fileUploader.ts +20 -7
  138. package/src/internal/upload/index.test.ts +99 -0
  139. package/src/internal/upload/index.ts +45 -4
  140. package/src/internal/upload/interface.ts +2 -0
  141. package/src/internal/upload/manager.test.ts +368 -2
  142. package/src/internal/upload/manager.ts +229 -78
  143. package/src/internal/upload/smallFileUploader.test.ts +491 -0
  144. package/src/internal/upload/smallFileUploader.ts +353 -0
  145. package/src/internal/upload/streamReader.test.ts +109 -0
  146. package/src/internal/upload/streamReader.ts +38 -0
  147. package/src/internal/upload/streamUploader.ts +1 -1
  148. package/src/internal/upload/telemetry.test.ts +6 -0
  149. package/src/internal/upload/telemetry.ts +8 -2
  150. package/src/protonDrivePhotosClient.ts +1 -1
  151. package/src/protonDrivePublicLinkClient.ts +2 -0
  152. package/src/telemetry.test.ts +40 -0
  153. package/src/telemetry.ts +22 -0
  154. package/src/transformers.ts +2 -0
@@ -1,6 +1,7 @@
1
1
  import { c } from 'ttag';
2
2
 
3
- import { Logger, ProtonDriveTelemetry, UploadMetadata } from '../../interface';
3
+ import { PrivateKey, SessionKey } from '../../crypto';
4
+ import { Logger, ProtonDriveTelemetry, ThumbnailType, UploadMetadata } from '../../interface';
4
5
  import { ValidationError, NodeWithSameNameExistsValidationError } from '../../errors';
5
6
  import { ErrorCode } from '../apiService';
6
7
  import { generateFileExtendedAttributes } from '../nodes';
@@ -8,6 +9,7 @@ import { UploadAPIService } from './apiService';
8
9
  import { UploadCryptoService } from './cryptoService';
9
10
  import { NodeRevisionDraft, NodesService, NodeCrypto } from './interface';
10
11
  import { makeNodeUid, splitNodeUid } from '../uids';
12
+ import { reduceSizePrecision } from '../../telemetry';
11
13
 
12
14
  /**
13
15
  * UploadManager is responsible for creating and deleting draft nodes
@@ -32,20 +34,11 @@ export class UploadManager {
32
34
  }
33
35
 
34
36
  async createDraftNode(parentFolderUid: string, name: string, metadata: UploadMetadata): Promise<NodeRevisionDraft> {
35
- const parentKeys = await this.nodesService.getNodeKeys(parentFolderUid);
36
- if (!parentKeys.hashKey) {
37
- throw new ValidationError(c('Error').t`Creating files in non-folders is not allowed`);
38
- }
39
-
40
- const generatedNodeCrypto = await this.cryptoService.generateFileCrypto(
41
- parentFolderUid,
42
- { key: parentKeys.key, hashKey: parentKeys.hashKey },
43
- name,
44
- );
37
+ const { parentHashKey, ...generatedNodeCrypto } = await this.generateNewFileCrypto(parentFolderUid, name);
45
38
 
46
39
  const { nodeUid, nodeRevisionUid } = await this.createDraftOnAPI(
47
40
  parentFolderUid,
48
- parentKeys.hashKey,
41
+ parentHashKey,
49
42
  name,
50
43
  metadata,
51
44
  generatedNodeCrypto,
@@ -60,7 +53,7 @@ export class UploadManager {
60
53
  signingKeys: generatedNodeCrypto.signingKeys,
61
54
  },
62
55
  parentNodeKeys: {
63
- hashKey: parentKeys.hashKey,
56
+ hashKey: parentHashKey,
64
57
  },
65
58
  newNodeInfo: {
66
59
  parentUid: parentFolderUid,
@@ -71,6 +64,57 @@ export class UploadManager {
71
64
  };
72
65
  }
73
66
 
67
+ async generateNewFileCrypto(
68
+ parentFolderUid: string,
69
+ name: string,
70
+ ): Promise<NodeCrypto & { parentHashKey: Uint8Array<ArrayBuffer> }> {
71
+ const parentKeys = await this.nodesService.getNodeKeys(parentFolderUid);
72
+ if (!parentKeys.hashKey) {
73
+ throw new ValidationError(c('Error').t`Creating files in non-folders is not allowed`);
74
+ }
75
+
76
+ const generatedNodeCrypto = await this.cryptoService.generateFileCrypto(
77
+ parentFolderUid,
78
+ { key: parentKeys.key, hashKey: parentKeys.hashKey },
79
+ name,
80
+ );
81
+
82
+ return {
83
+ ...generatedNodeCrypto,
84
+ parentHashKey: parentKeys.hashKey,
85
+ };
86
+ }
87
+
88
+ async getExistingFileNodeCrypto(nodeUid: string): Promise<{
89
+ key: PrivateKey;
90
+ contentKeyPacket: Uint8Array<ArrayBuffer>;
91
+ contentKeyPacketSessionKey: SessionKey;
92
+ signingKeys: NodeCrypto['signingKeys'];
93
+ }> {
94
+ const node = await this.nodesService.getNode(nodeUid);
95
+ const nodeKeys = await this.nodesService.getNodeKeys(nodeUid);
96
+
97
+ if (!node.activeRevision?.ok || !nodeKeys.contentKeyPacketSessionKey) {
98
+ throw new ValidationError(c('Error').t`Creating revisions in non-files is not allowed`);
99
+ }
100
+
101
+ if (!nodeKeys.contentKeyPacket) {
102
+ throw new ValidationError(c('Error').t`Content key packet is required for small revision upload`);
103
+ }
104
+
105
+ const signingKeys = await this.cryptoService.getSigningKeysForExistingNode({
106
+ nodeUid,
107
+ parentNodeUid: node.parentUid,
108
+ });
109
+
110
+ return {
111
+ key: nodeKeys.key,
112
+ contentKeyPacket: nodeKeys.contentKeyPacket,
113
+ contentKeyPacketSessionKey: nodeKeys.contentKeyPacketSessionKey,
114
+ signingKeys,
115
+ };
116
+ }
117
+
74
118
  private async createDraftOnAPI(
75
119
  parentFolderUid: string,
76
120
  parentHashKey: Uint8Array<ArrayBuffer>,
@@ -86,7 +130,7 @@ export class UploadManager {
86
130
  armoredEncryptedName: generatedNodeCrypto.encryptedNode.encryptedName,
87
131
  hash: generatedNodeCrypto.encryptedNode.hash,
88
132
  mediaType: metadata.mediaType,
89
- intendedUploadSize: metadata.expectedSize,
133
+ intendedUploadSize: reduceSizePrecision(metadata.expectedSize),
90
134
  armoredNodeKey: generatedNodeCrypto.nodeKeys.encrypted.armoredKey,
91
135
  armoredNodePassphrase: generatedNodeCrypto.nodeKeys.encrypted.armoredPassphrase,
92
136
  armoredNodePassphraseSignature: generatedNodeCrypto.nodeKeys.encrypted.armoredPassphraseSignature,
@@ -97,80 +141,187 @@ export class UploadManager {
97
141
  });
98
142
  return result;
99
143
  } catch (error: unknown) {
100
- if (error instanceof ValidationError) {
101
- if (error.code === ErrorCode.ALREADY_EXISTS) {
102
- this.logger.info(`Node with given name already exists`);
103
-
104
- const typedDetails = error.details as
105
- | {
106
- ConflictLinkID: string;
107
- ConflictRevisionID?: string;
108
- ConflictDraftRevisionID?: string;
109
- ConflictDraftClientUID?: string;
144
+ return this.handleConflictError(parentFolderUid, metadata, error, async () => {
145
+ return this.createDraftOnAPI(parentFolderUid, parentHashKey, name, metadata, generatedNodeCrypto);
146
+ });
147
+ }
148
+ }
149
+
150
+ async uploadFile(
151
+ parentFolderUid: string,
152
+ nodeCrypto: NodeCrypto,
153
+ metadata: UploadMetadata,
154
+ commitPayload: {
155
+ armoredManifestSignature: string;
156
+ armoredExtendedAttributes: string;
157
+ },
158
+ encryptedBlock:
159
+ | {
160
+ encryptedData: Uint8Array<ArrayBuffer>;
161
+ armoredSignature: string;
162
+ verificationToken: Uint8Array<ArrayBuffer>;
163
+ }
164
+ | undefined,
165
+ encryptedThumbnails: { type: ThumbnailType; encryptedData: Uint8Array<ArrayBuffer> }[],
166
+ signal?: AbortSignal,
167
+ ): Promise<{ nodeUid: string; nodeRevisionUid: string }> {
168
+ try {
169
+ const result = await this.apiService.uploadSmallFile(
170
+ parentFolderUid,
171
+ {
172
+ armoredEncryptedName: nodeCrypto.encryptedNode.encryptedName,
173
+ hash: nodeCrypto.encryptedNode.hash,
174
+ mediaType: metadata.mediaType ?? 'application/octet-stream',
175
+ armoredNodeKey: nodeCrypto.nodeKeys.encrypted.armoredKey,
176
+ armoredNodePassphrase: nodeCrypto.nodeKeys.encrypted.armoredPassphrase,
177
+ armoredNodePassphraseSignature: nodeCrypto.nodeKeys.encrypted.armoredPassphraseSignature,
178
+ base64ContentKeyPacket: nodeCrypto.contentKey.encrypted.base64ContentKeyPacket,
179
+ armoredContentKeyPacketSignature: nodeCrypto.contentKey.encrypted.armoredContentKeyPacketSignature,
180
+ armoredExtendedAttributes: commitPayload.armoredExtendedAttributes,
181
+ signatureEmail: nodeCrypto.signingKeys.email ?? null,
182
+ },
183
+ {
184
+ armoredManifestSignature: commitPayload.armoredManifestSignature,
185
+ block: encryptedBlock
186
+ ? {
187
+ encryptedData: encryptedBlock.encryptedData,
188
+ armoredSignature: encryptedBlock.armoredSignature,
189
+ verificationToken: encryptedBlock.verificationToken,
110
190
  }
111
- | undefined;
112
-
113
- // If the client doesn't specify the client UID, it should
114
- // never be considered own draft.
115
- const isOwnDraftConflict =
116
- typedDetails?.ConflictDraftRevisionID &&
117
- this.clientUid &&
118
- typedDetails?.ConflictDraftClientUID === this.clientUid;
119
-
120
- // If there is existing draft created by this client,
121
- // automatically delete it and try to create a new one
122
- // with the same name again.
123
- if (
124
- typedDetails?.ConflictDraftRevisionID &&
125
- (isOwnDraftConflict || metadata.overrideExistingDraftByOtherClient)
126
- ) {
127
- const existingDraftNodeUid = makeNodeUid(
128
- splitNodeUid(parentFolderUid).volumeId,
129
- typedDetails.ConflictLinkID,
130
- );
191
+ : undefined,
192
+ thumbnails: encryptedThumbnails,
193
+ },
194
+ signal,
195
+ );
196
+ await this.nodesService.notifyChildCreated(parentFolderUid);
197
+ return result;
198
+ } catch (error: unknown) {
199
+ return this.handleConflictError(parentFolderUid, metadata, error, async () => {
200
+ return this.uploadFile(
201
+ parentFolderUid,
202
+ nodeCrypto,
203
+ metadata,
204
+ commitPayload,
205
+ encryptedBlock,
206
+ encryptedThumbnails,
207
+ signal,
208
+ );
209
+ });
210
+ }
211
+ }
131
212
 
132
- let deleteFailed = false;
133
- try {
134
- this.logger.warn(
135
- `Deleting existing draft node ${existingDraftNodeUid} by ${typedDetails.ConflictDraftClientUID}`,
136
- );
137
- await this.apiService.deleteDraft(existingDraftNodeUid);
138
- } catch (deleteDraftError: unknown) {
139
- // Do not throw, let throw the conflict error.
140
- deleteFailed = true;
141
- this.logger.error('Failed to delete existing draft node', deleteDraftError);
142
- }
143
- if (!deleteFailed) {
144
- return this.createDraftOnAPI(
145
- parentFolderUid,
146
- parentHashKey,
147
- name,
148
- metadata,
149
- generatedNodeCrypto,
150
- );
151
- }
152
- }
213
+ async uploadSmallRevision(
214
+ nodeUid: string,
215
+ nodeCrypto: Pick<NodeCrypto, 'signingKeys'>,
216
+ commitPayload: {
217
+ armoredManifestSignature: string;
218
+ armoredExtendedAttributes: string;
219
+ },
220
+ encryptedBlock:
221
+ | {
222
+ encryptedData: Uint8Array<ArrayBuffer>;
223
+ armoredSignature: string;
224
+ verificationToken: Uint8Array<ArrayBuffer>;
225
+ }
226
+ | undefined,
227
+ encryptedThumbnails: { type: ThumbnailType; encryptedData: Uint8Array<ArrayBuffer> }[],
228
+ signal?: AbortSignal,
229
+ ): Promise<{ nodeUid: string; nodeRevisionUid: string }> {
230
+ const node = await this.nodesService.getNode(nodeUid);
231
+ if (!node.activeRevision?.ok) {
232
+ throw new ValidationError(c('Error').t`File has no revision`);
233
+ }
234
+ const result = await this.apiService.uploadSmallRevision(
235
+ nodeUid,
236
+ node.activeRevision.value.uid,
237
+ {
238
+ signatureEmail: nodeCrypto.signingKeys.email ?? null,
239
+ armoredExtendedAttributes: commitPayload.armoredExtendedAttributes,
240
+ },
241
+ {
242
+ armoredManifestSignature: commitPayload.armoredManifestSignature,
243
+ block: encryptedBlock,
244
+ thumbnails: encryptedThumbnails,
245
+ },
246
+ signal,
247
+ );
248
+ await this.nodesService.notifyNodeChanged(nodeUid);
249
+ return result;
250
+ }
251
+
252
+ private async handleConflictError(
253
+ parentFolderUid: string,
254
+ metadata: UploadMetadata,
255
+ error: unknown,
256
+ onRetryAfterDraftDeleted: () => Promise<{ nodeUid: string; nodeRevisionUid: string }>,
257
+ ): Promise<{ nodeUid: string; nodeRevisionUid: string }> {
258
+ if (error instanceof ValidationError) {
259
+ if (error.code === ErrorCode.ALREADY_EXISTS) {
260
+ this.logger.info(`Node with given name already exists`);
261
+
262
+ const typedDetails = error.details as
263
+ | {
264
+ ConflictLinkID: string;
265
+ ConflictRevisionID?: string;
266
+ ConflictDraftRevisionID?: string;
267
+ ConflictDraftClientUID?: string;
268
+ }
269
+ | undefined;
270
+
271
+ // If the client doesn't specify the client UID, it should
272
+ // never be considered own draft.
273
+ const isOwnDraftConflict =
274
+ typedDetails?.ConflictDraftRevisionID &&
275
+ this.clientUid &&
276
+ typedDetails?.ConflictDraftClientUID === this.clientUid;
277
+
278
+ // If there is existing draft created by this client,
279
+ // automatically delete it and try to create a new one
280
+ // with the same name again.
281
+ if (
282
+ typedDetails?.ConflictDraftRevisionID &&
283
+ (isOwnDraftConflict || metadata.overrideExistingDraftByOtherClient)
284
+ ) {
285
+ const existingDraftNodeUid = makeNodeUid(
286
+ splitNodeUid(parentFolderUid).volumeId,
287
+ typedDetails.ConflictLinkID,
288
+ );
153
289
 
154
- if (isOwnDraftConflict) {
290
+ let deleteFailed = false;
291
+ try {
155
292
  this.logger.warn(
156
- `Existing draft conflict by another client ${typedDetails.ConflictDraftClientUID}`,
293
+ `Deleting existing draft node ${existingDraftNodeUid} by ${typedDetails.ConflictDraftClientUID}`,
157
294
  );
295
+ await this.apiService.deleteDraft(existingDraftNodeUid);
296
+ } catch (deleteDraftError: unknown) {
297
+ // Do not throw, let throw the conflict error.
298
+ deleteFailed = true;
299
+ this.logger.error('Failed to delete existing draft node', deleteDraftError);
158
300
  }
301
+ if (!deleteFailed) {
302
+ return onRetryAfterDraftDeleted();
303
+ }
304
+ }
159
305
 
160
- const existingNodeUid = typedDetails
161
- ? makeNodeUid(splitNodeUid(parentFolderUid).volumeId, typedDetails.ConflictLinkID)
162
- : undefined;
163
-
164
- throw new NodeWithSameNameExistsValidationError(
165
- error.message,
166
- error.code,
167
- existingNodeUid,
168
- !!typedDetails?.ConflictDraftRevisionID,
306
+ if (isOwnDraftConflict) {
307
+ this.logger.warn(
308
+ `Existing draft conflict by another client ${typedDetails.ConflictDraftClientUID}`,
169
309
  );
170
310
  }
311
+
312
+ const existingNodeUid = typedDetails
313
+ ? makeNodeUid(splitNodeUid(parentFolderUid).volumeId, typedDetails.ConflictLinkID)
314
+ : undefined;
315
+
316
+ throw new NodeWithSameNameExistsValidationError(
317
+ error.message,
318
+ error.code,
319
+ existingNodeUid,
320
+ !!typedDetails?.ConflictDraftRevisionID,
321
+ );
171
322
  }
172
- throw error;
173
323
  }
324
+ throw error;
174
325
  }
175
326
 
176
327
  async deleteDraftNode(nodeUid: string): Promise<void> {
@@ -199,7 +350,7 @@ export class UploadManager {
199
350
 
200
351
  const { nodeRevisionUid } = await this.apiService.createDraftRevision(nodeUid, {
201
352
  currentRevisionUid: node.activeRevision.value.uid,
202
- intendedUploadSize: metadata.expectedSize,
353
+ intendedUploadSize: reduceSizePrecision(metadata.expectedSize),
203
354
  });
204
355
 
205
356
  return {