@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.
- package/dist/crypto/driveCrypto.d.ts +1 -0
- package/dist/crypto/driveCrypto.js +1 -0
- package/dist/crypto/driveCrypto.js.map +1 -1
- package/dist/interface/events.d.ts +1 -1
- package/dist/interface/featureFlags.d.ts +2 -1
- package/dist/interface/featureFlags.js +1 -0
- package/dist/interface/featureFlags.js.map +1 -1
- package/dist/interface/httpClient.d.ts +1 -0
- package/dist/interface/nodes.d.ts +7 -0
- package/dist/interface/nodes.js.map +1 -1
- package/dist/interface/telemetry.d.ts +4 -0
- package/dist/interface/telemetry.js.map +1 -1
- package/dist/internal/apiService/apiService.d.ts +1 -0
- package/dist/internal/apiService/apiService.js +16 -7
- package/dist/internal/apiService/apiService.js.map +1 -1
- package/dist/internal/apiService/apiService.test.js +24 -0
- package/dist/internal/apiService/apiService.test.js.map +1 -1
- package/dist/internal/apiService/driveTypes.d.ts +162 -101
- package/dist/internal/download/telemetry.js +4 -0
- package/dist/internal/download/telemetry.js.map +1 -1
- package/dist/internal/download/telemetry.test.js +5 -0
- package/dist/internal/download/telemetry.test.js.map +1 -1
- package/dist/internal/events/index.js +2 -2
- package/dist/internal/events/index.js.map +1 -1
- package/dist/internal/events/interface.d.ts +1 -1
- package/dist/internal/nodes/apiService.d.ts +4 -0
- package/dist/internal/nodes/apiService.js +4 -0
- package/dist/internal/nodes/apiService.js.map +1 -1
- package/dist/internal/nodes/apiService.test.js +8 -0
- package/dist/internal/nodes/apiService.test.js.map +1 -1
- package/dist/internal/nodes/cryptoService.js +3 -0
- package/dist/internal/nodes/cryptoService.js.map +1 -1
- package/dist/internal/nodes/extendedAttributes.d.ts +5 -5
- package/dist/internal/nodes/extendedAttributes.js +5 -14
- package/dist/internal/nodes/extendedAttributes.js.map +1 -1
- package/dist/internal/nodes/extendedAttributes.test.js +16 -22
- package/dist/internal/nodes/extendedAttributes.test.js.map +1 -1
- package/dist/internal/nodes/interface.d.ts +5 -0
- package/dist/internal/nodes/nodesManagement.d.ts +3 -3
- package/dist/internal/nodes/nodesManagement.js +7 -5
- package/dist/internal/nodes/nodesManagement.js.map +1 -1
- package/dist/internal/photos/albumsManager.js +1 -0
- package/dist/internal/photos/albumsManager.js.map +1 -1
- package/dist/internal/photos/nodes.d.ts +1 -1
- package/dist/internal/photos/nodes.js +2 -2
- package/dist/internal/photos/nodes.js.map +1 -1
- package/dist/internal/photos/upload.d.ts +5 -5
- package/dist/internal/photos/upload.js +8 -2
- package/dist/internal/photos/upload.js.map +1 -1
- package/dist/internal/sharing/apiService.js +1 -1
- package/dist/internal/sharing/apiService.js.map +1 -1
- package/dist/internal/sharingPublic/nodes.d.ts +1 -0
- package/dist/internal/upload/apiService.d.ts +45 -1
- package/dist/internal/upload/apiService.js +69 -1
- package/dist/internal/upload/apiService.js.map +1 -1
- package/dist/internal/upload/blockVerifier.d.ts +4 -1
- package/dist/internal/upload/blockVerifier.js +5 -0
- package/dist/internal/upload/blockVerifier.js.map +1 -1
- package/dist/internal/upload/cryptoService.d.ts +2 -2
- package/dist/internal/upload/cryptoService.js +1 -3
- package/dist/internal/upload/cryptoService.js.map +1 -1
- package/dist/internal/upload/fileUploader.d.ts +4 -3
- package/dist/internal/upload/fileUploader.js +17 -7
- package/dist/internal/upload/fileUploader.js.map +1 -1
- package/dist/internal/upload/index.d.ts +3 -3
- package/dist/internal/upload/index.js +17 -1
- package/dist/internal/upload/index.js.map +1 -1
- package/dist/internal/upload/index.test.d.ts +1 -0
- package/dist/internal/upload/index.test.js +71 -0
- package/dist/internal/upload/index.test.js.map +1 -0
- package/dist/internal/upload/interface.d.ts +2 -0
- package/dist/internal/upload/manager.d.ts +41 -2
- package/dist/internal/upload/manager.js +126 -44
- package/dist/internal/upload/manager.js.map +1 -1
- package/dist/internal/upload/manager.test.js +268 -1
- package/dist/internal/upload/manager.test.js.map +1 -1
- package/dist/internal/upload/smallFileUploader.d.ts +83 -0
- package/dist/internal/upload/smallFileUploader.js +197 -0
- package/dist/internal/upload/smallFileUploader.js.map +1 -0
- package/dist/internal/upload/smallFileUploader.test.d.ts +1 -0
- package/dist/internal/upload/smallFileUploader.test.js +358 -0
- package/dist/internal/upload/smallFileUploader.test.js.map +1 -0
- package/dist/internal/upload/streamReader.d.ts +4 -0
- package/dist/internal/upload/streamReader.js +37 -0
- package/dist/internal/upload/streamReader.js.map +1 -0
- package/dist/internal/upload/streamReader.test.d.ts +1 -0
- package/dist/internal/upload/streamReader.test.js +90 -0
- package/dist/internal/upload/streamReader.test.js.map +1 -0
- package/dist/internal/upload/streamUploader.d.ts +6 -0
- package/dist/internal/upload/streamUploader.js +3 -3
- package/dist/internal/upload/streamUploader.js.map +1 -1
- package/dist/internal/upload/telemetry.d.ts +3 -2
- package/dist/internal/upload/telemetry.js +5 -0
- package/dist/internal/upload/telemetry.js.map +1 -1
- package/dist/internal/upload/telemetry.test.js +6 -0
- package/dist/internal/upload/telemetry.test.js.map +1 -1
- package/dist/protonDrivePhotosClient.d.ts +1 -1
- package/dist/protonDrivePublicLinkClient.js +3 -1
- package/dist/protonDrivePublicLinkClient.js.map +1 -1
- package/dist/telemetry.d.ts +1 -0
- package/dist/telemetry.js +21 -0
- package/dist/telemetry.js.map +1 -1
- package/dist/telemetry.test.d.ts +1 -0
- package/dist/telemetry.test.js +37 -0
- package/dist/telemetry.test.js.map +1 -0
- package/dist/transformers.d.ts +1 -1
- package/dist/transformers.js +1 -0
- package/dist/transformers.js.map +1 -1
- package/package.json +1 -1
- package/src/crypto/driveCrypto.ts +2 -0
- package/src/interface/events.ts +1 -1
- package/src/interface/featureFlags.ts +1 -0
- package/src/interface/httpClient.ts +1 -0
- package/src/interface/nodes.ts +7 -0
- package/src/interface/telemetry.ts +4 -0
- package/src/internal/apiService/apiService.test.ts +30 -0
- package/src/internal/apiService/apiService.ts +23 -7
- package/src/internal/apiService/driveTypes.ts +162 -101
- package/src/internal/download/telemetry.test.ts +5 -0
- package/src/internal/download/telemetry.ts +5 -1
- package/src/internal/events/index.ts +2 -2
- package/src/internal/events/interface.ts +1 -1
- package/src/internal/nodes/apiService.test.ts +9 -0
- package/src/internal/nodes/apiService.ts +4 -0
- package/src/internal/nodes/cryptoService.ts +11 -1
- package/src/internal/nodes/extendedAttributes.test.ts +25 -25
- package/src/internal/nodes/extendedAttributes.ts +10 -19
- package/src/internal/nodes/interface.ts +5 -0
- package/src/internal/nodes/nodesManagement.ts +8 -6
- package/src/internal/photos/albumsManager.ts +1 -0
- package/src/internal/photos/nodes.ts +2 -2
- package/src/internal/photos/upload.ts +23 -10
- package/src/internal/sharing/apiService.ts +5 -5
- package/src/internal/upload/apiService.ts +167 -2
- package/src/internal/upload/blockVerifier.ts +12 -0
- package/src/internal/upload/cryptoService.ts +10 -10
- package/src/internal/upload/fileUploader.ts +20 -7
- package/src/internal/upload/index.test.ts +99 -0
- package/src/internal/upload/index.ts +45 -4
- package/src/internal/upload/interface.ts +2 -0
- package/src/internal/upload/manager.test.ts +368 -2
- package/src/internal/upload/manager.ts +229 -78
- package/src/internal/upload/smallFileUploader.test.ts +491 -0
- package/src/internal/upload/smallFileUploader.ts +353 -0
- package/src/internal/upload/streamReader.test.ts +109 -0
- package/src/internal/upload/streamReader.ts +38 -0
- package/src/internal/upload/streamUploader.ts +1 -1
- package/src/internal/upload/telemetry.test.ts +6 -0
- package/src/internal/upload/telemetry.ts +8 -2
- package/src/protonDrivePhotosClient.ts +1 -1
- package/src/protonDrivePublicLinkClient.ts +2 -0
- package/src/telemetry.test.ts +40 -0
- package/src/telemetry.ts +22 -0
- package/src/transformers.ts +2 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { c } from 'ttag';
|
|
2
2
|
|
|
3
|
-
import {
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
290
|
+
let deleteFailed = false;
|
|
291
|
+
try {
|
|
155
292
|
this.logger.warn(
|
|
156
|
-
`
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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 {
|