@protontech/drive-sdk 0.3.0 → 0.3.2

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 (178) hide show
  1. package/dist/crypto/driveCrypto.d.ts +1 -1
  2. package/dist/crypto/driveCrypto.js.map +1 -1
  3. package/dist/crypto/interface.d.ts +6 -1
  4. package/dist/crypto/openPGPCrypto.d.ts +1 -1
  5. package/dist/crypto/openPGPCrypto.js +4 -1
  6. package/dist/crypto/openPGPCrypto.js.map +1 -1
  7. package/dist/diagnostic/httpClient.d.ts +3 -3
  8. package/dist/interface/httpClient.d.ts +5 -5
  9. package/dist/interface/index.d.ts +15 -5
  10. package/dist/internal/apiService/apiService.js +1 -1
  11. package/dist/internal/apiService/apiService.js.map +1 -1
  12. package/dist/internal/apiService/errorCodes.d.ts +1 -0
  13. package/dist/internal/apiService/errorCodes.js.map +1 -1
  14. package/dist/internal/apiService/errors.d.ts +4 -3
  15. package/dist/internal/apiService/errors.js +7 -4
  16. package/dist/internal/apiService/errors.js.map +1 -1
  17. package/dist/internal/apiService/errors.test.js +2 -1
  18. package/dist/internal/apiService/errors.test.js.map +1 -1
  19. package/dist/internal/download/cryptoService.js +2 -2
  20. package/dist/internal/download/cryptoService.js.map +1 -1
  21. package/dist/internal/download/fileDownloader.js +2 -2
  22. package/dist/internal/download/fileDownloader.js.map +1 -1
  23. package/dist/internal/download/fileDownloader.test.js +3 -1
  24. package/dist/internal/download/fileDownloader.test.js.map +1 -1
  25. package/dist/internal/events/index.d.ts +1 -1
  26. package/dist/internal/nodes/cache.js +3 -1
  27. package/dist/internal/nodes/cache.js.map +1 -1
  28. package/dist/internal/nodes/cryptoCache.js +6 -7
  29. package/dist/internal/nodes/cryptoCache.js.map +1 -1
  30. package/dist/internal/nodes/cryptoCache.test.js +4 -7
  31. package/dist/internal/nodes/cryptoCache.test.js.map +1 -1
  32. package/dist/internal/nodes/cryptoReporter.d.ts +20 -0
  33. package/dist/internal/nodes/cryptoReporter.js +96 -0
  34. package/dist/internal/nodes/cryptoReporter.js.map +1 -0
  35. package/dist/internal/nodes/cryptoService.d.ts +17 -12
  36. package/dist/internal/nodes/cryptoService.js +17 -97
  37. package/dist/internal/nodes/cryptoService.js.map +1 -1
  38. package/dist/internal/nodes/cryptoService.test.js +3 -1
  39. package/dist/internal/nodes/cryptoService.test.js.map +1 -1
  40. package/dist/internal/nodes/index.js +3 -1
  41. package/dist/internal/nodes/index.js.map +1 -1
  42. package/dist/internal/nodes/interface.d.ts +1 -1
  43. package/dist/internal/nodes/nodesAccess.d.ts +2 -2
  44. package/dist/internal/nodes/nodesAccess.js +52 -54
  45. package/dist/internal/nodes/nodesAccess.js.map +1 -1
  46. package/dist/internal/shares/cryptoCache.d.ts +4 -3
  47. package/dist/internal/shares/cryptoCache.js +23 -6
  48. package/dist/internal/shares/cryptoCache.js.map +1 -1
  49. package/dist/internal/shares/cryptoCache.test.js +3 -2
  50. package/dist/internal/shares/cryptoCache.test.js.map +1 -1
  51. package/dist/internal/shares/index.js +1 -1
  52. package/dist/internal/shares/index.js.map +1 -1
  53. package/dist/internal/sharing/cache.d.ts +3 -0
  54. package/dist/internal/sharing/cache.js +17 -2
  55. package/dist/internal/sharing/cache.js.map +1 -1
  56. package/dist/internal/sharing/cryptoService.js +8 -6
  57. package/dist/internal/sharing/cryptoService.js.map +1 -1
  58. package/dist/internal/sharing/cryptoService.test.js +13 -0
  59. package/dist/internal/sharing/cryptoService.test.js.map +1 -1
  60. package/dist/internal/sharing/index.js +1 -1
  61. package/dist/internal/sharing/index.js.map +1 -1
  62. package/dist/internal/sharing/interface.d.ts +1 -1
  63. package/dist/internal/sharing/interface.js +1 -1
  64. package/dist/internal/sharing/sharingAccess.js +6 -0
  65. package/dist/internal/sharing/sharingAccess.js.map +1 -1
  66. package/dist/internal/sharing/sharingAccess.test.js +242 -33
  67. package/dist/internal/sharing/sharingAccess.test.js.map +1 -1
  68. package/dist/internal/sharing/sharingManagement.d.ts +3 -1
  69. package/dist/internal/sharing/sharingManagement.js +10 -1
  70. package/dist/internal/sharing/sharingManagement.js.map +1 -1
  71. package/dist/internal/sharing/sharingManagement.test.js +32 -1
  72. package/dist/internal/sharing/sharingManagement.test.js.map +1 -1
  73. package/dist/internal/sharingPublic/apiService.d.ts +19 -0
  74. package/dist/internal/sharingPublic/apiService.js +141 -0
  75. package/dist/internal/sharingPublic/apiService.js.map +1 -0
  76. package/dist/internal/sharingPublic/cryptoCache.d.ts +19 -0
  77. package/dist/internal/sharingPublic/cryptoCache.js +72 -0
  78. package/dist/internal/sharingPublic/cryptoCache.js.map +1 -0
  79. package/dist/internal/sharingPublic/cryptoService.d.ts +9 -0
  80. package/dist/internal/sharingPublic/cryptoService.js +57 -0
  81. package/dist/internal/sharingPublic/cryptoService.js.map +1 -0
  82. package/dist/internal/sharingPublic/index.d.ts +15 -0
  83. package/dist/internal/sharingPublic/index.js +27 -0
  84. package/dist/internal/sharingPublic/index.js.map +1 -0
  85. package/dist/internal/sharingPublic/interface.d.ts +6 -0
  86. package/dist/internal/sharingPublic/interface.js +3 -0
  87. package/dist/internal/sharingPublic/interface.js.map +1 -0
  88. package/dist/internal/sharingPublic/manager.d.ts +19 -0
  89. package/dist/internal/sharingPublic/manager.js +81 -0
  90. package/dist/internal/sharingPublic/manager.js.map +1 -0
  91. package/dist/internal/sharingPublic/session/apiService.d.ts +28 -0
  92. package/dist/internal/sharingPublic/session/apiService.js +55 -0
  93. package/dist/internal/sharingPublic/session/apiService.js.map +1 -0
  94. package/dist/internal/sharingPublic/session/httpClient.d.ts +16 -0
  95. package/dist/internal/sharingPublic/session/httpClient.js +41 -0
  96. package/dist/internal/sharingPublic/session/httpClient.js.map +1 -0
  97. package/dist/internal/sharingPublic/session/index.d.ts +1 -0
  98. package/dist/internal/sharingPublic/session/index.js +6 -0
  99. package/dist/internal/sharingPublic/session/index.js.map +1 -0
  100. package/dist/internal/sharingPublic/session/interface.d.ts +18 -0
  101. package/dist/internal/sharingPublic/session/interface.js +3 -0
  102. package/dist/internal/sharingPublic/session/interface.js.map +1 -0
  103. package/dist/internal/sharingPublic/session/manager.d.ts +49 -0
  104. package/dist/internal/sharingPublic/session/manager.js +75 -0
  105. package/dist/internal/sharingPublic/session/manager.js.map +1 -0
  106. package/dist/internal/sharingPublic/session/session.d.ts +34 -0
  107. package/dist/internal/sharingPublic/session/session.js +67 -0
  108. package/dist/internal/sharingPublic/session/session.js.map +1 -0
  109. package/dist/internal/sharingPublic/session/url.d.ts +12 -0
  110. package/dist/internal/sharingPublic/session/url.js +23 -0
  111. package/dist/internal/sharingPublic/session/url.js.map +1 -0
  112. package/dist/internal/sharingPublic/session/url.test.d.ts +1 -0
  113. package/dist/internal/sharingPublic/session/url.test.js +59 -0
  114. package/dist/internal/sharingPublic/session/url.test.js.map +1 -0
  115. package/dist/internal/upload/streamUploader.js +1 -1
  116. package/dist/internal/upload/streamUploader.js.map +1 -1
  117. package/dist/internal/upload/streamUploader.test.js +3 -1
  118. package/dist/internal/upload/streamUploader.test.js.map +1 -1
  119. package/dist/protonDriveClient.d.ts +18 -3
  120. package/dist/protonDriveClient.js +31 -8
  121. package/dist/protonDriveClient.js.map +1 -1
  122. package/dist/protonDrivePublicLinkClient.d.ts +57 -0
  123. package/dist/protonDrivePublicLinkClient.js +73 -0
  124. package/dist/protonDrivePublicLinkClient.js.map +1 -0
  125. package/package.json +1 -1
  126. package/src/crypto/driveCrypto.ts +1 -1
  127. package/src/crypto/interface.ts +12 -1
  128. package/src/crypto/openPGPCrypto.ts +5 -2
  129. package/src/diagnostic/httpClient.ts +4 -4
  130. package/src/interface/httpClient.ts +5 -5
  131. package/src/interface/index.ts +18 -6
  132. package/src/internal/apiService/apiService.ts +1 -1
  133. package/src/internal/apiService/errorCodes.ts +1 -0
  134. package/src/internal/apiService/errors.test.ts +2 -1
  135. package/src/internal/apiService/errors.ts +15 -4
  136. package/src/internal/download/cryptoService.ts +2 -2
  137. package/src/internal/download/fileDownloader.test.ts +3 -1
  138. package/src/internal/download/fileDownloader.ts +2 -2
  139. package/src/internal/events/index.ts +1 -1
  140. package/src/internal/nodes/cache.ts +3 -1
  141. package/src/internal/nodes/cryptoCache.test.ts +4 -7
  142. package/src/internal/nodes/cryptoCache.ts +6 -7
  143. package/src/internal/nodes/cryptoReporter.ts +145 -0
  144. package/src/internal/nodes/cryptoService.test.ts +3 -1
  145. package/src/internal/nodes/cryptoService.ts +44 -137
  146. package/src/internal/nodes/index.ts +3 -1
  147. package/src/internal/nodes/interface.ts +3 -1
  148. package/src/internal/nodes/nodesAccess.ts +59 -61
  149. package/src/internal/shares/cryptoCache.test.ts +3 -2
  150. package/src/internal/shares/cryptoCache.ts +26 -7
  151. package/src/internal/shares/index.ts +1 -1
  152. package/src/internal/sharing/cache.ts +19 -2
  153. package/src/internal/sharing/cryptoService.test.ts +22 -1
  154. package/src/internal/sharing/cryptoService.ts +8 -6
  155. package/src/internal/sharing/index.ts +1 -0
  156. package/src/internal/sharing/interface.ts +1 -1
  157. package/src/internal/sharing/sharingAccess.test.ts +282 -34
  158. package/src/internal/sharing/sharingAccess.ts +6 -0
  159. package/src/internal/sharing/sharingManagement.test.ts +33 -0
  160. package/src/internal/sharing/sharingManagement.ts +9 -0
  161. package/src/internal/sharingPublic/apiService.ts +173 -0
  162. package/src/internal/sharingPublic/cryptoCache.ts +79 -0
  163. package/src/internal/sharingPublic/cryptoService.ts +98 -0
  164. package/src/internal/sharingPublic/index.ts +41 -0
  165. package/src/internal/sharingPublic/interface.ts +14 -0
  166. package/src/internal/sharingPublic/manager.ts +86 -0
  167. package/src/internal/sharingPublic/session/apiService.ts +74 -0
  168. package/src/internal/sharingPublic/session/httpClient.ts +48 -0
  169. package/src/internal/sharingPublic/session/index.ts +1 -0
  170. package/src/internal/sharingPublic/session/interface.ts +20 -0
  171. package/src/internal/sharingPublic/session/manager.ts +97 -0
  172. package/src/internal/sharingPublic/session/session.ts +78 -0
  173. package/src/internal/sharingPublic/session/url.test.ts +72 -0
  174. package/src/internal/sharingPublic/session/url.ts +23 -0
  175. package/src/internal/upload/streamUploader.test.ts +3 -1
  176. package/src/internal/upload/streamUploader.ts +1 -1
  177. package/src/protonDriveClient.ts +48 -11
  178. package/src/protonDrivePublicLinkClient.ts +135 -0
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ProtonDrivePublicLinkClient = void 0;
4
+ const config_1 = require("./config");
5
+ const crypto_1 = require("./crypto");
6
+ const telemetry_1 = require("./telemetry");
7
+ const transformers_1 = require("./transformers");
8
+ const apiService_1 = require("./internal/apiService");
9
+ const sdkEvents_1 = require("./internal/sdkEvents");
10
+ const sharingPublic_1 = require("./internal/sharingPublic");
11
+ /**
12
+ * ProtonDrivePublicLinkClient is the interface for the public link client.
13
+ *
14
+ * The client provides high-level operations for managing nodes, and
15
+ * downloading/uploading files.
16
+ *
17
+ * Do not use this client direclty, use ProtonDriveClient instead.
18
+ * The main client handles public link sessions and provides access to
19
+ * public links.
20
+ *
21
+ * See `experimental.getPublicLinkInfo` and `experimental.authPublicLink`
22
+ * for more information.
23
+ */
24
+ class ProtonDrivePublicLinkClient {
25
+ logger;
26
+ sdkEvents;
27
+ sharingPublic;
28
+ experimental;
29
+ constructor({ httpClient, cryptoCache, account, openPGPCryptoModule, srpModule, config, telemetry, token, password, }) {
30
+ if (!telemetry) {
31
+ telemetry = new telemetry_1.Telemetry();
32
+ }
33
+ this.logger = telemetry.getLogger('interface');
34
+ const fullConfig = (0, config_1.getConfig)(config);
35
+ this.sdkEvents = new sdkEvents_1.SDKEvents(telemetry);
36
+ const apiService = new apiService_1.DriveAPIService(telemetry, this.sdkEvents, httpClient, fullConfig.baseUrl, fullConfig.language);
37
+ const driveCrypto = new crypto_1.DriveCrypto(openPGPCryptoModule, srpModule);
38
+ this.sharingPublic = (0, sharingPublic_1.initSharingPublicModule)(telemetry, apiService, cryptoCache, driveCrypto, account, token, password);
39
+ this.experimental = {
40
+ getNodeUrl: async (nodeUid) => {
41
+ this.logger.debug(`Getting node URL for ${(0, transformers_1.getUid)(nodeUid)}`);
42
+ // TODO: public node has different URL
43
+ return '';
44
+ },
45
+ getDocsKey: async (nodeUid) => {
46
+ this.logger.debug(`Getting docs keys for ${(0, transformers_1.getUid)(nodeUid)}`);
47
+ const keys = await this.sharingPublic.getNodeKeys((0, transformers_1.getUid)(nodeUid));
48
+ if (!keys.contentKeyPacketSessionKey) {
49
+ throw new Error('Node does not have a content key packet session key');
50
+ }
51
+ return keys.contentKeyPacketSessionKey;
52
+ },
53
+ };
54
+ }
55
+ /**
56
+ * @returns The root folder to the public link.
57
+ */
58
+ async getRootNode() {
59
+ this.logger.info(`Getting root node`);
60
+ return (0, transformers_1.convertInternalNodePromise)(this.sharingPublic.getRootNode());
61
+ }
62
+ /**
63
+ * Iterates the children of the given parent node.
64
+ *
65
+ * See `ProtonDriveClient.iterateFolderChildren` for more information.
66
+ */
67
+ async *iterateFolderChildren(parentUid, signal) {
68
+ this.logger.info(`Iterating children of ${(0, transformers_1.getUid)(parentUid)}`);
69
+ yield* (0, transformers_1.convertInternalNodeIterator)(this.sharingPublic.iterateFolderChildren((0, transformers_1.getUid)(parentUid), signal));
70
+ }
71
+ }
72
+ exports.ProtonDrivePublicLinkClient = ProtonDrivePublicLinkClient;
73
+ //# sourceMappingURL=protonDrivePublicLinkClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protonDrivePublicLinkClient.js","sourceRoot":"","sources":["../src/protonDrivePublicLinkClient.ts"],"names":[],"mappings":";;;AAAA,qCAAqC;AACrC,qCAA6E;AAW7E,2CAAwC;AACxC,iDAAiG;AACjG,sDAAwD;AACxD,oDAAiD;AACjD,4DAAmE;AAEnE;;;;;;;;;;;;GAYG;AACH,MAAa,2BAA2B;IAC5B,MAAM,CAAS;IACf,SAAS,CAAY;IACrB,aAAa,CAA6C;IAE3D,YAAY,CAejB;IAEF,YAAY,EACR,UAAU,EACV,WAAW,EACX,OAAO,EACP,mBAAmB,EACnB,SAAS,EACT,MAAM,EACN,SAAS,EACT,KAAK,EACL,QAAQ,GAWX;QACG,IAAI,CAAC,SAAS,EAAE,CAAC;YACb,SAAS,GAAG,IAAI,qBAAS,EAAE,CAAC;QAChC,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QAE/C,MAAM,UAAU,GAAG,IAAA,kBAAS,EAAC,MAAM,CAAC,CAAC;QACrC,IAAI,CAAC,SAAS,GAAG,IAAI,qBAAS,CAAC,SAAS,CAAC,CAAC;QAE1C,MAAM,UAAU,GAAG,IAAI,4BAAe,CAClC,SAAS,EACT,IAAI,CAAC,SAAS,EACd,UAAU,EACV,UAAU,CAAC,OAAO,EAClB,UAAU,CAAC,QAAQ,CACtB,CAAC;QACF,MAAM,WAAW,GAAG,IAAI,oBAAW,CAAC,mBAAmB,EAAE,SAAS,CAAC,CAAC;QACpE,IAAI,CAAC,aAAa,GAAG,IAAA,uCAAuB,EACxC,SAAS,EACT,UAAU,EACV,WAAW,EACX,WAAW,EACX,OAAO,EACP,KAAK,EACL,QAAQ,CACX,CAAC;QAEF,IAAI,CAAC,YAAY,GAAG;YAChB,UAAU,EAAE,KAAK,EAAE,OAAkB,EAAE,EAAE;gBACrC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,wBAAwB,IAAA,qBAAM,EAAC,OAAO,CAAC,EAAE,CAAC,CAAC;gBAC7D,sCAAsC;gBACtC,OAAO,EAAE,CAAC;YACd,CAAC;YACD,UAAU,EAAE,KAAK,EAAE,OAAkB,EAAE,EAAE;gBACrC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,yBAAyB,IAAA,qBAAM,EAAC,OAAO,CAAC,EAAE,CAAC,CAAC;gBAC9D,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC,IAAA,qBAAM,EAAC,OAAO,CAAC,CAAC,CAAC;gBACnE,IAAI,CAAC,IAAI,CAAC,0BAA0B,EAAE,CAAC;oBACnC,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;gBAC3E,CAAC;gBACD,OAAO,IAAI,CAAC,0BAA0B,CAAC;YAC3C,CAAC;SACJ,CAAC;IACN,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW;QACb,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;QACtC,OAAO,IAAA,yCAA0B,EAAC,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE,CAAC,CAAC;IACxE,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,CAAC,qBAAqB,CAAC,SAAoB,EAAE,MAAoB;QACnE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,yBAAyB,IAAA,qBAAM,EAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QAC/D,KAAM,CAAC,CAAC,IAAA,0CAA2B,EAAC,IAAI,CAAC,aAAa,CAAC,qBAAqB,CAAC,IAAA,qBAAM,EAAC,SAAS,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IAC7G,CAAC;CACJ;AAvGD,kEAuGC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protontech/drive-sdk",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Proton Drive SDK",
5
5
  "license": "GPL-3.0",
6
6
  "main": "dist/index.js",
@@ -170,7 +170,7 @@ export class DriveCrypto {
170
170
  async decryptKey(
171
171
  armoredKey: string,
172
172
  armoredPassphrase: string,
173
- armoredPassphraseSignature: string,
173
+ armoredPassphraseSignature: string | undefined,
174
174
  decryptionKeys: PrivateKey[],
175
175
  verificationKeys: PublicKey[],
176
176
  ): Promise<{
@@ -52,6 +52,17 @@ export enum VERIFICATION_STATUS {
52
52
  }
53
53
 
54
54
  export interface SRPModule {
55
+ getSrp: (
56
+ version: number,
57
+ modulus: string,
58
+ serverEphemeral: string,
59
+ salt: string,
60
+ password: string,
61
+ ) => Promise<{
62
+ expectedServerProof: string;
63
+ clientProof: string;
64
+ clientEphemeral: string;
65
+ }>;
55
66
  getSrpVerifier: (password: string) => Promise<SRPVerifier>;
56
67
  computeKeyPassword: (password: string, salt: string) => Promise<string>;
57
68
  }
@@ -229,7 +240,7 @@ export interface OpenPGPCrypto {
229
240
 
230
241
  decryptArmoredAndVerifyDetached: (
231
242
  armoredData: string,
232
- armoredSignature: string,
243
+ armoredSignature: string | undefined,
233
244
  sessionKey: SessionKey,
234
245
  verificationKeys: PublicKey | PublicKey[],
235
246
  ) => Promise<{
@@ -1,3 +1,4 @@
1
+ import { c } from 'ttag';
1
2
  import { OpenPGPCrypto, PrivateKey, PublicKey, SessionKey, VERIFICATION_STATUS } from './interface';
2
3
  import { uint8ArrayToBase64String } from './utils';
3
4
 
@@ -393,7 +394,7 @@ export class OpenPGPCryptoWithCryptoProxy implements OpenPGPCrypto {
393
394
 
394
395
  async decryptArmoredAndVerifyDetached(
395
396
  armoredData: string,
396
- armoredSignature: string,
397
+ armoredSignature: string | undefined,
397
398
  sessionKey: SessionKey,
398
399
  verificationKeys: PublicKey | PublicKey[],
399
400
  ) {
@@ -410,7 +411,9 @@ export class OpenPGPCryptoWithCryptoProxy implements OpenPGPCrypto {
410
411
  // pmcrypto 8.3.0 changes `verified` to `verificationStatus`.
411
412
  // Proper typing is too complex, it will be removed to support only newer pmcrypto.
412
413
  verified: verified || verificationStatus!,
413
- verificationErrors,
414
+ verificationErrors: !armoredSignature
415
+ ? [new Error(c('Error').t`Signature is missing`)]
416
+ : verificationErrors,
414
417
  };
415
418
  }
416
419
 
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ProtonDriveHTTPClient,
3
- ProtonDriveHTTPClientBlobOptions,
4
- ProtonDriveHTTPClientJsonOptions,
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: ProtonDriveHTTPClientJsonOptions): Promise<Response> {
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: ProtonDriveHTTPClientBlobOptions): Promise<Response> {
81
+ fetchBlob(options: ProtonDriveHTTPClientBlobRequest): Promise<Response> {
82
82
  return this.httpClient.fetchBlob(options);
83
83
  }
84
84
  }
@@ -1,18 +1,18 @@
1
1
  export interface ProtonDriveHTTPClient {
2
- fetchJson(options: ProtonDriveHTTPClientJsonOptions): Promise<Response>;
3
- fetchBlob(options: ProtonDriveHTTPClientBlobOptions): Promise<Response>;
2
+ fetchJson(request: ProtonDriveHTTPClientJsonRequest): Promise<Response>;
3
+ fetchBlob(request: ProtonDriveHTTPClientBlobRequest): Promise<Response>;
4
4
  }
5
5
 
6
- export type ProtonDriveHTTPClientJsonOptions = ProtonDriveHTTPClientBaseOptions & {
6
+ export type ProtonDriveHTTPClientJsonRequest = ProtonDriveHTTPClientBaseRequest & {
7
7
  json?: object;
8
8
  };
9
9
 
10
- export type ProtonDriveHTTPClientBlobOptions = ProtonDriveHTTPClientBaseOptions & {
10
+ export type ProtonDriveHTTPClientBlobRequest = ProtonDriveHTTPClientBaseRequest & {
11
11
  body?: XMLHttpRequestBodyInit;
12
12
  onProgress?: (progress: number) => void;
13
13
  };
14
14
 
15
- type ProtonDriveHTTPClientBaseOptions = {
15
+ type ProtonDriveHTTPClientBaseRequest = {
16
16
  url: string;
17
17
  method: string;
18
18
  headers: Headers;
@@ -27,8 +27,8 @@ export type {
27
27
  export { DriveEventType, SDKEvent } from './events';
28
28
  export type {
29
29
  ProtonDriveHTTPClient,
30
- ProtonDriveHTTPClientJsonOptions,
31
- ProtonDriveHTTPClientBlobOptions,
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
- passphrase?: string;
93
- key: PrivateKey;
94
- passphraseSessionKey: SessionKey;
95
- hashKey?: Uint8Array;
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
 
@@ -1,5 +1,6 @@
1
1
  export const enum HTTPErrorCode {
2
2
  OK = 200,
3
+ UNAUTHORIZED = 401,
3
4
  NOT_FOUND = 404,
4
5
  TOO_MANY_REQUESTS = 429,
5
6
  INTERNAL_SERVER_ERROR = 500,
@@ -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
- expectAPICodeError(error, ErrorCode.NOT_EXISTS, 'Not found');
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({ response, result }: { response: Response; result?: unknown }): ServerError {
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
- return new APIHTTPError(response.statusText || c('Error').t`Unknown error`, response.status);
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 APICodeError {
108
+ export class NotFoundAPIError extends ValidationError {
98
109
  name = 'NotFoundAPIError';
99
110
  }
@@ -49,7 +49,7 @@ export class DownloadCryptoService {
49
49
  );
50
50
  } catch (error: unknown) {
51
51
  const message = getErrorMessage(error);
52
- throw new DecryptionError(c('Error').t`Failed to decrypt block: ${message}`);
52
+ throw new DecryptionError(c('Error').t`Failed to decrypt block: ${message}`, { cause: error });
53
53
  }
54
54
 
55
55
  return decryptedBlock;
@@ -66,7 +66,7 @@ export class DownloadCryptoService {
66
66
  decryptedBlock = result.decryptedThumbnail;
67
67
  } catch (error: unknown) {
68
68
  const message = getErrorMessage(error);
69
- throw new DecryptionError(c('Error').t`Failed to decrypt thumbnail: ${message}`);
69
+ throw new DecryptionError(c('Error').t`Failed to decrypt thumbnail: ${message}`, { cause: error });
70
70
  }
71
71
 
72
72
  return decryptedBlock;
@@ -123,8 +123,10 @@ describe('FileDownloader', () => {
123
123
 
124
124
  const verifyOnProgress = async (downloadedBytes: number[]) => {
125
125
  expect(onProgress).toHaveBeenCalledTimes(downloadedBytes.length);
126
+ let fileProgress = 0;
126
127
  for (let i = 0; i < downloadedBytes.length; i++) {
127
- expect(onProgress).toHaveBeenNthCalledWith(i + 1, downloadedBytes[i]);
128
+ fileProgress += downloadedBytes[i];
129
+ expect(onProgress).toHaveBeenNthCalledWith(i + 1, fileProgress);
128
130
  }
129
131
  };
130
132
 
@@ -134,7 +134,7 @@ export class FileDownloader {
134
134
  const blockData = await this.downloadBlockData(blockMetadata, true, cryptoKeys);
135
135
  return blockData.slice(blockOffset);
136
136
  } catch (error: unknown) {
137
- return error instanceof Error ? error : new Error(`Unknown error: ${error}`);
137
+ return error instanceof Error ? error : new Error(`Unknown error: ${error}`, { cause: error });
138
138
  }
139
139
  }
140
140
 
@@ -193,7 +193,7 @@ export class FileDownloader {
193
193
  cryptoKeys,
194
194
  (downloadedBytes) => {
195
195
  fileProgress += downloadedBytes;
196
- onProgress?.(downloadedBytes);
196
+ onProgress?.(fileProgress);
197
197
  },
198
198
  );
199
199
  this.ongoingDownloads.set(blockMetadata.index, { downloadPromise });
@@ -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;
@@ -53,7 +53,9 @@ export class NodesCache {
53
53
  return deserialiseNode(nodeData);
54
54
  } catch (error: unknown) {
55
55
  await this.removeCorruptedNode({ nodeUid }, error);
56
- throw new Error(`Failed to deserialise node: ${error instanceof Error ? error.message : error}`);
56
+ throw new Error(`Failed to deserialise node: ${error instanceof Error ? error.message : error}`, {
57
+ cause: error,
58
+ });
57
59
  }
58
60
  }
59
61
 
@@ -18,10 +18,7 @@ describe('nodesCryptoCache', () => {
18
18
 
19
19
  beforeEach(async () => {
20
20
  memoryCache = new MemoryCache();
21
- await memoryCache.setEntity('nodeKeys-missingPassphrase', {
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('missingPassphrase');
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: missing passphrase');
99
+ expect(`${error}`).toBe('Error: Failed to deserialize node keys');
103
100
  }
104
101
 
105
102
  try {
106
- await memoryCache.getEntity('nodeKeys-missingPassphrase');
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, keys);
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.passphrase) {
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: missing passphrase`);
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> {
@@ -0,0 +1,145 @@
1
+ import { VERIFICATION_STATUS } from '../../crypto';
2
+ import {
3
+ resultOk,
4
+ resultError,
5
+ Author,
6
+ AnonymousUser,
7
+ ProtonDriveTelemetry,
8
+ Logger,
9
+ MetricsDecryptionErrorField,
10
+ MetricVerificationErrorField,
11
+ } from '../../interface';
12
+ import { getVerificationMessage } from '../errors';
13
+ import { splitNodeUid } from '../uids';
14
+ import {
15
+ EncryptedNode,
16
+ SharesService,
17
+ } from './interface';
18
+
19
+ export class NodesCryptoReporter {
20
+ private logger: Logger;
21
+
22
+ private reportedDecryptionErrors = new Set<string>();
23
+ private reportedVerificationErrors = new Set<string>();
24
+
25
+ constructor(
26
+ private telemetry: ProtonDriveTelemetry,
27
+ private shareService: SharesService,
28
+ ) {
29
+ this.telemetry = telemetry;
30
+ this.logger = telemetry.getLogger('nodes-crypto');
31
+ this.shareService = shareService;
32
+ }
33
+
34
+ async handleClaimedAuthor(
35
+ node: { uid: string; creationTime: Date },
36
+ field: MetricVerificationErrorField,
37
+ signatureType: string,
38
+ verified: VERIFICATION_STATUS,
39
+ verificationErrors?: Error[],
40
+ claimedAuthor?: string,
41
+ notAvailableVerificationKeys = false,
42
+ ): Promise<Author> {
43
+ const author = handleClaimedAuthor(
44
+ signatureType,
45
+ verified,
46
+ verificationErrors,
47
+ claimedAuthor,
48
+ notAvailableVerificationKeys,
49
+ );
50
+ if (!author.ok) {
51
+ void this.reportVerificationError(node, field, verificationErrors, claimedAuthor);
52
+ }
53
+ return author;
54
+ }
55
+
56
+ async reportVerificationError(
57
+ node: { uid: string; creationTime: Date },
58
+ field: MetricVerificationErrorField,
59
+ verificationErrors?: Error[],
60
+ claimedAuthor?: string,
61
+ ) {
62
+ if (this.reportedVerificationErrors.has(node.uid)) {
63
+ return;
64
+ }
65
+ this.reportedVerificationErrors.add(node.uid);
66
+
67
+ const fromBefore2024 = node.creationTime < new Date('2024-01-01');
68
+
69
+ let addressMatchingDefaultShare, volumeType;
70
+ try {
71
+ const { volumeId } = splitNodeUid(node.uid);
72
+ const { email } = await this.shareService.getMyFilesShareMemberEmailKey();
73
+ addressMatchingDefaultShare = claimedAuthor ? claimedAuthor === email : undefined;
74
+ volumeType = await this.shareService.getVolumeMetricContext(volumeId);
75
+ } catch (error: unknown) {
76
+ this.logger.error('Failed to check if claimed author matches default share', error);
77
+ }
78
+
79
+ this.logger.warn(
80
+ `Failed to verify ${field} for node ${node.uid} (from before 2024: ${fromBefore2024}, matching address: ${addressMatchingDefaultShare})`,
81
+ );
82
+
83
+ this.telemetry.recordMetric({
84
+ eventName: 'verificationError',
85
+ volumeType,
86
+ field,
87
+ addressMatchingDefaultShare,
88
+ fromBefore2024,
89
+ error: verificationErrors?.map((e) => e.message).join(', '),
90
+ uid: node.uid,
91
+ });
92
+ }
93
+
94
+ async reportDecryptionError(node: EncryptedNode, field: MetricsDecryptionErrorField, error: unknown) {
95
+ if (this.reportedDecryptionErrors.has(node.uid)) {
96
+ return;
97
+ }
98
+
99
+ const fromBefore2024 = node.creationTime < new Date('2024-01-01');
100
+
101
+ let volumeType;
102
+ try {
103
+ const { volumeId } = splitNodeUid(node.uid);
104
+ volumeType = await this.shareService.getVolumeMetricContext(volumeId);
105
+ } catch (error: unknown) {
106
+ this.logger.error('Failed to get metric context', error);
107
+ }
108
+
109
+ this.logger.error(`Failed to decrypt node ${node.uid} (from before 2024: ${fromBefore2024})`, error);
110
+
111
+ this.telemetry.recordMetric({
112
+ eventName: 'decryptionError',
113
+ volumeType,
114
+ field,
115
+ fromBefore2024,
116
+ error,
117
+ uid: node.uid,
118
+ });
119
+ this.reportedDecryptionErrors.add(node.uid);
120
+ }
121
+ }
122
+
123
+ /**
124
+ * @param signatureType - Must be translated before calling this function.
125
+ */
126
+ function handleClaimedAuthor(
127
+ signatureType: string,
128
+ verified: VERIFICATION_STATUS,
129
+ verificationErrors?: Error[],
130
+ claimedAuthor?: string,
131
+ notAvailableVerificationKeys = false,
132
+ ): Author {
133
+ if (!claimedAuthor && notAvailableVerificationKeys) {
134
+ return resultOk(null as AnonymousUser);
135
+ }
136
+
137
+ if (verified === VERIFICATION_STATUS.SIGNED_AND_VALID) {
138
+ return resultOk(claimedAuthor || (null as AnonymousUser));
139
+ }
140
+
141
+ return resultError({
142
+ claimedAuthor,
143
+ error: getVerificationMessage(verified, verificationErrors, signatureType, notAvailableVerificationKeys),
144
+ });
145
+ }
@@ -3,6 +3,7 @@ import { MemberRole, ProtonDriveAccount, ProtonDriveTelemetry, RevisionState } f
3
3
  import { getMockTelemetry } from '../../tests/telemetry';
4
4
  import { DecryptedNode, DecryptedNodeKeys, DecryptedUnparsedNode, EncryptedNode, SharesService } from './interface';
5
5
  import { NodesCryptoService } from './cryptoService';
6
+ import { NodesCryptoReporter } from './cryptoReporter';
6
7
 
7
8
  describe('nodesCryptoService', () => {
8
9
  let telemetry: ProtonDriveTelemetry;
@@ -74,7 +75,8 @@ describe('nodesCryptoService', () => {
74
75
  getVolumeMetricContext: jest.fn().mockResolvedValue('own_volume'),
75
76
  };
76
77
 
77
- cryptoService = new NodesCryptoService(telemetry, driveCrypto, account, sharesService);
78
+ const nodesCryptoReporter = new NodesCryptoReporter(telemetry, sharesService);
79
+ cryptoService = new NodesCryptoService(telemetry, driveCrypto, account, nodesCryptoReporter);
78
80
  });
79
81
 
80
82
  const parentKey = 'parentKey' as unknown as PrivateKey;