@protontech/drive-sdk 0.0.13 → 0.1.0

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 (234) hide show
  1. package/dist/cache/index.d.ts +1 -0
  2. package/dist/cache/index.js +3 -1
  3. package/dist/cache/index.js.map +1 -1
  4. package/dist/cache/memoryCache.d.ts +1 -1
  5. package/dist/cache/nullCache.d.ts +14 -0
  6. package/dist/cache/nullCache.js +37 -0
  7. package/dist/cache/nullCache.js.map +1 -0
  8. package/dist/config.d.ts +16 -1
  9. package/dist/config.js +1 -1
  10. package/dist/config.js.map +1 -1
  11. package/dist/crypto/openPGPCrypto.js +2 -0
  12. package/dist/crypto/openPGPCrypto.js.map +1 -1
  13. package/dist/diagnostic/eventsGenerator.d.ts +14 -0
  14. package/dist/diagnostic/eventsGenerator.js +49 -0
  15. package/dist/diagnostic/eventsGenerator.js.map +1 -0
  16. package/dist/diagnostic/httpClient.d.ts +16 -0
  17. package/dist/diagnostic/httpClient.js +81 -0
  18. package/dist/diagnostic/httpClient.js.map +1 -0
  19. package/dist/diagnostic/index.d.ts +10 -0
  20. package/dist/diagnostic/index.js +35 -0
  21. package/dist/diagnostic/index.js.map +1 -0
  22. package/dist/diagnostic/integrityVerificationStream.d.ts +21 -0
  23. package/dist/diagnostic/integrityVerificationStream.js +56 -0
  24. package/dist/diagnostic/integrityVerificationStream.js.map +1 -0
  25. package/dist/diagnostic/interface.d.ts +102 -0
  26. package/dist/diagnostic/interface.js +3 -0
  27. package/dist/diagnostic/interface.js.map +1 -0
  28. package/dist/diagnostic/sdkDiagnostic.d.ts +22 -0
  29. package/dist/diagnostic/sdkDiagnostic.js +216 -0
  30. package/dist/diagnostic/sdkDiagnostic.js.map +1 -0
  31. package/dist/diagnostic/sdkDiagnosticFull.d.ts +18 -0
  32. package/dist/diagnostic/sdkDiagnosticFull.js +35 -0
  33. package/dist/diagnostic/sdkDiagnosticFull.js.map +1 -0
  34. package/dist/diagnostic/telemetry.d.ts +25 -0
  35. package/dist/diagnostic/telemetry.js +70 -0
  36. package/dist/diagnostic/telemetry.js.map +1 -0
  37. package/dist/diagnostic/zipGenerators.d.ts +9 -0
  38. package/dist/diagnostic/zipGenerators.js +64 -0
  39. package/dist/diagnostic/zipGenerators.js.map +1 -0
  40. package/dist/diagnostic/zipGenerators.test.js +144 -0
  41. package/dist/diagnostic/zipGenerators.test.js.map +1 -0
  42. package/dist/errors.d.ts +2 -1
  43. package/dist/errors.js +3 -1
  44. package/dist/errors.js.map +1 -1
  45. package/dist/interface/config.d.ts +26 -0
  46. package/dist/interface/config.js +3 -0
  47. package/dist/interface/config.js.map +1 -0
  48. package/dist/interface/download.d.ts +2 -2
  49. package/dist/interface/events.d.ts +60 -20
  50. package/dist/interface/events.js +11 -1
  51. package/dist/interface/events.js.map +1 -1
  52. package/dist/interface/httpClient.d.ts +0 -14
  53. package/dist/interface/index.d.ts +8 -4
  54. package/dist/interface/index.js +2 -1
  55. package/dist/interface/index.js.map +1 -1
  56. package/dist/interface/nodes.d.ts +9 -0
  57. package/dist/interface/nodes.js.map +1 -1
  58. package/dist/interface/sharing.d.ts +1 -0
  59. package/dist/interface/upload.d.ts +6 -0
  60. package/dist/internal/download/apiService.js +32 -31
  61. package/dist/internal/download/apiService.js.map +1 -1
  62. package/dist/internal/download/fileDownloader.d.ts +2 -2
  63. package/dist/internal/download/fileDownloader.js.map +1 -1
  64. package/dist/internal/events/apiService.d.ts +4 -6
  65. package/dist/internal/events/apiService.js +15 -22
  66. package/dist/internal/events/apiService.js.map +1 -1
  67. package/dist/internal/events/coreEventManager.d.ts +7 -10
  68. package/dist/internal/events/coreEventManager.js +19 -36
  69. package/dist/internal/events/coreEventManager.js.map +1 -1
  70. package/dist/internal/events/coreEventManager.test.js +87 -0
  71. package/dist/internal/events/coreEventManager.test.js.map +1 -0
  72. package/dist/internal/events/eventManager.d.ts +11 -36
  73. package/dist/internal/events/eventManager.js +59 -105
  74. package/dist/internal/events/eventManager.js.map +1 -1
  75. package/dist/internal/events/eventManager.test.js +167 -82
  76. package/dist/internal/events/eventManager.test.js.map +1 -1
  77. package/dist/internal/events/index.d.ts +13 -33
  78. package/dist/internal/events/index.js +56 -72
  79. package/dist/internal/events/index.js.map +1 -1
  80. package/dist/internal/events/interface.d.ts +59 -14
  81. package/dist/internal/events/interface.js +13 -3
  82. package/dist/internal/events/interface.js.map +1 -1
  83. package/dist/internal/events/volumeEventManager.d.ts +7 -17
  84. package/dist/internal/events/volumeEventManager.js +58 -45
  85. package/dist/internal/events/volumeEventManager.js.map +1 -1
  86. package/dist/internal/events/volumeEventManager.test.d.ts +1 -0
  87. package/dist/internal/events/volumeEventManager.test.js +203 -0
  88. package/dist/internal/events/volumeEventManager.test.js.map +1 -0
  89. package/dist/internal/nodes/cache.d.ts +10 -1
  90. package/dist/internal/nodes/cache.js +17 -0
  91. package/dist/internal/nodes/cache.js.map +1 -1
  92. package/dist/internal/nodes/cryptoService.d.ts +1 -1
  93. package/dist/internal/nodes/cryptoService.js.map +1 -1
  94. package/dist/internal/nodes/events.d.ts +7 -83
  95. package/dist/internal/nodes/events.js +43 -217
  96. package/dist/internal/nodes/events.js.map +1 -1
  97. package/dist/internal/nodes/events.test.js +27 -277
  98. package/dist/internal/nodes/events.test.js.map +1 -1
  99. package/dist/internal/nodes/index.d.ts +3 -4
  100. package/dist/internal/nodes/index.js +5 -5
  101. package/dist/internal/nodes/index.js.map +1 -1
  102. package/dist/internal/nodes/nodesAccess.d.ts +15 -0
  103. package/dist/internal/nodes/nodesAccess.js +37 -0
  104. package/dist/internal/nodes/nodesAccess.js.map +1 -1
  105. package/dist/internal/nodes/nodesAccess.test.js +131 -93
  106. package/dist/internal/nodes/nodesAccess.test.js.map +1 -1
  107. package/dist/internal/nodes/nodesManagement.d.ts +1 -3
  108. package/dist/internal/nodes/nodesManagement.js +12 -26
  109. package/dist/internal/nodes/nodesManagement.js.map +1 -1
  110. package/dist/internal/nodes/nodesManagement.test.js +35 -14
  111. package/dist/internal/nodes/nodesManagement.test.js.map +1 -1
  112. package/dist/internal/shares/cache.d.ts +2 -0
  113. package/dist/internal/shares/cache.js +2 -0
  114. package/dist/internal/shares/cache.js.map +1 -1
  115. package/dist/internal/shares/manager.d.ts +1 -0
  116. package/dist/internal/shares/manager.js +3 -0
  117. package/dist/internal/shares/manager.js.map +1 -1
  118. package/dist/internal/sharing/apiService.js +1 -0
  119. package/dist/internal/sharing/apiService.js.map +1 -1
  120. package/dist/internal/sharing/cryptoService.js +1 -0
  121. package/dist/internal/sharing/cryptoService.js.map +1 -1
  122. package/dist/internal/sharing/events.d.ts +23 -55
  123. package/dist/internal/sharing/events.js +46 -138
  124. package/dist/internal/sharing/events.js.map +1 -1
  125. package/dist/internal/sharing/events.test.js +77 -180
  126. package/dist/internal/sharing/events.test.js.map +1 -1
  127. package/dist/internal/sharing/index.d.ts +4 -5
  128. package/dist/internal/sharing/index.js +5 -5
  129. package/dist/internal/sharing/index.js.map +1 -1
  130. package/dist/internal/sharing/interface.d.ts +3 -0
  131. package/dist/internal/sharing/sharingManagement.d.ts +2 -3
  132. package/dist/internal/sharing/sharingManagement.js +7 -9
  133. package/dist/internal/sharing/sharingManagement.js.map +1 -1
  134. package/dist/internal/sharing/sharingManagement.test.js +9 -39
  135. package/dist/internal/sharing/sharingManagement.test.js.map +1 -1
  136. package/dist/internal/upload/apiService.d.ts +2 -3
  137. package/dist/internal/upload/apiService.js +7 -4
  138. package/dist/internal/upload/apiService.js.map +1 -1
  139. package/dist/internal/upload/index.d.ts +2 -2
  140. package/dist/internal/upload/index.js +3 -3
  141. package/dist/internal/upload/index.js.map +1 -1
  142. package/dist/internal/upload/interface.d.ts +2 -0
  143. package/dist/internal/upload/manager.d.ts +5 -5
  144. package/dist/internal/upload/manager.js +19 -50
  145. package/dist/internal/upload/manager.js.map +1 -1
  146. package/dist/internal/upload/manager.test.js +68 -44
  147. package/dist/internal/upload/manager.test.js.map +1 -1
  148. package/dist/internal/upload/streamUploader.js +1 -2
  149. package/dist/internal/upload/streamUploader.js.map +1 -1
  150. package/dist/internal/upload/streamUploader.test.js +1 -1
  151. package/dist/internal/upload/streamUploader.test.js.map +1 -1
  152. package/dist/protonDriveClient.d.ts +19 -162
  153. package/dist/protonDriveClient.js +26 -190
  154. package/dist/protonDriveClient.js.map +1 -1
  155. package/dist/protonDrivePhotosClient.js +3 -2
  156. package/dist/protonDrivePhotosClient.js.map +1 -1
  157. package/package.json +3 -3
  158. package/src/cache/index.ts +1 -0
  159. package/src/cache/memoryCache.ts +1 -1
  160. package/src/cache/nullCache.ts +38 -0
  161. package/src/config.ts +17 -2
  162. package/src/crypto/openPGPCrypto.ts +2 -0
  163. package/src/diagnostic/eventsGenerator.ts +48 -0
  164. package/src/diagnostic/httpClient.ts +80 -0
  165. package/src/diagnostic/index.ts +38 -0
  166. package/src/diagnostic/integrityVerificationStream.ts +56 -0
  167. package/src/diagnostic/interface.ts +158 -0
  168. package/src/diagnostic/sdkDiagnostic.ts +238 -0
  169. package/src/diagnostic/sdkDiagnosticFull.ts +40 -0
  170. package/src/diagnostic/telemetry.ts +71 -0
  171. package/src/diagnostic/zipGenerators.test.ts +177 -0
  172. package/src/diagnostic/zipGenerators.ts +70 -0
  173. package/src/errors.ts +4 -1
  174. package/src/interface/config.ts +28 -0
  175. package/src/interface/download.ts +2 -2
  176. package/src/interface/events.ts +66 -21
  177. package/src/interface/httpClient.ts +0 -16
  178. package/src/interface/index.ts +8 -4
  179. package/src/interface/nodes.ts +21 -12
  180. package/src/interface/sharing.ts +1 -0
  181. package/src/interface/upload.ts +6 -0
  182. package/src/internal/download/apiService.ts +11 -8
  183. package/src/internal/download/fileDownloader.ts +2 -2
  184. package/src/internal/events/apiService.ts +25 -28
  185. package/src/internal/events/coreEventManager.test.ts +101 -0
  186. package/src/internal/events/coreEventManager.ts +20 -45
  187. package/src/internal/events/eventManager.test.ts +201 -88
  188. package/src/internal/events/eventManager.ts +69 -115
  189. package/src/internal/events/index.ts +54 -84
  190. package/src/internal/events/interface.ts +70 -15
  191. package/src/internal/events/volumeEventManager.test.ts +243 -0
  192. package/src/internal/events/volumeEventManager.ts +55 -53
  193. package/src/internal/nodes/cache.ts +20 -2
  194. package/src/internal/nodes/cryptoService.ts +1 -1
  195. package/src/internal/nodes/events.test.ts +29 -335
  196. package/src/internal/nodes/events.ts +45 -253
  197. package/src/internal/nodes/index.ts +6 -8
  198. package/src/internal/nodes/interface.ts +2 -2
  199. package/src/internal/nodes/nodesAccess.test.ts +132 -91
  200. package/src/internal/nodes/nodesAccess.ts +40 -1
  201. package/src/internal/nodes/nodesManagement.test.ts +39 -15
  202. package/src/internal/nodes/nodesManagement.ts +12 -30
  203. package/src/internal/shares/cache.ts +4 -2
  204. package/src/internal/shares/manager.ts +9 -5
  205. package/src/internal/sharing/apiService.ts +1 -0
  206. package/src/internal/sharing/cache.ts +1 -1
  207. package/src/internal/sharing/cryptoService.ts +1 -0
  208. package/src/internal/sharing/events.test.ts +89 -195
  209. package/src/internal/sharing/events.ts +42 -156
  210. package/src/internal/sharing/index.ts +6 -9
  211. package/src/internal/sharing/interface.ts +6 -2
  212. package/src/internal/sharing/sharingManagement.test.ts +10 -40
  213. package/src/internal/sharing/sharingManagement.ts +7 -11
  214. package/src/internal/upload/apiService.ts +5 -6
  215. package/src/internal/upload/index.ts +5 -5
  216. package/src/internal/upload/interface.ts +2 -0
  217. package/src/internal/upload/manager.test.ts +75 -45
  218. package/src/internal/upload/manager.ts +24 -54
  219. package/src/internal/upload/streamUploader.test.ts +0 -1
  220. package/src/internal/upload/streamUploader.ts +0 -2
  221. package/src/protonDriveClient.ts +75 -244
  222. package/src/protonDrivePhotosClient.ts +4 -3
  223. package/dist/internal/events/cache.d.ts +0 -28
  224. package/dist/internal/events/cache.js +0 -67
  225. package/dist/internal/events/cache.js.map +0 -1
  226. package/dist/internal/events/cache.test.js +0 -43
  227. package/dist/internal/events/cache.test.js.map +0 -1
  228. package/dist/internal/nodes/index.test.js +0 -114
  229. package/dist/internal/nodes/index.test.js.map +0 -1
  230. package/src/internal/events/cache.test.ts +0 -47
  231. package/src/internal/events/cache.ts +0 -80
  232. package/src/internal/nodes/index.test.ts +0 -137
  233. /package/dist/{internal/events/cache.test.d.ts → diagnostic/zipGenerators.test.d.ts} +0 -0
  234. /package/dist/internal/{nodes/index.test.d.ts → events/coreEventManager.test.d.ts} +0 -0
@@ -0,0 +1,56 @@
1
+ import { sha1 } from "@noble/hashes/legacy";
2
+ import { bytesToHex } from '@noble/hashes/utils';
3
+
4
+ /**
5
+ * A WritableStream that computes SHA1 hash on the fly.
6
+ * The computed SHA1 hash is available after the stream is closed.
7
+ */
8
+ export class IntegrityVerificationStream extends WritableStream<Uint8Array> {
9
+ private sha1Hash = sha1.create();
10
+ private _computedSha1: string | undefined = undefined;
11
+ private _computedSizeInBytes: number = 0;
12
+ private _isClosed = false;
13
+
14
+ constructor() {
15
+ super({
16
+ start: () => {},
17
+ write: (chunk: Uint8Array) => {
18
+ if (this._isClosed) {
19
+ throw new Error('Cannot write to a closed stream');
20
+ }
21
+ this.sha1Hash.update(chunk);
22
+ this._computedSizeInBytes += chunk.length;
23
+ },
24
+ close: () => {
25
+ if (!this._isClosed) {
26
+ this._computedSha1 = bytesToHex(this.sha1Hash.digest());
27
+ this._isClosed = true;
28
+ }
29
+ },
30
+ abort: () => {
31
+ this._isClosed = true;
32
+ this._computedSha1 = undefined;
33
+ }
34
+ });
35
+ }
36
+
37
+ /**
38
+ * Get the computed SHA1 hash. Only available after the stream is closed.
39
+ * @returns The SHA1 hash as a hex string, or null if not yet computed or stream was aborted
40
+ */
41
+ get computedSha1(): string | undefined {
42
+ return this._computedSha1;
43
+ }
44
+
45
+ /**
46
+ * Get the computed size in bytes. Only available after the stream is closed.
47
+ * @returns The size in bytes, or 0 if not yet computed or stream was aborted
48
+ */
49
+ get computedSizeInBytes(): number | undefined {
50
+ if (!this._isClosed) {
51
+ return undefined;
52
+ }
53
+ return this._computedSizeInBytes;
54
+ }
55
+
56
+ }
@@ -0,0 +1,158 @@
1
+ import { DegradedNode, MaybeNode, MetricEvent } from "../interface";
2
+ import { LogRecord } from "../telemetry";
3
+
4
+ export interface Diagnostic {
5
+ verifyMyFiles(options?: DiagnosticOptions): AsyncGenerator<DiagnosticResult>;
6
+ verifyNodeTree(node: MaybeNode, options?: DiagnosticOptions): AsyncGenerator<DiagnosticResult>;
7
+ }
8
+
9
+ export type DiagnosticOptions = {
10
+ verifyContent?: boolean,
11
+ verifyThumbnails?: boolean,
12
+ }
13
+
14
+ export type DiagnosticResult =
15
+ | FatalErrorResult
16
+ | SdkErrorResult
17
+ | HttpErrorResult
18
+ | DegradedNodeResult
19
+ | UnverifiedAuthorResult
20
+ | ExtendedAttributesErrorResult
21
+ | ExtendedAttributesMissingFieldResult
22
+ | ContentFileMissingRevisionResult
23
+ | ContentIntegrityErrorResult
24
+ | ContentDownloadErrorResult
25
+ | ThumbnailsErrorResult
26
+ | LogErrorResult
27
+ | LogWarningResult
28
+ | MetricResult;
29
+
30
+ // Event representing that fatal error occurred during the diagnostic.
31
+ // This error prevents the diagnostic to finish.
32
+ export type FatalErrorResult = {
33
+ type: 'fatal_error',
34
+ message: string,
35
+ error?: unknown,
36
+ }
37
+
38
+ // Event representing that SDK call failed.
39
+ // It can be any throwable error from any SDK call. Normally no error should be thrown.
40
+ export type SdkErrorResult = {
41
+ type: 'sdk_error',
42
+ call: string,
43
+ error?: unknown,
44
+ }
45
+
46
+ // Event representing that HTTP call failed.
47
+ // It can be any call from the SDK, including validation error. Normally no error should be present.
48
+ export type HttpErrorResult = {
49
+ type: 'http_error',
50
+ request: {
51
+ url: string,
52
+ method: string,
53
+ json: unknown,
54
+ },
55
+ // Error if the whole call failed (`fetch` failed).
56
+ error?: unknown,
57
+ // Response if the response is not 2xx or 3xx.
58
+ response?: {
59
+ status: number,
60
+ statusText: string,
61
+ // Either json object or error if the response is not JSON.
62
+ json?: object,
63
+ jsonError?: unknown,
64
+ },
65
+ }
66
+
67
+ // Event representing that node has some decryption or other (e.g., invalid name) issues.
68
+ export type DegradedNodeResult = {
69
+ type: 'degraded_node',
70
+ nodeUid: string,
71
+ node: DegradedNode,
72
+ }
73
+
74
+ // Event representing that signature verification failing.
75
+ export type UnverifiedAuthorResult = {
76
+ type: 'unverified_author',
77
+ nodeUid: string,
78
+ revisionUid?: string,
79
+ authorType: string,
80
+ claimedAuthor?: string,
81
+ error: string,
82
+ node: MaybeNode,
83
+ }
84
+
85
+ // Event representing that field from the extended attributes is not valid format.
86
+ // Currently only `sha1` verification is supported.
87
+ export type ExtendedAttributesErrorResult = {
88
+ type: 'extended_attributes_error',
89
+ nodeUid: string,
90
+ revisionUid?: string,
91
+ field: 'sha1',
92
+ value: string,
93
+ }
94
+
95
+ // Event representing that field from the extended attributes is missing.
96
+ // Currently only `sha1` verification is supported.
97
+ export type ExtendedAttributesMissingFieldResult = {
98
+ type: 'extended_attributes_missing_field',
99
+ nodeUid: string,
100
+ revisionUid?: string,
101
+ missingField: 'sha1',
102
+ }
103
+
104
+ // Event representing that file is missing the active revision.
105
+ export type ContentFileMissingRevisionResult = {
106
+ type: 'content_file_missing_revision',
107
+ nodeUid: string,
108
+ revisionUid?: string,
109
+ }
110
+
111
+ // Event representing that file content is not valid - either sha1 or size is not correct.
112
+ export type ContentIntegrityErrorResult = {
113
+ type: 'content_integrity_error',
114
+ nodeUid: string,
115
+ revisionUid?: string,
116
+ claimedSha1?: string,
117
+ computedSha1?: string,
118
+ claimedSizeInBytes?: number,
119
+ computedSizeInBytes?: number,
120
+ }
121
+
122
+ // Event representing that downloading the file content failed.
123
+ // This can be connection issue or server error. If its integrity issue,
124
+ // it should be reported as `ContentIntegrityErrorResult`.
125
+ export type ContentDownloadErrorResult = {
126
+ type: 'content_download_error',
127
+ nodeUid: string,
128
+ revisionUid?: string,
129
+ error: unknown,
130
+ }
131
+
132
+ // Event representing that getting the thumbnails failed.
133
+ // This can be connection issue or server error.
134
+ export type ThumbnailsErrorResult = {
135
+ type: 'thumbnails_error',
136
+ nodeUid: string,
137
+ revisionUid?: string,
138
+ message?: string,
139
+ error?: unknown,
140
+ }
141
+
142
+ // Event representing errors logged during the diagnostic.
143
+ export type LogErrorResult = {
144
+ type: 'log_error',
145
+ log: LogRecord,
146
+ }
147
+
148
+ // Event representing warnings logged during the diagnostic.
149
+ export type LogWarningResult = {
150
+ type: 'log_warning',
151
+ log: LogRecord,
152
+ }
153
+
154
+ // Event representing metrics logged during the diagnostic.
155
+ export type MetricResult = {
156
+ type: 'metric',
157
+ event: MetricEvent,
158
+ }
@@ -0,0 +1,238 @@
1
+ import { Author, FileDownloader, MaybeNode, NodeType, Revision, ThumbnailType } from "../interface";
2
+ import { ProtonDriveClient } from "../protonDriveClient";
3
+ import { Diagnostic, DiagnosticOptions, DiagnosticResult } from "./interface";
4
+ import { IntegrityVerificationStream } from "./integrityVerificationStream";
5
+
6
+ /**
7
+ * Diagnostic tool that uses SDK to traverse the node tree and verify
8
+ * the integrity of the node tree.
9
+ *
10
+ * It produces only events that can be read by direct SDK invocation.
11
+ * To get the full diagnostic, use {@link FullSDKDiagnostic}.
12
+ */
13
+ export class SDKDiagnostic implements Diagnostic {
14
+ constructor(private protonDriveClient: ProtonDriveClient) {
15
+ this.protonDriveClient = protonDriveClient;
16
+ }
17
+
18
+ async* verifyMyFiles(options?: DiagnosticOptions): AsyncGenerator<DiagnosticResult> {
19
+ let myFilesRootFolder: MaybeNode;
20
+
21
+ try {
22
+ myFilesRootFolder = await this.protonDriveClient.getMyFilesRootFolder();
23
+ } catch (error: unknown) {
24
+ yield {
25
+ type: 'fatal_error',
26
+ message: `Error getting my files root folder`,
27
+ error,
28
+ };
29
+ return;
30
+ }
31
+
32
+ yield* this.verifyNodeTree(myFilesRootFolder, options);
33
+ }
34
+
35
+ async* verifyNodeTree(node: MaybeNode, options?: DiagnosticOptions): AsyncGenerator<DiagnosticResult> {
36
+ const isFolder = getNodeType(node) === NodeType.Folder;
37
+
38
+ yield* this.verifyNode(node, options);
39
+
40
+ if (isFolder) {
41
+ yield* this.verifyNodeChildren(node, options);
42
+ }
43
+ }
44
+
45
+ private async* verifyNode(node: MaybeNode, options?: DiagnosticOptions): AsyncGenerator<DiagnosticResult> {
46
+ const nodeUid = node.ok ? node.value.uid : node.error.uid;
47
+
48
+ if (!node.ok) {
49
+ yield {
50
+ type: 'degraded_node',
51
+ nodeUid,
52
+ node: node.error,
53
+ };
54
+ }
55
+
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, { ...nodeInfo, authorType: 'key' });
63
+ yield* this.verifyAuthor(node.ok ? node.value.nameAuthor : node.error.nameAuthor, { ...nodeInfo, authorType: 'name' });
64
+ if (activeRevision) {
65
+ yield* this.verifyAuthor(activeRevision.contentAuthor, { ...nodeInfo, authorType: 'content' });
66
+ }
67
+
68
+ yield* this.verifyFileExtendedAttributes(node);
69
+
70
+ if (options?.verifyContent) {
71
+ yield* this.verifyContent(node);
72
+ }
73
+ if (options?.verifyThumbnails) {
74
+ yield* this.verifyThumbnails(node);
75
+ }
76
+ }
77
+
78
+ private async* verifyAuthor(author: Author, info: { nodeUid: string, authorType: string, revisionUid?: string, node: MaybeNode }): AsyncGenerator<DiagnosticResult> {
79
+ if (!author.ok) {
80
+ yield {
81
+ type: 'unverified_author',
82
+ claimedAuthor: author.error.claimedAuthor,
83
+ error: author.error.error,
84
+ ...info,
85
+ };
86
+ }
87
+ }
88
+
89
+ private async* verifyFileExtendedAttributes(node: MaybeNode): AsyncGenerator<DiagnosticResult> {
90
+ const activeRevision = getActiveRevision(node);
91
+
92
+ const expectedAttributes = getNodeType(node) === NodeType.File;
93
+
94
+ const claimedSha1 = activeRevision?.claimedDigests?.sha1;
95
+ if (claimedSha1 && !/^[0-9a-f]{40}$/i.test(claimedSha1)) {
96
+ yield {
97
+ type: 'extended_attributes_error',
98
+ ...getNodeUids(node),
99
+ field: 'sha1',
100
+ value: claimedSha1,
101
+ }
102
+ }
103
+
104
+ if (expectedAttributes && !claimedSha1) {
105
+ yield {
106
+ type: 'extended_attributes_missing_field',
107
+ ...getNodeUids(node),
108
+ missingField: 'sha1',
109
+ }
110
+ }
111
+ }
112
+
113
+ private async* verifyContent(node: MaybeNode): AsyncGenerator<DiagnosticResult> {
114
+ if (getNodeType(node) !== NodeType.File) {
115
+ return;
116
+ }
117
+ const activeRevision = getActiveRevision(node);
118
+ if (!activeRevision) {
119
+ yield {
120
+ type: 'content_file_missing_revision',
121
+ nodeUid: node.ok ? node.value.uid : node.error.uid,
122
+ }
123
+ return;
124
+ }
125
+
126
+ let downloader: FileDownloader;
127
+ try {
128
+ downloader = await this.protonDriveClient.getFileRevisionDownloader(activeRevision.uid);
129
+ } catch (error: unknown) {
130
+ yield {
131
+ type: 'sdk_error',
132
+ call: `getFileRevisionDownloader(${activeRevision.uid})`,
133
+ error,
134
+ };
135
+ return;
136
+ }
137
+
138
+ const claimedSha1 = activeRevision.claimedDigests?.sha1;
139
+ const claimedSizeInBytes = downloader.getClaimedSizeInBytes();
140
+
141
+ const integrityVerificationStream = new IntegrityVerificationStream();
142
+ const controller = downloader.writeToStream(integrityVerificationStream);
143
+
144
+ try {
145
+ await controller.completion();
146
+
147
+ const computedSha1 = integrityVerificationStream.computedSha1;
148
+ const computedSizeInBytes = integrityVerificationStream.computedSizeInBytes;
149
+ if (claimedSha1 !== computedSha1 || claimedSizeInBytes !== computedSizeInBytes) {
150
+ yield {
151
+ type: 'content_integrity_error',
152
+ ...getNodeUids(node),
153
+ claimedSha1,
154
+ computedSha1,
155
+ claimedSizeInBytes,
156
+ computedSizeInBytes,
157
+ };
158
+ }
159
+ } catch (error: unknown) {
160
+ yield {
161
+ type: 'content_download_error',
162
+ ...getNodeUids(node),
163
+ error,
164
+ };
165
+ }
166
+ }
167
+
168
+ private async* verifyThumbnails(node: MaybeNode): AsyncGenerator<DiagnosticResult> {
169
+ if (getNodeType(node) !== NodeType.File) {
170
+ return;
171
+ }
172
+
173
+ const nodeUid = node.ok ? node.value.uid : node.error.uid;
174
+
175
+ try {
176
+ const result = await Array.fromAsync(this.protonDriveClient.iterateThumbnails([nodeUid], ThumbnailType.Type1));
177
+
178
+ if (result.length === 0) {
179
+ yield {
180
+ type: 'sdk_error',
181
+ call: `iterateThumbnails(${nodeUid})`,
182
+ error: new Error('No thumbnails found'),
183
+ }
184
+ }
185
+ // TODO: We should have better way to check if the thumbnail is not expected.
186
+ if (!result[0].ok && result[0].error !== 'Node has no thumbnail') {
187
+ yield {
188
+ type: 'thumbnails_error',
189
+ nodeUid,
190
+ error: result[0].error,
191
+ }
192
+ }
193
+ } catch (error: unknown) {
194
+ yield {
195
+ type: 'sdk_error',
196
+ call: `iterateThumbnails(${nodeUid})`,
197
+ error,
198
+ }
199
+ }
200
+ }
201
+
202
+ private async* verifyNodeChildren(node: MaybeNode, options?: DiagnosticOptions): AsyncGenerator<DiagnosticResult> {
203
+ const nodeUid = node.ok ? node.value.uid : node.error.uid;
204
+ try {
205
+ for await (const child of this.protonDriveClient.iterateFolderChildren(node)) {
206
+ yield* this.verifyNodeTree(child, options);
207
+ }
208
+ } catch (error: unknown) {
209
+ yield {
210
+ type: 'sdk_error',
211
+ call: `iterateFolderChildren(${nodeUid})`,
212
+ error,
213
+ };
214
+ }
215
+ }
216
+ }
217
+
218
+ function getNodeUids(node: MaybeNode): { nodeUid: string, revisionUid?: string } {
219
+ const activeRevision = getActiveRevision(node);
220
+ return {
221
+ nodeUid: node.ok ? node.value.uid : node.error.uid,
222
+ revisionUid: activeRevision?.uid,
223
+ };
224
+ }
225
+
226
+ function getNodeType(node: MaybeNode): NodeType {
227
+ return node.ok ? node.value.type : node.error.type;
228
+ }
229
+
230
+ function getActiveRevision(node: MaybeNode): Revision | undefined {
231
+ if (node.ok) {
232
+ return node.value.activeRevision;
233
+ }
234
+ if (node.error.activeRevision?.ok) {
235
+ return node.error.activeRevision.value;
236
+ }
237
+ return undefined;
238
+ }
@@ -0,0 +1,40 @@
1
+ import { MaybeNode } from "../interface";
2
+ import { DiagnosticHTTPClient } from "./httpClient";
3
+ import { Diagnostic, DiagnosticOptions, DiagnosticResult } from "./interface";
4
+ import { DiagnosticTelemetry } from "./telemetry";
5
+ import { zipGenerators } from "./zipGenerators";
6
+
7
+ /**
8
+ * Diagnostic tool that produces full diagnostic, including logs and metrics
9
+ * by reading the events from the telemetry and HTTP client.
10
+ */
11
+ export class FullSDKDiagnostic implements Diagnostic {
12
+ constructor(private diagnostic: Diagnostic, private telemetry: DiagnosticTelemetry, private httpClient: DiagnosticHTTPClient) {
13
+ this.diagnostic = diagnostic;
14
+ this.telemetry = telemetry;
15
+ this.httpClient = httpClient;
16
+ }
17
+
18
+ async* verifyMyFiles(options?: DiagnosticOptions): AsyncGenerator<DiagnosticResult> {
19
+ yield* this.yieldEvents(this.diagnostic.verifyMyFiles(options));
20
+ }
21
+
22
+ async* verifyNodeTree(node: MaybeNode, options?: DiagnosticOptions): AsyncGenerator<DiagnosticResult> {
23
+ yield* this.yieldEvents(this.diagnostic.verifyNodeTree(node, options));
24
+ }
25
+
26
+ private async* yieldEvents(generator: AsyncGenerator<DiagnosticResult>): AsyncGenerator<DiagnosticResult> {
27
+ yield* zipGenerators(
28
+ generator,
29
+ this.internalGenerator(),
30
+ { stopOnFirstDone: true },
31
+ );
32
+ }
33
+
34
+ private async* internalGenerator(): AsyncGenerator<DiagnosticResult> {
35
+ yield* zipGenerators(
36
+ this.telemetry.iterateEvents(),
37
+ this.httpClient.iterateEvents(),
38
+ );
39
+ }
40
+ }
@@ -0,0 +1,71 @@
1
+ import { MetricEvent } from '../interface';
2
+ import { LogRecord, LogLevel } from '../telemetry';
3
+ import { EventsGenerator } from './eventsGenerator';
4
+
5
+ /**
6
+ * Special telemetry that is compatible with the SDK.
7
+ *
8
+ * It is a probe into SDK to observe whats going on and report any suspicious
9
+ * behavior.
10
+ *
11
+ * It should be used only for diagnostic purposes.
12
+ */
13
+ export class DiagnosticTelemetry extends EventsGenerator {
14
+ getLogger(name: string): Logger {
15
+ return new Logger(name, (log) => {
16
+ this.enqueueEvent({
17
+ type: log.level === LogLevel.ERROR ? 'log_error' : 'log_warning',
18
+ log,
19
+ });
20
+ });
21
+ }
22
+
23
+ logEvent(event: MetricEvent): void {
24
+ if (event.eventName === 'download' && !event.error) {
25
+ return;
26
+ }
27
+ if (event.eventName === 'volumeEventsSubscriptionsChanged') {
28
+ return;
29
+ }
30
+
31
+ this.enqueueEvent({
32
+ type: 'metric',
33
+ event,
34
+ });
35
+ }
36
+ }
37
+
38
+ class Logger {
39
+ constructor(private name: string, private callback?: (log: LogRecord) => void) {
40
+ this.name = name;
41
+ this.callback = callback;
42
+ }
43
+
44
+ // Debug or info logs are excluded from the diagnostic.
45
+ // These logs should not include any suspicious behavior.
46
+
47
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
48
+ debug(message: string) {}
49
+
50
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
51
+ info(message: string) {}
52
+
53
+ warn(message: string) {
54
+ this.callback?.({
55
+ time: new Date(),
56
+ level: LogLevel.WARNING,
57
+ loggerName: this.name,
58
+ message,
59
+ });
60
+ }
61
+
62
+ error(message: string, error?: unknown) {
63
+ this.callback?.({
64
+ time: new Date(),
65
+ level: LogLevel.ERROR,
66
+ loggerName: this.name,
67
+ message,
68
+ error,
69
+ });
70
+ }
71
+ }