@protontech/drive-sdk 0.2.1 → 0.3.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/interface.d.ts +5 -0
- package/dist/diagnostic/httpClient.d.ts +3 -3
- package/dist/diagnostic/interface.d.ts +26 -29
- package/dist/diagnostic/sdkDiagnostic.js +50 -24
- package/dist/diagnostic/sdkDiagnostic.js.map +1 -1
- package/dist/errors.d.ts +3 -3
- package/dist/errors.js +7 -7
- package/dist/errors.js.map +1 -1
- package/dist/interface/author.d.ts +1 -1
- package/dist/interface/events.d.ts +1 -1
- package/dist/interface/events.js.map +1 -1
- package/dist/interface/httpClient.d.ts +5 -5
- package/dist/interface/index.d.ts +15 -5
- package/dist/internal/apiService/apiService.js +12 -4
- package/dist/internal/apiService/apiService.js.map +1 -1
- package/dist/internal/apiService/errorCodes.d.ts +1 -0
- package/dist/internal/apiService/errorCodes.js.map +1 -1
- package/dist/internal/apiService/errors.d.ts +4 -3
- package/dist/internal/apiService/errors.js +7 -4
- package/dist/internal/apiService/errors.js.map +1 -1
- package/dist/internal/apiService/errors.test.js +2 -1
- package/dist/internal/apiService/errors.test.js.map +1 -1
- package/dist/internal/events/index.d.ts +1 -1
- package/dist/internal/nodes/apiService.js +3 -0
- package/dist/internal/nodes/apiService.js.map +1 -1
- package/dist/internal/nodes/apiService.test.js +18 -0
- package/dist/internal/nodes/apiService.test.js.map +1 -1
- package/dist/internal/nodes/cryptoCache.js +6 -7
- package/dist/internal/nodes/cryptoCache.js.map +1 -1
- package/dist/internal/nodes/cryptoCache.test.js +4 -7
- package/dist/internal/nodes/cryptoCache.test.js.map +1 -1
- package/dist/internal/nodes/cryptoService.js +44 -20
- package/dist/internal/nodes/cryptoService.js.map +1 -1
- package/dist/internal/nodes/nodesAccess.js +2 -2
- package/dist/internal/nodes/nodesAccess.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.js +0 -2
- package/dist/internal/nodes/nodesManagement.js.map +1 -1
- package/dist/internal/shares/cryptoCache.d.ts +4 -3
- package/dist/internal/shares/cryptoCache.js +23 -6
- package/dist/internal/shares/cryptoCache.js.map +1 -1
- package/dist/internal/shares/cryptoCache.test.js +3 -2
- package/dist/internal/shares/cryptoCache.test.js.map +1 -1
- package/dist/internal/shares/index.js +1 -1
- package/dist/internal/shares/index.js.map +1 -1
- package/dist/internal/sharing/cryptoService.js +8 -6
- package/dist/internal/sharing/cryptoService.js.map +1 -1
- package/dist/internal/sharing/cryptoService.test.js +13 -0
- package/dist/internal/sharing/cryptoService.test.js.map +1 -1
- package/dist/internal/sharing/index.js +1 -1
- package/dist/internal/sharing/index.js.map +1 -1
- package/dist/internal/sharing/interface.d.ts +0 -4
- package/dist/internal/sharing/sharingAccess.d.ts +1 -0
- package/dist/internal/sharing/sharingAccess.js +6 -1
- package/dist/internal/sharing/sharingAccess.js.map +1 -1
- package/dist/internal/sharing/sharingAccess.test.js +3 -3
- package/dist/internal/sharing/sharingAccess.test.js.map +1 -1
- package/dist/internal/sharing/sharingManagement.d.ts +3 -1
- package/dist/internal/sharing/sharingManagement.js +37 -17
- package/dist/internal/sharing/sharingManagement.js.map +1 -1
- package/dist/internal/sharing/sharingManagement.test.js +61 -14
- package/dist/internal/sharing/sharingManagement.test.js.map +1 -1
- package/dist/internal/sharingPublic/apiService.d.ts +19 -0
- package/dist/internal/sharingPublic/apiService.js +134 -0
- package/dist/internal/sharingPublic/apiService.js.map +1 -0
- package/dist/internal/sharingPublic/cryptoCache.d.ts +19 -0
- package/dist/internal/sharingPublic/cryptoCache.js +72 -0
- package/dist/internal/sharingPublic/cryptoCache.js.map +1 -0
- package/dist/internal/sharingPublic/cryptoService.d.ts +23 -0
- package/dist/internal/sharingPublic/cryptoService.js +120 -0
- package/dist/internal/sharingPublic/cryptoService.js.map +1 -0
- package/dist/internal/sharingPublic/index.d.ts +15 -0
- package/dist/internal/sharingPublic/index.js +27 -0
- package/dist/internal/sharingPublic/index.js.map +1 -0
- package/dist/internal/sharingPublic/interface.d.ts +48 -0
- package/dist/internal/sharingPublic/interface.js +3 -0
- package/dist/internal/sharingPublic/interface.js.map +1 -0
- package/dist/internal/sharingPublic/manager.d.ts +19 -0
- package/dist/internal/sharingPublic/manager.js +79 -0
- package/dist/internal/sharingPublic/manager.js.map +1 -0
- package/dist/internal/sharingPublic/session/apiService.d.ts +28 -0
- package/dist/internal/sharingPublic/session/apiService.js +55 -0
- package/dist/internal/sharingPublic/session/apiService.js.map +1 -0
- package/dist/internal/sharingPublic/session/httpClient.d.ts +16 -0
- package/dist/internal/sharingPublic/session/httpClient.js +41 -0
- package/dist/internal/sharingPublic/session/httpClient.js.map +1 -0
- package/dist/internal/sharingPublic/session/index.d.ts +1 -0
- package/dist/internal/sharingPublic/session/index.js +6 -0
- package/dist/internal/sharingPublic/session/index.js.map +1 -0
- package/dist/internal/sharingPublic/session/interface.d.ts +18 -0
- package/dist/internal/sharingPublic/session/interface.js +3 -0
- package/dist/internal/sharingPublic/session/interface.js.map +1 -0
- package/dist/internal/sharingPublic/session/manager.d.ts +49 -0
- package/dist/internal/sharingPublic/session/manager.js +75 -0
- package/dist/internal/sharingPublic/session/manager.js.map +1 -0
- package/dist/internal/sharingPublic/session/session.d.ts +34 -0
- package/dist/internal/sharingPublic/session/session.js +67 -0
- package/dist/internal/sharingPublic/session/session.js.map +1 -0
- package/dist/internal/sharingPublic/session/url.d.ts +12 -0
- package/dist/internal/sharingPublic/session/url.js +23 -0
- package/dist/internal/sharingPublic/session/url.js.map +1 -0
- package/dist/internal/sharingPublic/session/url.test.d.ts +1 -0
- package/dist/internal/sharingPublic/session/url.test.js +59 -0
- package/dist/internal/sharingPublic/session/url.test.js.map +1 -0
- package/dist/internal/upload/manager.js +1 -3
- package/dist/internal/upload/manager.js.map +1 -1
- package/dist/internal/upload/manager.test.js +2 -2
- package/dist/internal/upload/manager.test.js.map +1 -1
- package/dist/protonDriveClient.d.ts +25 -10
- package/dist/protonDriveClient.js +44 -22
- package/dist/protonDriveClient.js.map +1 -1
- package/dist/protonDrivePublicLinkClient.d.ts +48 -0
- package/dist/protonDrivePublicLinkClient.js +71 -0
- package/dist/protonDrivePublicLinkClient.js.map +1 -0
- package/package.json +1 -1
- package/src/crypto/interface.ts +11 -0
- package/src/diagnostic/httpClient.ts +4 -4
- package/src/diagnostic/interface.ts +27 -29
- package/src/diagnostic/sdkDiagnostic.ts +58 -30
- package/src/errors.ts +5 -5
- package/src/interface/author.ts +1 -1
- package/src/interface/events.ts +1 -7
- package/src/interface/httpClient.ts +5 -5
- package/src/interface/index.ts +18 -6
- package/src/internal/apiService/apiService.ts +13 -4
- package/src/internal/apiService/errorCodes.ts +1 -0
- package/src/internal/apiService/errors.test.ts +2 -1
- package/src/internal/apiService/errors.ts +15 -4
- package/src/internal/events/index.ts +1 -1
- package/src/internal/nodes/apiService.test.ts +28 -0
- package/src/internal/nodes/apiService.ts +3 -0
- package/src/internal/nodes/cryptoCache.test.ts +4 -7
- package/src/internal/nodes/cryptoCache.ts +6 -7
- package/src/internal/nodes/cryptoService.ts +68 -34
- package/src/internal/nodes/interface.ts +2 -0
- package/src/internal/nodes/nodesAccess.ts +2 -2
- package/src/internal/nodes/nodesManagement.ts +0 -3
- package/src/internal/shares/cryptoCache.test.ts +3 -2
- package/src/internal/shares/cryptoCache.ts +26 -7
- package/src/internal/shares/index.ts +1 -1
- package/src/internal/sharing/cryptoService.test.ts +22 -1
- package/src/internal/sharing/cryptoService.ts +8 -6
- package/src/internal/sharing/index.ts +1 -0
- package/src/internal/sharing/interface.ts +0 -4
- package/src/internal/sharing/sharingAccess.test.ts +4 -4
- package/src/internal/sharing/sharingAccess.ts +6 -0
- package/src/internal/sharing/sharingManagement.test.ts +87 -24
- package/src/internal/sharing/sharingManagement.ts +56 -16
- package/src/internal/sharingPublic/apiService.ts +164 -0
- package/src/internal/sharingPublic/cryptoCache.ts +79 -0
- package/src/internal/sharingPublic/cryptoService.ts +162 -0
- package/src/internal/sharingPublic/index.ts +40 -0
- package/src/internal/sharingPublic/interface.ts +59 -0
- package/src/internal/sharingPublic/manager.ts +85 -0
- package/src/internal/sharingPublic/session/apiService.ts +74 -0
- package/src/internal/sharingPublic/session/httpClient.ts +48 -0
- package/src/internal/sharingPublic/session/index.ts +1 -0
- package/src/internal/sharingPublic/session/interface.ts +20 -0
- package/src/internal/sharingPublic/session/manager.ts +97 -0
- package/src/internal/sharingPublic/session/session.ts +78 -0
- package/src/internal/sharingPublic/session/url.test.ts +72 -0
- package/src/internal/sharingPublic/session/url.ts +23 -0
- package/src/internal/upload/manager.test.ts +2 -2
- package/src/internal/upload/manager.ts +2 -4
- package/src/protonDriveClient.ts +64 -27
- package/src/protonDrivePublicLinkClient.ts +121 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ProtonDriveHTTPClient,
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
ProtonDriveHTTPClientBlobRequest,
|
|
4
|
+
ProtonDriveHTTPClientJsonRequest,
|
|
5
5
|
} from '../interface';
|
|
6
6
|
import { EventsGenerator } from './eventsGenerator';
|
|
7
7
|
|
|
@@ -19,7 +19,7 @@ export class DiagnosticHTTPClient extends EventsGenerator implements ProtonDrive
|
|
|
19
19
|
this.httpClient = httpClient;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
async fetchJson(options:
|
|
22
|
+
async fetchJson(options: ProtonDriveHTTPClientJsonRequest): Promise<Response> {
|
|
23
23
|
try {
|
|
24
24
|
const response = await this.httpClient.fetchJson(options);
|
|
25
25
|
|
|
@@ -78,7 +78,7 @@ export class DiagnosticHTTPClient extends EventsGenerator implements ProtonDrive
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
fetchBlob(options:
|
|
81
|
+
fetchBlob(options: ProtonDriveHTTPClientBlobRequest): Promise<Response> {
|
|
82
82
|
return this.httpClient.fetchBlob(options);
|
|
83
83
|
}
|
|
84
84
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Author, MaybeNode, MetricEvent, NodeType, AnonymousUser } from '../interface';
|
|
2
2
|
import { LogRecord } from '../telemetry';
|
|
3
3
|
|
|
4
4
|
export interface Diagnostic {
|
|
@@ -67,77 +67,59 @@ export type HttpErrorResult = {
|
|
|
67
67
|
// Event representing that node has some decryption or other (e.g., invalid name) issues.
|
|
68
68
|
export type DegradedNodeResult = {
|
|
69
69
|
type: 'degraded_node';
|
|
70
|
-
|
|
71
|
-
node: DegradedNode;
|
|
72
|
-
};
|
|
70
|
+
} & NodeDetails;
|
|
73
71
|
|
|
74
72
|
// Event representing that signature verification failing.
|
|
75
73
|
export type UnverifiedAuthorResult = {
|
|
76
74
|
type: 'unverified_author';
|
|
77
|
-
nodeUid: string;
|
|
78
|
-
revisionUid?: string;
|
|
79
75
|
authorType: string;
|
|
80
|
-
claimedAuthor?: string;
|
|
76
|
+
claimedAuthor?: string | AnonymousUser;
|
|
81
77
|
error: string;
|
|
82
|
-
|
|
83
|
-
};
|
|
78
|
+
} & NodeDetails;
|
|
84
79
|
|
|
85
80
|
// Event representing that field from the extended attributes is not valid format.
|
|
86
81
|
// Currently only `sha1` verification is supported.
|
|
87
82
|
export type ExtendedAttributesErrorResult = {
|
|
88
83
|
type: 'extended_attributes_error';
|
|
89
|
-
nodeUid: string;
|
|
90
|
-
revisionUid?: string;
|
|
91
84
|
field: 'sha1';
|
|
92
85
|
value: string;
|
|
93
|
-
};
|
|
86
|
+
} & NodeDetails;
|
|
94
87
|
|
|
95
88
|
// Event representing that field from the extended attributes is missing.
|
|
96
89
|
// Currently only `sha1` verification is supported.
|
|
97
90
|
export type ExtendedAttributesMissingFieldResult = {
|
|
98
91
|
type: 'extended_attributes_missing_field';
|
|
99
|
-
nodeUid: string;
|
|
100
|
-
revisionUid?: string;
|
|
101
92
|
missingField: 'sha1';
|
|
102
|
-
};
|
|
93
|
+
} & NodeDetails;
|
|
103
94
|
|
|
104
95
|
// Event representing that file is missing the active revision.
|
|
105
96
|
export type ContentFileMissingRevisionResult = {
|
|
106
97
|
type: 'content_file_missing_revision';
|
|
107
|
-
|
|
108
|
-
revisionUid?: string;
|
|
109
|
-
};
|
|
98
|
+
} & NodeDetails;
|
|
110
99
|
|
|
111
100
|
// Event representing that file content is not valid - either sha1 or size is not correct.
|
|
112
101
|
export type ContentIntegrityErrorResult = {
|
|
113
102
|
type: 'content_integrity_error';
|
|
114
|
-
nodeUid: string;
|
|
115
|
-
revisionUid?: string;
|
|
116
103
|
claimedSha1?: string;
|
|
117
104
|
computedSha1?: string;
|
|
118
105
|
claimedSizeInBytes?: number;
|
|
119
106
|
computedSizeInBytes?: number;
|
|
120
|
-
};
|
|
107
|
+
} & NodeDetails;
|
|
121
108
|
|
|
122
109
|
// Event representing that downloading the file content failed.
|
|
123
110
|
// This can be connection issue or server error. If its integrity issue,
|
|
124
111
|
// it should be reported as `ContentIntegrityErrorResult`.
|
|
125
112
|
export type ContentDownloadErrorResult = {
|
|
126
113
|
type: 'content_download_error';
|
|
127
|
-
nodeUid: string;
|
|
128
|
-
revisionUid?: string;
|
|
129
114
|
error: unknown;
|
|
130
|
-
};
|
|
115
|
+
} & NodeDetails;
|
|
131
116
|
|
|
132
117
|
// Event representing that getting the thumbnails failed.
|
|
133
118
|
// This can be connection issue or server error.
|
|
134
119
|
export type ThumbnailsErrorResult = {
|
|
135
120
|
type: 'thumbnails_error';
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
message?: string;
|
|
139
|
-
error?: unknown;
|
|
140
|
-
};
|
|
121
|
+
error: unknown;
|
|
122
|
+
} & NodeDetails;
|
|
141
123
|
|
|
142
124
|
// Event representing errors logged during the diagnostic.
|
|
143
125
|
export type LogErrorResult = {
|
|
@@ -156,3 +138,19 @@ export type MetricResult = {
|
|
|
156
138
|
type: 'metric';
|
|
157
139
|
event: MetricEvent;
|
|
158
140
|
};
|
|
141
|
+
|
|
142
|
+
export type NodeDetails = {
|
|
143
|
+
safeNodeDetails: {
|
|
144
|
+
nodeUid: string;
|
|
145
|
+
revisionUid?: string;
|
|
146
|
+
nodeType: NodeType;
|
|
147
|
+
nodeCreationTime: Date;
|
|
148
|
+
keyAuthor: Author;
|
|
149
|
+
nameAuthor: Author;
|
|
150
|
+
errors: {
|
|
151
|
+
field: string;
|
|
152
|
+
error: unknown;
|
|
153
|
+
}[];
|
|
154
|
+
};
|
|
155
|
+
sensitiveNodeDetails: MaybeNode;
|
|
156
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Author, FileDownloader, MaybeNode, NodeType, Revision, ThumbnailType } from '../interface';
|
|
2
2
|
import { ProtonDriveClient } from '../protonDriveClient';
|
|
3
|
-
import { Diagnostic, DiagnosticOptions, DiagnosticResult } from './interface';
|
|
3
|
+
import { Diagnostic, DiagnosticOptions, DiagnosticResult, NodeDetails } from './interface';
|
|
4
4
|
import { IntegrityVerificationStream } from './integrityVerificationStream';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -43,32 +43,19 @@ export class SDKDiagnostic implements Diagnostic {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
private async *verifyNode(node: MaybeNode, options?: DiagnosticOptions): AsyncGenerator<DiagnosticResult> {
|
|
46
|
-
const nodeUid = node.ok ? node.value.uid : node.error.uid;
|
|
47
|
-
|
|
48
46
|
if (!node.ok) {
|
|
49
47
|
yield {
|
|
50
48
|
type: 'degraded_node',
|
|
51
|
-
|
|
52
|
-
node: node.error,
|
|
49
|
+
...getNodeDetails(node),
|
|
53
50
|
};
|
|
54
51
|
}
|
|
55
52
|
|
|
53
|
+
yield* this.verifyAuthor(node.ok ? node.value.keyAuthor : node.error.keyAuthor, 'key', node);
|
|
54
|
+
yield* this.verifyAuthor(node.ok ? node.value.nameAuthor : node.error.nameAuthor, 'name', node);
|
|
55
|
+
|
|
56
56
|
const activeRevision = getActiveRevision(node);
|
|
57
|
-
const nodeInfo = {
|
|
58
|
-
...getNodeUids(node),
|
|
59
|
-
node,
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
yield* this.verifyAuthor(node.ok ? node.value.keyAuthor : node.error.keyAuthor, {
|
|
63
|
-
...nodeInfo,
|
|
64
|
-
authorType: 'key',
|
|
65
|
-
});
|
|
66
|
-
yield* this.verifyAuthor(node.ok ? node.value.nameAuthor : node.error.nameAuthor, {
|
|
67
|
-
...nodeInfo,
|
|
68
|
-
authorType: 'name',
|
|
69
|
-
});
|
|
70
57
|
if (activeRevision) {
|
|
71
|
-
yield* this.verifyAuthor(activeRevision.contentAuthor,
|
|
58
|
+
yield* this.verifyAuthor(activeRevision.contentAuthor, 'content', node);
|
|
72
59
|
}
|
|
73
60
|
|
|
74
61
|
yield* this.verifyFileExtendedAttributes(node);
|
|
@@ -81,16 +68,14 @@ export class SDKDiagnostic implements Diagnostic {
|
|
|
81
68
|
}
|
|
82
69
|
}
|
|
83
70
|
|
|
84
|
-
private async *verifyAuthor(
|
|
85
|
-
author: Author,
|
|
86
|
-
info: { nodeUid: string; authorType: string; revisionUid?: string; node: MaybeNode },
|
|
87
|
-
): AsyncGenerator<DiagnosticResult> {
|
|
71
|
+
private async *verifyAuthor(author: Author, authorType: string, node: MaybeNode): AsyncGenerator<DiagnosticResult> {
|
|
88
72
|
if (!author.ok) {
|
|
89
73
|
yield {
|
|
90
74
|
type: 'unverified_author',
|
|
75
|
+
authorType,
|
|
91
76
|
claimedAuthor: author.error.claimedAuthor,
|
|
92
77
|
error: author.error.error,
|
|
93
|
-
...
|
|
78
|
+
...getNodeDetails(node),
|
|
94
79
|
};
|
|
95
80
|
}
|
|
96
81
|
}
|
|
@@ -104,17 +89,17 @@ export class SDKDiagnostic implements Diagnostic {
|
|
|
104
89
|
if (claimedSha1 && !/^[0-9a-f]{40}$/i.test(claimedSha1)) {
|
|
105
90
|
yield {
|
|
106
91
|
type: 'extended_attributes_error',
|
|
107
|
-
...getNodeUids(node),
|
|
108
92
|
field: 'sha1',
|
|
109
93
|
value: claimedSha1,
|
|
94
|
+
...getNodeDetails(node),
|
|
110
95
|
};
|
|
111
96
|
}
|
|
112
97
|
|
|
113
98
|
if (expectedAttributes && !claimedSha1) {
|
|
114
99
|
yield {
|
|
115
100
|
type: 'extended_attributes_missing_field',
|
|
116
|
-
...getNodeUids(node),
|
|
117
101
|
missingField: 'sha1',
|
|
102
|
+
...getNodeDetails(node),
|
|
118
103
|
};
|
|
119
104
|
}
|
|
120
105
|
}
|
|
@@ -127,7 +112,7 @@ export class SDKDiagnostic implements Diagnostic {
|
|
|
127
112
|
if (!activeRevision) {
|
|
128
113
|
yield {
|
|
129
114
|
type: 'content_file_missing_revision',
|
|
130
|
-
|
|
115
|
+
...getNodeDetails(node),
|
|
131
116
|
};
|
|
132
117
|
return;
|
|
133
118
|
}
|
|
@@ -158,18 +143,18 @@ export class SDKDiagnostic implements Diagnostic {
|
|
|
158
143
|
if (claimedSha1 !== computedSha1 || claimedSizeInBytes !== computedSizeInBytes) {
|
|
159
144
|
yield {
|
|
160
145
|
type: 'content_integrity_error',
|
|
161
|
-
...getNodeUids(node),
|
|
162
146
|
claimedSha1,
|
|
163
147
|
computedSha1,
|
|
164
148
|
claimedSizeInBytes,
|
|
165
149
|
computedSizeInBytes,
|
|
150
|
+
...getNodeDetails(node),
|
|
166
151
|
};
|
|
167
152
|
}
|
|
168
153
|
} catch (error: unknown) {
|
|
169
154
|
yield {
|
|
170
155
|
type: 'content_download_error',
|
|
171
|
-
...getNodeUids(node),
|
|
172
156
|
error,
|
|
157
|
+
...getNodeDetails(node),
|
|
173
158
|
};
|
|
174
159
|
}
|
|
175
160
|
}
|
|
@@ -197,8 +182,8 @@ export class SDKDiagnostic implements Diagnostic {
|
|
|
197
182
|
if (!result[0].ok && result[0].error !== 'Node has no thumbnail') {
|
|
198
183
|
yield {
|
|
199
184
|
type: 'thumbnails_error',
|
|
200
|
-
nodeUid,
|
|
201
185
|
error: result[0].error,
|
|
186
|
+
...getNodeDetails(node),
|
|
202
187
|
};
|
|
203
188
|
}
|
|
204
189
|
} catch (error: unknown) {
|
|
@@ -226,6 +211,49 @@ export class SDKDiagnostic implements Diagnostic {
|
|
|
226
211
|
}
|
|
227
212
|
}
|
|
228
213
|
|
|
214
|
+
function getNodeDetails(node: MaybeNode): NodeDetails {
|
|
215
|
+
const errors: {
|
|
216
|
+
field: string;
|
|
217
|
+
error: unknown;
|
|
218
|
+
}[] = [];
|
|
219
|
+
|
|
220
|
+
if (!node.ok) {
|
|
221
|
+
const degradedNode = node.error;
|
|
222
|
+
if (!degradedNode.name.ok) {
|
|
223
|
+
errors.push({
|
|
224
|
+
field: 'name',
|
|
225
|
+
error: degradedNode.name.error,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
if (degradedNode.activeRevision?.ok === false) {
|
|
229
|
+
errors.push({
|
|
230
|
+
field: 'activeRevision',
|
|
231
|
+
error: degradedNode.activeRevision.error,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
for (const error of degradedNode.errors ?? []) {
|
|
235
|
+
if (error instanceof Error) {
|
|
236
|
+
errors.push({
|
|
237
|
+
field: 'error',
|
|
238
|
+
error,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
safeNodeDetails: {
|
|
246
|
+
...getNodeUids(node),
|
|
247
|
+
nodeType: getNodeType(node),
|
|
248
|
+
nodeCreationTime: node.ok ? node.value.creationTime : node.error.creationTime,
|
|
249
|
+
keyAuthor: node.ok ? node.value.keyAuthor : node.error.keyAuthor,
|
|
250
|
+
nameAuthor: node.ok ? node.value.nameAuthor : node.error.nameAuthor,
|
|
251
|
+
errors,
|
|
252
|
+
},
|
|
253
|
+
sensitiveNodeDetails: node,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
229
257
|
function getNodeUids(node: MaybeNode): { nodeUid: string; revisionUid?: string } {
|
|
230
258
|
const activeRevision = getActiveRevision(node);
|
|
231
259
|
return {
|
package/src/errors.ts
CHANGED
|
@@ -70,17 +70,17 @@ export class ValidationError extends ProtonDriveError {
|
|
|
70
70
|
* or choose another name. The available name is provided in the `availableName`
|
|
71
71
|
* property (that will contain original name with the index that can be used).
|
|
72
72
|
*/
|
|
73
|
-
export class
|
|
74
|
-
name = '
|
|
73
|
+
export class NodeWithSameNameExistsValidationError extends ValidationError {
|
|
74
|
+
name = 'NodeWithSameNameExistsValidationError';
|
|
75
75
|
|
|
76
76
|
public readonly existingNodeUid?: string;
|
|
77
77
|
|
|
78
|
-
public readonly
|
|
78
|
+
public readonly isUnfinishedUpload: boolean;
|
|
79
79
|
|
|
80
|
-
constructor(message: string, code: number, existingNodeUid?: string,
|
|
80
|
+
constructor(message: string, code: number, existingNodeUid?: string, isUnfinishedUpload = false) {
|
|
81
81
|
super(message, code);
|
|
82
82
|
this.existingNodeUid = existingNodeUid;
|
|
83
|
-
this.
|
|
83
|
+
this.isUnfinishedUpload = isUnfinishedUpload;
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
package/src/interface/author.ts
CHANGED
package/src/interface/events.ts
CHANGED
|
@@ -20,13 +20,7 @@ export interface LatestEventIdProvider {
|
|
|
20
20
|
*/
|
|
21
21
|
export type DriveListener = (event: DriveEvent) => Promise<void>;
|
|
22
22
|
|
|
23
|
-
export type DriveEvent =
|
|
24
|
-
| NodeEvent
|
|
25
|
-
| FastForwardEvent
|
|
26
|
-
| TreeRefreshEvent
|
|
27
|
-
| TreeRemovalEvent
|
|
28
|
-
| FastForwardEvent
|
|
29
|
-
| SharedWithMeUpdated;
|
|
23
|
+
export type DriveEvent = NodeEvent | FastForwardEvent | TreeRefreshEvent | TreeRemovalEvent | SharedWithMeUpdated;
|
|
30
24
|
|
|
31
25
|
export type NodeEvent =
|
|
32
26
|
| {
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
export interface ProtonDriveHTTPClient {
|
|
2
|
-
fetchJson(
|
|
3
|
-
fetchBlob(
|
|
2
|
+
fetchJson(request: ProtonDriveHTTPClientJsonRequest): Promise<Response>;
|
|
3
|
+
fetchBlob(request: ProtonDriveHTTPClientBlobRequest): Promise<Response>;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
export type
|
|
6
|
+
export type ProtonDriveHTTPClientJsonRequest = ProtonDriveHTTPClientBaseRequest & {
|
|
7
7
|
json?: object;
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
-
export type
|
|
10
|
+
export type ProtonDriveHTTPClientBlobRequest = ProtonDriveHTTPClientBaseRequest & {
|
|
11
11
|
body?: XMLHttpRequestBodyInit;
|
|
12
12
|
onProgress?: (progress: number) => void;
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
type
|
|
15
|
+
type ProtonDriveHTTPClientBaseRequest = {
|
|
16
16
|
url: string;
|
|
17
17
|
method: string;
|
|
18
18
|
headers: Headers;
|
package/src/interface/index.ts
CHANGED
|
@@ -27,8 +27,8 @@ export type {
|
|
|
27
27
|
export { DriveEventType, SDKEvent } from './events';
|
|
28
28
|
export type {
|
|
29
29
|
ProtonDriveHTTPClient,
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
ProtonDriveHTTPClientJsonRequest,
|
|
31
|
+
ProtonDriveHTTPClientBlobRequest,
|
|
32
32
|
} from './httpClient';
|
|
33
33
|
export type {
|
|
34
34
|
MaybeNode,
|
|
@@ -89,10 +89,22 @@ export type ProtonDriveTelemetry = Telemetry<MetricEvent>;
|
|
|
89
89
|
export type ProtonDriveEntitiesCache = ProtonDriveCache<string>;
|
|
90
90
|
export type ProtonDriveCryptoCache = ProtonDriveCache<CachedCryptoMaterial>;
|
|
91
91
|
export type CachedCryptoMaterial = {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
nodeKeys?: {
|
|
93
|
+
// Passphrase should not be needed to keep, sessionKey should be enough.
|
|
94
|
+
// We will improve this in the future.
|
|
95
|
+
passphrase: string;
|
|
96
|
+
key: PrivateKey;
|
|
97
|
+
passphraseSessionKey: SessionKey;
|
|
98
|
+
contentKeyPacketSessionKey?: SessionKey;
|
|
99
|
+
hashKey?: Uint8Array;
|
|
100
|
+
};
|
|
101
|
+
shareKey?: {
|
|
102
|
+
key: PrivateKey;
|
|
103
|
+
passphraseSessionKey: SessionKey;
|
|
104
|
+
};
|
|
105
|
+
publicShareKey?: {
|
|
106
|
+
key: PrivateKey;
|
|
107
|
+
};
|
|
96
108
|
};
|
|
97
109
|
|
|
98
110
|
export interface ProtonDriveClientContructorParameters {
|
|
@@ -165,7 +165,7 @@ export class DriveAPIService {
|
|
|
165
165
|
if (error instanceof ProtonDriveError) {
|
|
166
166
|
throw error;
|
|
167
167
|
}
|
|
168
|
-
throw apiErrorFactory({ response });
|
|
168
|
+
throw apiErrorFactory({ response, error });
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
171
|
|
|
@@ -239,7 +239,11 @@ export class DriveAPIService {
|
|
|
239
239
|
throw new AbortError(c('Error').t`Request aborted`);
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
-
|
|
242
|
+
if (attempt > 0) {
|
|
243
|
+
this.logger.debug(`${request.method} ${request.url}: retry ${attempt}`);
|
|
244
|
+
} else {
|
|
245
|
+
this.logger.debug(`${request.method} ${request.url}`);
|
|
246
|
+
}
|
|
243
247
|
|
|
244
248
|
if (this.hasReachedServerErrorLimit) {
|
|
245
249
|
this.logger.warn('Server errors limit reached');
|
|
@@ -250,6 +254,8 @@ export class DriveAPIService {
|
|
|
250
254
|
throw new RateLimitedError(c('Error').t`Too many server requests, please try again later`);
|
|
251
255
|
}
|
|
252
256
|
|
|
257
|
+
const start = Date.now();
|
|
258
|
+
|
|
253
259
|
let response;
|
|
254
260
|
try {
|
|
255
261
|
response = await callback();
|
|
@@ -276,10 +282,13 @@ export class DriveAPIService {
|
|
|
276
282
|
throw error;
|
|
277
283
|
}
|
|
278
284
|
|
|
285
|
+
const end = Date.now();
|
|
286
|
+
const duration = end - start;
|
|
287
|
+
|
|
279
288
|
if (response.ok) {
|
|
280
|
-
this.logger.info(`${request.method} ${request.url}: ${response.status}`);
|
|
289
|
+
this.logger.info(`${request.method} ${request.url}: ${response.status} (${duration}ms)`);
|
|
281
290
|
} else {
|
|
282
|
-
this.logger.warn(`${request.method} ${request.url}: ${response.status}`);
|
|
291
|
+
this.logger.warn(`${request.method} ${request.url}: ${response.status} (${duration}ms)`);
|
|
283
292
|
}
|
|
284
293
|
|
|
285
294
|
if (response.status === HTTPErrorCode.TOO_MANY_REQUESTS) {
|
|
@@ -62,7 +62,8 @@ describe('apiErrorFactory should return', () => {
|
|
|
62
62
|
it('NotFoundAPIError when code is ErrorCode.NOT_EXISTS', () => {
|
|
63
63
|
const error = apiErrorFactory(mockAPIResponseAndResult({ code: ErrorCode.NOT_EXISTS, message: 'Not found' }));
|
|
64
64
|
expect(error).toBeInstanceOf(errors.NotFoundAPIError);
|
|
65
|
-
|
|
65
|
+
expect(error.message).toBe('Not found');
|
|
66
|
+
expect((error as errors.NotFoundAPIError).code).toBe(ErrorCode.NOT_EXISTS);
|
|
66
67
|
});
|
|
67
68
|
});
|
|
68
69
|
|
|
@@ -3,12 +3,23 @@ import { c } from 'ttag';
|
|
|
3
3
|
import { ServerError, ValidationError } from '../../errors';
|
|
4
4
|
import { ErrorCode, HTTPErrorCode } from './errorCodes';
|
|
5
5
|
|
|
6
|
-
export function apiErrorFactory({
|
|
6
|
+
export function apiErrorFactory({
|
|
7
|
+
response,
|
|
8
|
+
result,
|
|
9
|
+
error,
|
|
10
|
+
}: {
|
|
11
|
+
response: Response;
|
|
12
|
+
result?: unknown;
|
|
13
|
+
error?: unknown;
|
|
14
|
+
}): ServerError {
|
|
7
15
|
// Backend responses with 404 both in the response and body code.
|
|
8
16
|
// In such a case we want to stick to APIHTTPError to be very clear
|
|
9
17
|
// it is not NotFoundAPIError.
|
|
10
18
|
if (response.status === HTTPErrorCode.NOT_FOUND || !result) {
|
|
11
|
-
|
|
19
|
+
const fallbackMessage = error instanceof Error ? error.message : c('Error').t`Unknown error`;
|
|
20
|
+
const apiHttpError = new APIHTTPError(response.statusText || fallbackMessage, response.status);
|
|
21
|
+
apiHttpError.cause = error;
|
|
22
|
+
return apiHttpError;
|
|
12
23
|
}
|
|
13
24
|
|
|
14
25
|
const typedResult = result as {
|
|
@@ -40,7 +51,7 @@ export function apiErrorFactory({ response, result }: { response: Response; resu
|
|
|
40
51
|
|
|
41
52
|
switch (code) {
|
|
42
53
|
case ErrorCode.NOT_EXISTS:
|
|
43
|
-
return new NotFoundAPIError(message, code);
|
|
54
|
+
return new NotFoundAPIError(message, code, details);
|
|
44
55
|
// ValidationError should be only when it is clearly user input error,
|
|
45
56
|
// otherwise it should be ServerError.
|
|
46
57
|
// Here we convert only general enough codes. Specific cases that are
|
|
@@ -94,6 +105,6 @@ export class APICodeError extends ServerError {
|
|
|
94
105
|
}
|
|
95
106
|
}
|
|
96
107
|
|
|
97
|
-
export class NotFoundAPIError extends
|
|
108
|
+
export class NotFoundAPIError extends ValidationError {
|
|
98
109
|
name = 'NotFoundAPIError';
|
|
99
110
|
}
|
|
@@ -7,7 +7,7 @@ import { VolumeEventManager } from './volumeEventManager';
|
|
|
7
7
|
import { EventManager } from './eventManager';
|
|
8
8
|
import { SharesManager } from '../shares/manager';
|
|
9
9
|
|
|
10
|
-
export type { DriveEvent, DriveListener } from './interface';
|
|
10
|
+
export type { DriveEvent, DriveListener, EventSubscription } from './interface';
|
|
11
11
|
export { DriveEventType } from './interface';
|
|
12
12
|
|
|
13
13
|
const OWN_VOLUME_POLLING_INTERVAL = 30;
|
|
@@ -173,6 +173,34 @@ describe('nodeAPIService', () => {
|
|
|
173
173
|
api = new NodeAPIService(getMockLogger(), apiMock);
|
|
174
174
|
});
|
|
175
175
|
|
|
176
|
+
describe('getNode', () => {
|
|
177
|
+
it('should get node', async () => {
|
|
178
|
+
// @ts-expect-error Mocking for testing purposes
|
|
179
|
+
apiMock.post = jest.fn(async () =>
|
|
180
|
+
Promise.resolve({
|
|
181
|
+
Links: [generateAPIFolderNode()],
|
|
182
|
+
}),
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
const node = await api.getNode('volumeId~nodeId', 'volumeId');
|
|
186
|
+
|
|
187
|
+
expect(node).toStrictEqual(generateFolderNode());
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should throw error if node is not found', async () => {
|
|
191
|
+
// @ts-expect-error Mocking for testing purposes
|
|
192
|
+
apiMock.post = jest.fn(async () =>
|
|
193
|
+
Promise.resolve({
|
|
194
|
+
Links: [],
|
|
195
|
+
}),
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
const promise = api.getNode('volumeId~nodeId', 'volumeId');
|
|
199
|
+
|
|
200
|
+
await expect(promise).rejects.toThrow('Node not found');
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
176
204
|
describe('iterateNodes', () => {
|
|
177
205
|
async function testIterateNodes(mockedLink: any, expectedNode: any, ownVolumeId = 'volumeId') {
|
|
178
206
|
// @ts-expect-error Mocking for testing purposes
|
|
@@ -110,6 +110,9 @@ export class NodeAPIService {
|
|
|
110
110
|
async getNode(nodeUid: string, ownVolumeId: string, signal?: AbortSignal): Promise<EncryptedNode> {
|
|
111
111
|
const nodesGenerator = this.iterateNodes([nodeUid], ownVolumeId, signal);
|
|
112
112
|
const result = await nodesGenerator.next();
|
|
113
|
+
if (!result.value) {
|
|
114
|
+
throw new ValidationError(c('Error').t`Node not found`);
|
|
115
|
+
}
|
|
113
116
|
await nodesGenerator.return('finish');
|
|
114
117
|
return result.value;
|
|
115
118
|
}
|
|
@@ -18,10 +18,7 @@ describe('nodesCryptoCache', () => {
|
|
|
18
18
|
|
|
19
19
|
beforeEach(async () => {
|
|
20
20
|
memoryCache = new MemoryCache();
|
|
21
|
-
await memoryCache.setEntity('nodeKeys-
|
|
22
|
-
key: 'privateKey',
|
|
23
|
-
sessionKey: 'sessionKey',
|
|
24
|
-
} as any);
|
|
21
|
+
await memoryCache.setEntity('nodeKeys-missingProperties', {} as any);
|
|
25
22
|
|
|
26
23
|
cache = new NodesCryptoCache(getMockLogger(), memoryCache);
|
|
27
24
|
});
|
|
@@ -96,14 +93,14 @@ describe('nodesCryptoCache', () => {
|
|
|
96
93
|
|
|
97
94
|
it('should throw an error when retrieving a bad keys and remove the key', async () => {
|
|
98
95
|
try {
|
|
99
|
-
await cache.getNodeKeys('
|
|
96
|
+
await cache.getNodeKeys('missingProperties');
|
|
100
97
|
throw new Error('Should have thrown an error');
|
|
101
98
|
} catch (error) {
|
|
102
|
-
expect(`${error}`).toBe('Error: Failed to deserialize node keys
|
|
99
|
+
expect(`${error}`).toBe('Error: Failed to deserialize node keys');
|
|
103
100
|
}
|
|
104
101
|
|
|
105
102
|
try {
|
|
106
|
-
await memoryCache.getEntity('nodeKeys-
|
|
103
|
+
await memoryCache.getEntity('nodeKeys-missingProperties');
|
|
107
104
|
throw new Error('Should have thrown an error');
|
|
108
105
|
} catch (error) {
|
|
109
106
|
expect(`${error}`).toBe('Error: Entity not found');
|
|
@@ -18,12 +18,14 @@ export class NodesCryptoCache {
|
|
|
18
18
|
|
|
19
19
|
async setNodeKeys(nodeUid: string, keys: DecryptedNodeKeys): Promise<void> {
|
|
20
20
|
const cacheUid = getCacheKey(nodeUid);
|
|
21
|
-
await this.driveCache.setEntity(cacheUid,
|
|
21
|
+
await this.driveCache.setEntity(cacheUid, {
|
|
22
|
+
nodeKeys: keys,
|
|
23
|
+
});
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
async getNodeKeys(nodeUid: string): Promise<DecryptedNodeKeys> {
|
|
25
27
|
const nodeKeysData = await this.driveCache.getEntity(getCacheKey(nodeUid));
|
|
26
|
-
if (!nodeKeysData.
|
|
28
|
+
if (!nodeKeysData.nodeKeys) {
|
|
27
29
|
try {
|
|
28
30
|
await this.removeNodeKeys([nodeUid]);
|
|
29
31
|
} catch (removingError: unknown) {
|
|
@@ -33,12 +35,9 @@ export class NodesCryptoCache {
|
|
|
33
35
|
`Failed to remove corrupted node keys from the cache: ${removingError instanceof Error ? removingError.message : removingError}`,
|
|
34
36
|
);
|
|
35
37
|
}
|
|
36
|
-
throw new Error(`Failed to deserialize node keys
|
|
38
|
+
throw new Error(`Failed to deserialize node keys`);
|
|
37
39
|
}
|
|
38
|
-
return
|
|
39
|
-
...nodeKeysData,
|
|
40
|
-
passphrase: nodeKeysData.passphrase,
|
|
41
|
-
};
|
|
40
|
+
return nodeKeysData.nodeKeys;
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
async removeNodeKeys(nodeUids: string[]): Promise<void> {
|