@protontech/drive-sdk 0.4.1 → 0.5.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/diagnostic/{sdkDiagnosticFull.d.ts → diagnostic.d.ts} +5 -4
- package/dist/diagnostic/{sdkDiagnosticFull.js → diagnostic.js} +13 -10
- package/dist/diagnostic/diagnostic.js.map +1 -0
- package/dist/diagnostic/index.js +2 -4
- package/dist/diagnostic/index.js.map +1 -1
- package/dist/diagnostic/interface.d.ts +22 -1
- package/dist/diagnostic/sdkDiagnostic.d.ts +3 -2
- package/dist/diagnostic/sdkDiagnostic.js +80 -8
- package/dist/diagnostic/sdkDiagnostic.js.map +1 -1
- package/dist/interface/download.d.ts +4 -4
- package/dist/interface/index.d.ts +1 -1
- package/dist/interface/index.js.map +1 -1
- package/dist/interface/nodes.d.ts +9 -0
- package/dist/interface/telemetry.d.ts +4 -1
- package/dist/interface/telemetry.js.map +1 -1
- package/dist/interface/upload.d.ts +6 -3
- package/dist/internal/apiService/apiService.d.ts +3 -0
- package/dist/internal/apiService/apiService.js +25 -2
- package/dist/internal/apiService/apiService.js.map +1 -1
- package/dist/internal/apiService/apiService.test.js +38 -0
- package/dist/internal/apiService/apiService.test.js.map +1 -1
- package/dist/internal/apiService/driveTypes.d.ts +2595 -2397
- package/dist/internal/apiService/errors.js +3 -0
- package/dist/internal/apiService/errors.js.map +1 -1
- package/dist/internal/apiService/errors.test.js +15 -7
- package/dist/internal/apiService/errors.test.js.map +1 -1
- package/dist/internal/asyncIteratorMap.d.ts +1 -1
- package/dist/internal/asyncIteratorMap.js +6 -1
- package/dist/internal/asyncIteratorMap.js.map +1 -1
- package/dist/internal/asyncIteratorMap.test.js +9 -0
- package/dist/internal/asyncIteratorMap.test.js.map +1 -1
- package/dist/internal/download/controller.d.ts +2 -0
- package/dist/internal/download/controller.js +15 -1
- package/dist/internal/download/controller.js.map +1 -1
- package/dist/internal/download/fileDownloader.d.ts +3 -3
- package/dist/internal/download/fileDownloader.js +11 -6
- package/dist/internal/download/fileDownloader.js.map +1 -1
- package/dist/internal/download/fileDownloader.test.js +8 -8
- package/dist/internal/download/fileDownloader.test.js.map +1 -1
- package/dist/internal/nodes/apiService.d.ts +6 -1
- package/dist/internal/nodes/apiService.js +71 -44
- package/dist/internal/nodes/apiService.js.map +1 -1
- package/dist/internal/nodes/apiService.test.js +204 -15
- package/dist/internal/nodes/apiService.test.js.map +1 -1
- package/dist/internal/nodes/debouncer.d.ts +24 -0
- package/dist/internal/nodes/debouncer.js +92 -0
- package/dist/internal/nodes/debouncer.js.map +1 -0
- package/dist/internal/nodes/debouncer.test.d.ts +1 -0
- package/dist/internal/nodes/debouncer.test.js +108 -0
- package/dist/internal/nodes/debouncer.test.js.map +1 -0
- package/dist/internal/nodes/extendedAttributes.js +2 -2
- package/dist/internal/nodes/extendedAttributes.js.map +1 -1
- package/dist/internal/nodes/index.js +1 -1
- package/dist/internal/nodes/index.js.map +1 -1
- package/dist/internal/nodes/nodesAccess.d.ts +6 -4
- package/dist/internal/nodes/nodesAccess.js +29 -9
- package/dist/internal/nodes/nodesAccess.js.map +1 -1
- package/dist/internal/nodes/nodesAccess.test.js +19 -7
- package/dist/internal/nodes/nodesAccess.test.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.d.ts +2 -2
- package/dist/internal/nodes/nodesManagement.js +5 -3
- package/dist/internal/nodes/nodesManagement.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.test.js +3 -1
- package/dist/internal/nodes/nodesManagement.test.js.map +1 -1
- package/dist/internal/photos/apiService.js +9 -20
- package/dist/internal/photos/apiService.js.map +1 -1
- package/dist/internal/photos/upload.d.ts +2 -1
- package/dist/internal/photos/upload.js +9 -3
- package/dist/internal/photos/upload.js.map +1 -1
- package/dist/internal/sharing/apiService.d.ts +1 -1
- package/dist/internal/sharing/apiService.js +2 -2
- package/dist/internal/sharing/apiService.js.map +1 -1
- package/dist/internal/sharing/sharingManagement.d.ts +4 -1
- package/dist/internal/sharing/sharingManagement.js +7 -4
- package/dist/internal/sharing/sharingManagement.js.map +1 -1
- package/dist/internal/sharingPublic/apiService.d.ts +8 -10
- package/dist/internal/sharingPublic/apiService.js +9 -125
- package/dist/internal/sharingPublic/apiService.js.map +1 -1
- package/dist/internal/sharingPublic/cryptoReporter.d.ts +16 -0
- package/dist/internal/sharingPublic/{cryptoService.js → cryptoReporter.js} +3 -16
- package/dist/internal/sharingPublic/cryptoReporter.js.map +1 -0
- package/dist/internal/sharingPublic/index.d.ts +22 -4
- package/dist/internal/sharingPublic/index.js +37 -12
- package/dist/internal/sharingPublic/index.js.map +1 -1
- package/dist/internal/sharingPublic/nodes.d.ts +18 -0
- package/dist/internal/sharingPublic/nodes.js +46 -0
- package/dist/internal/sharingPublic/nodes.js.map +1 -0
- package/dist/internal/sharingPublic/session/apiService.d.ts +7 -5
- package/dist/internal/sharingPublic/session/apiService.js +25 -4
- package/dist/internal/sharingPublic/session/apiService.js.map +1 -1
- package/dist/internal/sharingPublic/session/interface.d.ts +17 -0
- package/dist/internal/sharingPublic/session/manager.d.ts +12 -4
- package/dist/internal/sharingPublic/session/manager.js +14 -4
- package/dist/internal/sharingPublic/session/manager.js.map +1 -1
- package/dist/internal/sharingPublic/session/session.d.ts +7 -4
- package/dist/internal/sharingPublic/session/session.js +7 -3
- package/dist/internal/sharingPublic/session/session.js.map +1 -1
- package/dist/internal/sharingPublic/session/url.test.js +3 -3
- package/dist/internal/sharingPublic/shares.d.ts +27 -0
- package/dist/internal/sharingPublic/shares.js +46 -0
- package/dist/internal/sharingPublic/shares.js.map +1 -0
- package/dist/internal/upload/apiService.js +10 -1
- package/dist/internal/upload/apiService.js.map +1 -1
- package/dist/internal/upload/controller.d.ts +11 -3
- package/dist/internal/upload/controller.js +16 -2
- package/dist/internal/upload/controller.js.map +1 -1
- package/dist/internal/upload/fileUploader.d.ts +6 -3
- package/dist/internal/upload/fileUploader.js +4 -4
- package/dist/internal/upload/fileUploader.js.map +1 -1
- package/dist/internal/upload/fileUploader.test.js +23 -11
- package/dist/internal/upload/fileUploader.test.js.map +1 -1
- package/dist/internal/upload/streamUploader.d.ts +9 -4
- package/dist/internal/upload/streamUploader.js +67 -20
- package/dist/internal/upload/streamUploader.js.map +1 -1
- package/dist/internal/upload/streamUploader.test.js +43 -13
- package/dist/internal/upload/streamUploader.test.js.map +1 -1
- package/dist/protonDriveClient.d.ts +11 -6
- package/dist/protonDriveClient.js +11 -10
- package/dist/protonDriveClient.js.map +1 -1
- package/dist/protonDrivePublicLinkClient.d.ts +34 -6
- package/dist/protonDrivePublicLinkClient.js +52 -9
- package/dist/protonDrivePublicLinkClient.js.map +1 -1
- package/dist/tests/telemetry.d.ts +4 -2
- package/dist/tests/telemetry.js +3 -1
- package/dist/tests/telemetry.js.map +1 -1
- package/dist/transformers.d.ts +3 -2
- package/dist/transformers.js +6 -0
- package/dist/transformers.js.map +1 -1
- package/package.json +1 -1
- package/src/diagnostic/{sdkDiagnosticFull.ts → diagnostic.ts} +10 -6
- package/src/diagnostic/index.ts +3 -5
- package/src/diagnostic/interface.ts +39 -0
- package/src/diagnostic/sdkDiagnostic.ts +111 -10
- package/src/interface/download.ts +4 -4
- package/src/interface/index.ts +1 -0
- package/src/interface/nodes.ts +3 -0
- package/src/interface/telemetry.ts +5 -0
- package/src/interface/upload.ts +3 -3
- package/src/internal/apiService/apiService.test.ts +50 -0
- package/src/internal/apiService/apiService.ts +33 -2
- package/src/internal/apiService/driveTypes.ts +2713 -2561
- package/src/internal/apiService/errors.test.ts +10 -0
- package/src/internal/apiService/errors.ts +5 -1
- package/src/internal/asyncIteratorMap.test.ts +12 -0
- package/src/internal/asyncIteratorMap.ts +8 -0
- package/src/internal/download/controller.ts +13 -1
- package/src/internal/download/fileDownloader.test.ts +8 -8
- package/src/internal/download/fileDownloader.ts +13 -6
- package/src/internal/nodes/apiService.test.ts +261 -14
- package/src/internal/nodes/apiService.ts +99 -65
- package/src/internal/nodes/debouncer.test.ts +141 -0
- package/src/internal/nodes/debouncer.ts +109 -0
- package/src/internal/nodes/extendedAttributes.ts +2 -2
- package/src/internal/nodes/index.ts +1 -8
- package/src/internal/nodes/nodesAccess.test.ts +19 -7
- package/src/internal/nodes/nodesAccess.ts +44 -9
- package/src/internal/nodes/nodesManagement.test.ts +3 -1
- package/src/internal/nodes/nodesManagement.ts +11 -5
- package/src/internal/photos/apiService.ts +12 -29
- package/src/internal/photos/upload.ts +22 -1
- package/src/internal/sharing/apiService.ts +2 -2
- package/src/internal/sharing/sharingManagement.ts +7 -4
- package/src/internal/sharingPublic/apiService.ts +23 -160
- package/src/internal/sharingPublic/{cryptoService.ts → cryptoReporter.ts} +2 -27
- package/src/internal/sharingPublic/index.ts +76 -13
- package/src/internal/sharingPublic/nodes.ts +59 -0
- package/src/internal/sharingPublic/session/apiService.ts +32 -10
- package/src/internal/sharingPublic/session/interface.ts +20 -0
- package/src/internal/sharingPublic/session/manager.ts +31 -8
- package/src/internal/sharingPublic/session/session.ts +12 -7
- package/src/internal/sharingPublic/session/url.test.ts +3 -3
- package/src/internal/sharingPublic/shares.ts +50 -0
- package/src/internal/upload/apiService.ts +12 -1
- package/src/internal/upload/controller.ts +16 -4
- package/src/internal/upload/fileUploader.test.ts +25 -11
- package/src/internal/upload/fileUploader.ts +6 -5
- package/src/internal/upload/streamUploader.test.ts +56 -12
- package/src/internal/upload/streamUploader.ts +78 -20
- package/src/protonDriveClient.ts +29 -11
- package/src/protonDrivePublicLinkClient.ts +100 -16
- package/src/tests/telemetry.ts +6 -3
- package/src/transformers.ts +8 -0
- package/dist/diagnostic/sdkDiagnosticFull.js.map +0 -1
- package/dist/internal/sharingPublic/cryptoCache.d.ts +0 -19
- package/dist/internal/sharingPublic/cryptoCache.js +0 -72
- package/dist/internal/sharingPublic/cryptoCache.js.map +0 -1
- package/dist/internal/sharingPublic/cryptoService.d.ts +0 -9
- package/dist/internal/sharingPublic/cryptoService.js.map +0 -1
- package/dist/internal/sharingPublic/interface.d.ts +0 -6
- package/dist/internal/sharingPublic/interface.js +0 -3
- package/dist/internal/sharingPublic/interface.js.map +0 -1
- package/dist/internal/sharingPublic/manager.d.ts +0 -19
- package/dist/internal/sharingPublic/manager.js +0 -81
- package/dist/internal/sharingPublic/manager.js.map +0 -1
- package/src/internal/sharingPublic/cryptoCache.ts +0 -79
- package/src/internal/sharingPublic/interface.ts +0 -14
- package/src/internal/sharingPublic/manager.ts +0 -86
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Author, FileDownloader, MaybeNode, NodeType, Revision, ThumbnailType } from '../interface';
|
|
2
2
|
import { ProtonDriveClient } from '../protonDriveClient';
|
|
3
|
-
import {
|
|
3
|
+
import { DiagnosticOptions, DiagnosticResult, NodeDetails, ExcpectedTreeNode } from './interface';
|
|
4
4
|
import { IntegrityVerificationStream } from './integrityVerificationStream';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -10,7 +10,7 @@ import { IntegrityVerificationStream } from './integrityVerificationStream';
|
|
|
10
10
|
* It produces only events that can be read by direct SDK invocation.
|
|
11
11
|
* To get the full diagnostic, use {@link FullSDKDiagnostic}.
|
|
12
12
|
*/
|
|
13
|
-
export class SDKDiagnostic
|
|
13
|
+
export class SDKDiagnostic {
|
|
14
14
|
constructor(private protonDriveClient: ProtonDriveClient) {
|
|
15
15
|
this.protonDriveClient = protonDriveClient;
|
|
16
16
|
}
|
|
@@ -58,7 +58,7 @@ export class SDKDiagnostic implements Diagnostic {
|
|
|
58
58
|
yield* this.verifyAuthor(activeRevision.contentAuthor, 'content', node);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
yield* this.verifyFileExtendedAttributes(node);
|
|
61
|
+
yield* this.verifyFileExtendedAttributes(node, options);
|
|
62
62
|
|
|
63
63
|
if (options?.verifyContent) {
|
|
64
64
|
yield* this.verifyContent(node);
|
|
@@ -80,12 +80,17 @@ export class SDKDiagnostic implements Diagnostic {
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
private async *verifyFileExtendedAttributes(
|
|
83
|
+
private async *verifyFileExtendedAttributes(
|
|
84
|
+
node: MaybeNode,
|
|
85
|
+
options?: DiagnosticOptions,
|
|
86
|
+
): AsyncGenerator<DiagnosticResult> {
|
|
84
87
|
const activeRevision = getActiveRevision(node);
|
|
85
88
|
|
|
86
89
|
const expectedAttributes = getNodeType(node) === NodeType.File;
|
|
87
90
|
|
|
88
91
|
const claimedSha1 = activeRevision?.claimedDigests?.sha1;
|
|
92
|
+
const claimedSizeInBytes = activeRevision?.claimedSize;
|
|
93
|
+
|
|
89
94
|
if (claimedSha1 && !/^[0-9a-f]{40}$/i.test(claimedSha1)) {
|
|
90
95
|
yield {
|
|
91
96
|
type: 'extended_attributes_error',
|
|
@@ -102,6 +107,24 @@ export class SDKDiagnostic implements Diagnostic {
|
|
|
102
107
|
...getNodeDetails(node),
|
|
103
108
|
};
|
|
104
109
|
}
|
|
110
|
+
|
|
111
|
+
if (options?.expectedStructure) {
|
|
112
|
+
const expectedSha1 = options.expectedStructure.expectedSha1;
|
|
113
|
+
const expectedSizeInBytes = options.expectedStructure.expectedSizeInBytes;
|
|
114
|
+
|
|
115
|
+
const wrongSha1 = expectedSha1 !== undefined && claimedSha1 !== expectedSha1;
|
|
116
|
+
const wrongSizeInBytes = expectedSizeInBytes !== undefined && claimedSizeInBytes !== expectedSizeInBytes;
|
|
117
|
+
|
|
118
|
+
if (wrongSha1 || wrongSizeInBytes) {
|
|
119
|
+
yield {
|
|
120
|
+
type: 'expected_structure_integrity_error',
|
|
121
|
+
claimedSha1,
|
|
122
|
+
claimedSizeInBytes,
|
|
123
|
+
expectedNode: getExpectedTreeNodeDetails(options.expectedStructure),
|
|
124
|
+
...getNodeDetails(node),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
105
128
|
}
|
|
106
129
|
|
|
107
130
|
private async *verifyContent(node: MaybeNode): AsyncGenerator<DiagnosticResult> {
|
|
@@ -133,7 +156,7 @@ export class SDKDiagnostic implements Diagnostic {
|
|
|
133
156
|
const claimedSizeInBytes = downloader.getClaimedSizeInBytes();
|
|
134
157
|
|
|
135
158
|
const integrityVerificationStream = new IntegrityVerificationStream();
|
|
136
|
-
const controller = downloader.
|
|
159
|
+
const controller = downloader.downloadToStream(integrityVerificationStream);
|
|
137
160
|
|
|
138
161
|
try {
|
|
139
162
|
await controller.completion();
|
|
@@ -195,19 +218,73 @@ export class SDKDiagnostic implements Diagnostic {
|
|
|
195
218
|
}
|
|
196
219
|
}
|
|
197
220
|
|
|
198
|
-
private async *verifyNodeChildren(
|
|
199
|
-
|
|
221
|
+
private async *verifyNodeChildren(
|
|
222
|
+
parentNode: MaybeNode,
|
|
223
|
+
options?: DiagnosticOptions,
|
|
224
|
+
): AsyncGenerator<DiagnosticResult> {
|
|
225
|
+
const parentNodeUid = parentNode.ok ? parentNode.value.uid : parentNode.error.uid;
|
|
226
|
+
const children: MaybeNode[] = [];
|
|
227
|
+
|
|
200
228
|
try {
|
|
201
|
-
for await (const child of this.protonDriveClient.iterateFolderChildren(
|
|
202
|
-
|
|
229
|
+
for await (const child of this.protonDriveClient.iterateFolderChildren(parentNode)) {
|
|
230
|
+
if (options?.expectedStructure) {
|
|
231
|
+
children.push(child);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
yield *
|
|
235
|
+
this.verifyNodeTree(child, {
|
|
236
|
+
...options,
|
|
237
|
+
expectedStructure: options?.expectedStructure
|
|
238
|
+
? getTreeNodeChildByNodeName(options.expectedStructure, getNodeName(child))
|
|
239
|
+
: undefined,
|
|
240
|
+
});
|
|
203
241
|
}
|
|
204
242
|
} catch (error: unknown) {
|
|
205
243
|
yield {
|
|
206
244
|
type: 'sdk_error',
|
|
207
|
-
call: `iterateFolderChildren(${
|
|
245
|
+
call: `iterateFolderChildren(${parentNodeUid})`,
|
|
208
246
|
error,
|
|
209
247
|
};
|
|
210
248
|
}
|
|
249
|
+
|
|
250
|
+
if (options?.expectedStructure) {
|
|
251
|
+
yield* this.verifyExpectedNodeChildren(parentNodeUid, children, options);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private async *verifyExpectedNodeChildren(
|
|
256
|
+
parentNodeUid: string,
|
|
257
|
+
children: MaybeNode[],
|
|
258
|
+
options: DiagnosticOptions,
|
|
259
|
+
): AsyncGenerator<DiagnosticResult> {
|
|
260
|
+
if (!options.expectedStructure) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const expectedNodes = options.expectedStructure.children ?? [];
|
|
265
|
+
const actualNodeNames = children.map((child) => getNodeName(child));
|
|
266
|
+
|
|
267
|
+
for (const expectedNode of expectedNodes) {
|
|
268
|
+
if (!actualNodeNames.includes(expectedNode.name)) {
|
|
269
|
+
yield {
|
|
270
|
+
type: 'expected_structure_missing_node',
|
|
271
|
+
expectedNode: getExpectedTreeNodeDetails(expectedNode),
|
|
272
|
+
parentNodeUid,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
for (const child of children) {
|
|
278
|
+
const childName = getNodeName(child);
|
|
279
|
+
const isExpected = expectedNodes.some((expectedNode) => expectedNode.name === childName);
|
|
280
|
+
|
|
281
|
+
if (!isExpected) {
|
|
282
|
+
yield {
|
|
283
|
+
type: 'expected_structure_unexpected_node',
|
|
284
|
+
...getNodeDetails(child),
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
}
|
|
211
288
|
}
|
|
212
289
|
}
|
|
213
290
|
|
|
@@ -275,3 +352,27 @@ function getActiveRevision(node: MaybeNode): Revision | undefined {
|
|
|
275
352
|
}
|
|
276
353
|
return undefined;
|
|
277
354
|
}
|
|
355
|
+
|
|
356
|
+
function getNodeName(node: MaybeNode): string {
|
|
357
|
+
if (node.ok) {
|
|
358
|
+
return node.value.name;
|
|
359
|
+
}
|
|
360
|
+
if (node.error.name.ok) {
|
|
361
|
+
return node.error.name.value;
|
|
362
|
+
}
|
|
363
|
+
return 'N/A';
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function getExpectedTreeNodeDetails(expectedNode: ExcpectedTreeNode): ExcpectedTreeNode {
|
|
367
|
+
return {
|
|
368
|
+
...expectedNode,
|
|
369
|
+
children: undefined,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function getTreeNodeChildByNodeName(
|
|
374
|
+
expectedSubtree: ExcpectedTreeNode,
|
|
375
|
+
nodeName: string,
|
|
376
|
+
): ExcpectedTreeNode | undefined {
|
|
377
|
+
return expectedSubtree.children?.find((expectedNode) => expectedNode.name === nodeName);
|
|
378
|
+
}
|
|
@@ -15,14 +15,14 @@ export interface FileDownloader {
|
|
|
15
15
|
*
|
|
16
16
|
* @param onProgress - Callback that is called with the number of downloaded bytes
|
|
17
17
|
*/
|
|
18
|
-
|
|
18
|
+
downloadToStream(streamFactory: WritableStream, onProgress?: (downloadedBytes: number) => void): DownloadController;
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
* Same as `
|
|
21
|
+
* Same as `downloadToStream` but without verification checks.
|
|
22
22
|
*
|
|
23
23
|
* Use this only for debugging purposes.
|
|
24
24
|
*/
|
|
25
|
-
|
|
25
|
+
unsafeDownloadToStream(
|
|
26
26
|
streamFactory: WritableStream,
|
|
27
27
|
onProgress?: (downloadedBytes: number) => void,
|
|
28
28
|
): DownloadController;
|
|
@@ -34,7 +34,7 @@ export interface FileDownloader {
|
|
|
34
34
|
* need to download the entire file.
|
|
35
35
|
*
|
|
36
36
|
* Stream doesn't verify data integrity. For the full integrity of
|
|
37
|
-
* the file, use `
|
|
37
|
+
* the file, use `downloadToStream` instead.
|
|
38
38
|
*
|
|
39
39
|
* The stream is not opportunitistically downloading the data ahead of
|
|
40
40
|
* the time. It will only download the data when it is requested. To
|
package/src/interface/index.ts
CHANGED
package/src/interface/nodes.ts
CHANGED
|
@@ -224,3 +224,6 @@ export type NodeOrUid = MaybeNode | NodeEntity | DegradedNode | string;
|
|
|
224
224
|
export type RevisionOrUid = Revision | string;
|
|
225
225
|
|
|
226
226
|
export type NodeResult = { uid: string; ok: true } | { uid: string; ok: false; error: string };
|
|
227
|
+
export type NodeResultWithNewUid =
|
|
228
|
+
| { uid: string; newUid: string; ok: true }
|
|
229
|
+
| { uid: string; ok: false; error: string };
|
|
@@ -12,6 +12,7 @@ export interface Logger {
|
|
|
12
12
|
|
|
13
13
|
export type MetricEvent =
|
|
14
14
|
| MetricAPIRetrySucceededEvent
|
|
15
|
+
| MetricDebounceLongWaitEvent
|
|
15
16
|
| MetricUploadEvent
|
|
16
17
|
| MetricDownloadEvent
|
|
17
18
|
| MetricDecryptionErrorEvent
|
|
@@ -25,6 +26,10 @@ export interface MetricAPIRetrySucceededEvent {
|
|
|
25
26
|
failedAttempts: number;
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
export interface MetricDebounceLongWaitEvent {
|
|
30
|
+
eventName: 'debounceLongWait';
|
|
31
|
+
}
|
|
32
|
+
|
|
28
33
|
export interface MetricUploadEvent {
|
|
29
34
|
eventName: 'upload';
|
|
30
35
|
volumeType?: MetricVolumeType;
|
package/src/interface/upload.ts
CHANGED
|
@@ -41,7 +41,7 @@ export interface FileRevisionUploader {
|
|
|
41
41
|
*
|
|
42
42
|
* The function will reject if the node with the given name already exists.
|
|
43
43
|
*/
|
|
44
|
-
|
|
44
|
+
uploadFromStream(
|
|
45
45
|
stream: ReadableStream,
|
|
46
46
|
thumnbails: Thumbnail[],
|
|
47
47
|
onProgress?: (uploadedBytes: number) => void,
|
|
@@ -57,7 +57,7 @@ export interface FileRevisionUploader {
|
|
|
57
57
|
*
|
|
58
58
|
* The function will reject if the node with the given name already exists.
|
|
59
59
|
*/
|
|
60
|
-
|
|
60
|
+
uploadFromFile(
|
|
61
61
|
fileObject: File,
|
|
62
62
|
thumnbails: Thumbnail[],
|
|
63
63
|
onProgress?: (uploadedBytes: number) => void,
|
|
@@ -79,5 +79,5 @@ export interface FileUploader extends FileRevisionUploader {
|
|
|
79
79
|
export interface UploadController {
|
|
80
80
|
pause(): void;
|
|
81
81
|
resume(): void;
|
|
82
|
-
completion(): Promise<string>;
|
|
82
|
+
completion(): Promise<{ nodeRevisionUid: string, nodeUid: string }>;
|
|
83
83
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AbortError } from '../../errors';
|
|
1
2
|
import { ProtonDriveHTTPClient, SDKEvent } from '../../interface';
|
|
2
3
|
import { getMockTelemetry } from '../../tests/telemetry';
|
|
3
4
|
import { SDKEvents } from '../sdkEvents';
|
|
@@ -112,6 +113,16 @@ describe('DriveAPIService', () => {
|
|
|
112
113
|
});
|
|
113
114
|
|
|
114
115
|
describe('should throw', () => {
|
|
116
|
+
it('AbortError on aborted error from the provided HTTP client', async () => {
|
|
117
|
+
const abortError = new Error('AbortError');
|
|
118
|
+
abortError.name = 'AbortError';
|
|
119
|
+
|
|
120
|
+
httpClient.fetchJson = jest.fn(() => Promise.reject(abortError));
|
|
121
|
+
|
|
122
|
+
await expect(api.get('test')).rejects.toThrow(new AbortError('Request aborted'));
|
|
123
|
+
expectSDKEvents();
|
|
124
|
+
});
|
|
125
|
+
|
|
115
126
|
it('APIHTTPError on 4xx response without JSON body', async () => {
|
|
116
127
|
httpClient.fetchJson = jest.fn(() =>
|
|
117
128
|
Promise.resolve(new Response('Not found', { status: 404, statusText: 'Not found' })),
|
|
@@ -314,5 +325,44 @@ describe('DriveAPIService', () => {
|
|
|
314
325
|
expect(httpClient.fetchJson).toHaveBeenCalledTimes(35);
|
|
315
326
|
expectSDKEvents();
|
|
316
327
|
});
|
|
328
|
+
|
|
329
|
+
it('notify about offline error', async () => {
|
|
330
|
+
jest.useFakeTimers();
|
|
331
|
+
const offlineError = new Error('OfflineError');
|
|
332
|
+
offlineError.name = 'OfflineError';
|
|
333
|
+
|
|
334
|
+
let attempt = 0;
|
|
335
|
+
httpClient.fetchJson = jest.fn().mockImplementation(() => {
|
|
336
|
+
if (attempt++ >= 15) {
|
|
337
|
+
return generateOkResponse();
|
|
338
|
+
}
|
|
339
|
+
throw offlineError;
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
const promise = api.get('test');
|
|
343
|
+
|
|
344
|
+
// First 9 calls (first is immediate, then 8 with 5 second delay), no events are sent yet
|
|
345
|
+
await jest.advanceTimersByTimeAsync(5 * 8 * 1000);
|
|
346
|
+
expect(httpClient.fetchJson).toHaveBeenCalledTimes(9);
|
|
347
|
+
expectSDKEvents();
|
|
348
|
+
|
|
349
|
+
// 10th call, service sends TransfersPaused event
|
|
350
|
+
await jest.advanceTimersByTimeAsync(5 * 1000);
|
|
351
|
+
expect(httpClient.fetchJson).toHaveBeenCalledTimes(10);
|
|
352
|
+
expectSDKEvents(SDKEvent.TransfersPaused);
|
|
353
|
+
|
|
354
|
+
// Next 5 calls, still offline, no more events are sent
|
|
355
|
+
await jest.advanceTimersByTimeAsync(5 * 5 * 1000);
|
|
356
|
+
expect(httpClient.fetchJson).toHaveBeenCalledTimes(15);
|
|
357
|
+
expectSDKEvents(SDKEvent.TransfersPaused);
|
|
358
|
+
|
|
359
|
+
// 16th call, mock returns OK response, service sends TransfersResumed event
|
|
360
|
+
await jest.advanceTimersByTimeAsync(5 * 1000);
|
|
361
|
+
expect(httpClient.fetchJson).toHaveBeenCalledTimes(16);
|
|
362
|
+
expectSDKEvents(SDKEvent.TransfersPaused, SDKEvent.TransfersResumed);
|
|
363
|
+
|
|
364
|
+
await promise;
|
|
365
|
+
});
|
|
366
|
+
|
|
317
367
|
});
|
|
318
368
|
});
|
|
@@ -40,6 +40,11 @@ const TOO_MANY_SUBSEQUENT_SERVER_ERRORS = 10;
|
|
|
40
40
|
*/
|
|
41
41
|
const TOO_MANY_SUBSEQUENT_SERVER_ERRORS_TIMEOUT_IN_SECONDS = 60;
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* How many subsequent offline errors are allowed before we consider the client offline.
|
|
45
|
+
*/
|
|
46
|
+
const TOO_MANY_SUBSEQUENT_OFFLINE_ERRORS = 10;
|
|
47
|
+
|
|
43
48
|
/**
|
|
44
49
|
* After how long to re-try after 5xx or timeout error.
|
|
45
50
|
*/
|
|
@@ -88,6 +93,8 @@ export class DriveAPIService {
|
|
|
88
93
|
private subsequentServerErrorsCounter = 0;
|
|
89
94
|
private lastServerErrorAt?: number;
|
|
90
95
|
|
|
96
|
+
private subsequentOfflineErrorsCounter = 0;
|
|
97
|
+
|
|
91
98
|
private logger: Logger;
|
|
92
99
|
|
|
93
100
|
constructor(
|
|
@@ -219,7 +226,7 @@ export class DriveAPIService {
|
|
|
219
226
|
if (error instanceof ProtonDriveError) {
|
|
220
227
|
throw error;
|
|
221
228
|
}
|
|
222
|
-
throw apiErrorFactory({ response });
|
|
229
|
+
throw apiErrorFactory({ response, error });
|
|
223
230
|
}
|
|
224
231
|
}
|
|
225
232
|
return response;
|
|
@@ -261,7 +268,13 @@ export class DriveAPIService {
|
|
|
261
268
|
response = await callback();
|
|
262
269
|
} catch (error: unknown) {
|
|
263
270
|
if (error instanceof Error) {
|
|
271
|
+
if (error.name === 'AbortError') {
|
|
272
|
+
this.logger.debug(`${request.method} ${request.url}: Aborted`);
|
|
273
|
+
throw new AbortError(c('Error').t`Request aborted`);
|
|
274
|
+
}
|
|
275
|
+
|
|
264
276
|
if (error.name === 'OfflineError') {
|
|
277
|
+
this.offlineErrorHappened();
|
|
265
278
|
this.logger.info(`${request.method} ${request.url}: Offline error, retrying`);
|
|
266
279
|
await waitSeconds(OFFLINE_RETRY_DELAY_SECONDS);
|
|
267
280
|
return this.fetch(request, callback, attempt + 1);
|
|
@@ -282,6 +295,8 @@ export class DriveAPIService {
|
|
|
282
295
|
throw error;
|
|
283
296
|
}
|
|
284
297
|
|
|
298
|
+
this.clearSubsequentOfflineErrors();
|
|
299
|
+
|
|
285
300
|
const end = Date.now();
|
|
286
301
|
const duration = end - start;
|
|
287
302
|
|
|
@@ -342,7 +357,7 @@ export class DriveAPIService {
|
|
|
342
357
|
// the client is very limited. This is generic event and it doesn't
|
|
343
358
|
// take into account that various endpoints can be rate limited
|
|
344
359
|
// independently.
|
|
345
|
-
if (this.subsequentTooManyRequestsCounter
|
|
360
|
+
if (this.subsequentTooManyRequestsCounter === TOO_MANY_SUBSEQUENT_429_ERRORS) {
|
|
346
361
|
this.sdkEvents.requestsThrottled();
|
|
347
362
|
}
|
|
348
363
|
}
|
|
@@ -373,4 +388,20 @@ export class DriveAPIService {
|
|
|
373
388
|
this.subsequentServerErrorsCounter = 0;
|
|
374
389
|
this.lastServerErrorAt = undefined;
|
|
375
390
|
}
|
|
391
|
+
|
|
392
|
+
private offlineErrorHappened() {
|
|
393
|
+
this.subsequentOfflineErrorsCounter++;
|
|
394
|
+
|
|
395
|
+
if (this.subsequentOfflineErrorsCounter === TOO_MANY_SUBSEQUENT_OFFLINE_ERRORS) {
|
|
396
|
+
this.sdkEvents.transfersPaused();
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
private clearSubsequentOfflineErrors() {
|
|
401
|
+
if (this.subsequentOfflineErrorsCounter >= TOO_MANY_SUBSEQUENT_OFFLINE_ERRORS) {
|
|
402
|
+
this.sdkEvents.transfersResumed();
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
this.subsequentOfflineErrorsCounter = 0;
|
|
406
|
+
}
|
|
376
407
|
}
|