@protontech/drive-sdk 0.4.1 → 0.5.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 (130) hide show
  1. package/dist/diagnostic/sdkDiagnostic.js +1 -1
  2. package/dist/diagnostic/sdkDiagnostic.js.map +1 -1
  3. package/dist/interface/download.d.ts +4 -4
  4. package/dist/interface/upload.d.ts +6 -3
  5. package/dist/internal/apiService/apiService.d.ts +3 -0
  6. package/dist/internal/apiService/apiService.js +25 -2
  7. package/dist/internal/apiService/apiService.js.map +1 -1
  8. package/dist/internal/apiService/apiService.test.js +38 -0
  9. package/dist/internal/apiService/apiService.test.js.map +1 -1
  10. package/dist/internal/apiService/driveTypes.d.ts +31 -48
  11. package/dist/internal/apiService/errors.js +3 -0
  12. package/dist/internal/apiService/errors.js.map +1 -1
  13. package/dist/internal/apiService/errors.test.js +15 -7
  14. package/dist/internal/apiService/errors.test.js.map +1 -1
  15. package/dist/internal/asyncIteratorMap.d.ts +1 -1
  16. package/dist/internal/asyncIteratorMap.js +6 -1
  17. package/dist/internal/asyncIteratorMap.js.map +1 -1
  18. package/dist/internal/asyncIteratorMap.test.js +9 -0
  19. package/dist/internal/asyncIteratorMap.test.js.map +1 -1
  20. package/dist/internal/download/fileDownloader.d.ts +3 -3
  21. package/dist/internal/download/fileDownloader.js +5 -5
  22. package/dist/internal/download/fileDownloader.js.map +1 -1
  23. package/dist/internal/download/fileDownloader.test.js +8 -8
  24. package/dist/internal/download/fileDownloader.test.js.map +1 -1
  25. package/dist/internal/nodes/apiService.d.ts +6 -1
  26. package/dist/internal/nodes/apiService.js +44 -32
  27. package/dist/internal/nodes/apiService.js.map +1 -1
  28. package/dist/internal/nodes/apiService.test.js +148 -17
  29. package/dist/internal/nodes/apiService.test.js.map +1 -1
  30. package/dist/internal/nodes/debouncer.d.ts +23 -0
  31. package/dist/internal/nodes/debouncer.js +80 -0
  32. package/dist/internal/nodes/debouncer.js.map +1 -0
  33. package/dist/internal/nodes/debouncer.test.d.ts +1 -0
  34. package/dist/internal/nodes/debouncer.test.js +100 -0
  35. package/dist/internal/nodes/debouncer.test.js.map +1 -0
  36. package/dist/internal/nodes/nodesAccess.d.ts +2 -1
  37. package/dist/internal/nodes/nodesAccess.js +24 -5
  38. package/dist/internal/nodes/nodesAccess.js.map +1 -1
  39. package/dist/internal/nodes/nodesAccess.test.js +2 -2
  40. package/dist/internal/nodes/nodesAccess.test.js.map +1 -1
  41. package/dist/internal/photos/upload.d.ts +2 -1
  42. package/dist/internal/photos/upload.js +3 -3
  43. package/dist/internal/photos/upload.js.map +1 -1
  44. package/dist/internal/sharingPublic/apiService.d.ts +2 -2
  45. package/dist/internal/sharingPublic/apiService.js +1 -63
  46. package/dist/internal/sharingPublic/apiService.js.map +1 -1
  47. package/dist/internal/sharingPublic/cryptoCache.d.ts +0 -4
  48. package/dist/internal/sharingPublic/cryptoCache.js +0 -28
  49. package/dist/internal/sharingPublic/cryptoCache.js.map +1 -1
  50. package/dist/internal/sharingPublic/cryptoReporter.d.ts +16 -0
  51. package/dist/internal/sharingPublic/cryptoReporter.js +44 -0
  52. package/dist/internal/sharingPublic/cryptoReporter.js.map +1 -0
  53. package/dist/internal/sharingPublic/cryptoService.d.ts +3 -4
  54. package/dist/internal/sharingPublic/cryptoService.js +5 -43
  55. package/dist/internal/sharingPublic/cryptoService.js.map +1 -1
  56. package/dist/internal/sharingPublic/index.d.ts +21 -3
  57. package/dist/internal/sharingPublic/index.js +43 -12
  58. package/dist/internal/sharingPublic/index.js.map +1 -1
  59. package/dist/internal/sharingPublic/interface.d.ts +0 -1
  60. package/dist/internal/sharingPublic/nodes.d.ts +13 -0
  61. package/dist/internal/sharingPublic/nodes.js +28 -0
  62. package/dist/internal/sharingPublic/nodes.js.map +1 -0
  63. package/dist/internal/sharingPublic/session/session.d.ts +3 -3
  64. package/dist/internal/sharingPublic/session/url.test.js +3 -3
  65. package/dist/internal/sharingPublic/shares.d.ts +34 -0
  66. package/dist/internal/sharingPublic/shares.js +69 -0
  67. package/dist/internal/sharingPublic/shares.js.map +1 -0
  68. package/dist/internal/upload/apiService.js +10 -1
  69. package/dist/internal/upload/apiService.js.map +1 -1
  70. package/dist/internal/upload/controller.d.ts +8 -2
  71. package/dist/internal/upload/controller.js.map +1 -1
  72. package/dist/internal/upload/fileUploader.d.ts +6 -3
  73. package/dist/internal/upload/fileUploader.js +3 -3
  74. package/dist/internal/upload/fileUploader.js.map +1 -1
  75. package/dist/internal/upload/fileUploader.test.js +23 -11
  76. package/dist/internal/upload/fileUploader.test.js.map +1 -1
  77. package/dist/internal/upload/streamUploader.d.ts +6 -2
  78. package/dist/internal/upload/streamUploader.js +8 -4
  79. package/dist/internal/upload/streamUploader.js.map +1 -1
  80. package/dist/internal/upload/streamUploader.test.js +10 -6
  81. package/dist/internal/upload/streamUploader.test.js.map +1 -1
  82. package/dist/protonDriveClient.d.ts +3 -3
  83. package/dist/protonDriveClient.js +4 -4
  84. package/dist/protonDriveClient.js.map +1 -1
  85. package/dist/protonDrivePublicLinkClient.d.ts +31 -4
  86. package/dist/protonDrivePublicLinkClient.js +52 -9
  87. package/dist/protonDrivePublicLinkClient.js.map +1 -1
  88. package/package.json +1 -1
  89. package/src/diagnostic/sdkDiagnostic.ts +1 -1
  90. package/src/interface/download.ts +4 -4
  91. package/src/interface/upload.ts +3 -3
  92. package/src/internal/apiService/apiService.test.ts +50 -0
  93. package/src/internal/apiService/apiService.ts +33 -2
  94. package/src/internal/apiService/driveTypes.ts +31 -48
  95. package/src/internal/apiService/errors.test.ts +10 -0
  96. package/src/internal/apiService/errors.ts +5 -1
  97. package/src/internal/asyncIteratorMap.test.ts +12 -0
  98. package/src/internal/asyncIteratorMap.ts +8 -0
  99. package/src/internal/download/fileDownloader.test.ts +8 -8
  100. package/src/internal/download/fileDownloader.ts +5 -5
  101. package/src/internal/nodes/apiService.test.ts +199 -16
  102. package/src/internal/nodes/apiService.ts +62 -49
  103. package/src/internal/nodes/debouncer.test.ts +129 -0
  104. package/src/internal/nodes/debouncer.ts +93 -0
  105. package/src/internal/nodes/nodesAccess.test.ts +2 -2
  106. package/src/internal/nodes/nodesAccess.ts +30 -5
  107. package/src/internal/photos/upload.ts +4 -1
  108. package/src/internal/sharingPublic/apiService.ts +4 -87
  109. package/src/internal/sharingPublic/cryptoCache.ts +0 -34
  110. package/src/internal/sharingPublic/cryptoReporter.ts +73 -0
  111. package/src/internal/sharingPublic/cryptoService.ts +4 -80
  112. package/src/internal/sharingPublic/index.ts +68 -6
  113. package/src/internal/sharingPublic/interface.ts +0 -9
  114. package/src/internal/sharingPublic/nodes.ts +37 -0
  115. package/src/internal/sharingPublic/session/apiService.ts +1 -1
  116. package/src/internal/sharingPublic/session/session.ts +3 -3
  117. package/src/internal/sharingPublic/session/url.test.ts +3 -3
  118. package/src/internal/sharingPublic/shares.ts +86 -0
  119. package/src/internal/upload/apiService.ts +12 -1
  120. package/src/internal/upload/controller.ts +2 -2
  121. package/src/internal/upload/fileUploader.test.ts +25 -11
  122. package/src/internal/upload/fileUploader.ts +4 -3
  123. package/src/internal/upload/streamUploader.test.ts +15 -3
  124. package/src/internal/upload/streamUploader.ts +8 -3
  125. package/src/protonDriveClient.ts +4 -4
  126. package/src/protonDrivePublicLinkClient.ts +93 -12
  127. package/dist/internal/sharingPublic/manager.d.ts +0 -19
  128. package/dist/internal/sharingPublic/manager.js +0 -81
  129. package/dist/internal/sharingPublic/manager.js.map +0 -1
  130. package/src/internal/sharingPublic/manager.ts +0 -86
@@ -1,11 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ProtonDrivePublicLinkClient = void 0;
4
+ const cache_1 = require("./cache");
4
5
  const config_1 = require("./config");
5
6
  const crypto_1 = require("./crypto");
6
7
  const telemetry_1 = require("./telemetry");
7
8
  const transformers_1 = require("./transformers");
8
9
  const apiService_1 = require("./internal/apiService");
10
+ const download_1 = require("./internal/download");
9
11
  const sdkEvents_1 = require("./internal/sdkEvents");
10
12
  const sharingPublic_1 = require("./internal/sharingPublic");
11
13
  /**
@@ -25,26 +27,30 @@ class ProtonDrivePublicLinkClient {
25
27
  logger;
26
28
  sdkEvents;
27
29
  sharingPublic;
30
+ download;
28
31
  experimental;
29
- constructor({ httpClient, cryptoCache, account, openPGPCryptoModule, srpModule, config, telemetry, token, password, }) {
32
+ constructor({ httpClient, account, openPGPCryptoModule, srpModule, config, telemetry, url, token, password, }) {
30
33
  if (!telemetry) {
31
34
  telemetry = new telemetry_1.Telemetry();
32
35
  }
33
36
  this.logger = telemetry.getLogger('interface');
37
+ // Use only in memory cache for public link as there are no events to keep it up to date if persisted.
38
+ const entitiesCache = new cache_1.MemoryCache();
39
+ const cryptoCache = new cache_1.MemoryCache();
34
40
  const fullConfig = (0, config_1.getConfig)(config);
35
41
  this.sdkEvents = new sdkEvents_1.SDKEvents(telemetry);
36
42
  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);
43
+ const cryptoModule = new crypto_1.DriveCrypto(openPGPCryptoModule, srpModule);
44
+ this.sharingPublic = (0, sharingPublic_1.initSharingPublicModule)(telemetry, apiService, entitiesCache, cryptoCache, cryptoModule, account, url, token, password);
45
+ this.download = (0, download_1.initDownloadModule)(telemetry, apiService, cryptoModule, account, this.sharingPublic.shares, this.sharingPublic.nodes.access, this.sharingPublic.nodes.revisions);
39
46
  this.experimental = {
40
47
  getNodeUrl: async (nodeUid) => {
41
48
  this.logger.debug(`Getting node URL for ${(0, transformers_1.getUid)(nodeUid)}`);
42
- // TODO: public node has different URL
43
- return '';
49
+ return this.sharingPublic.nodes.access.getNodeUrl((0, transformers_1.getUid)(nodeUid));
44
50
  },
45
51
  getDocsKey: async (nodeUid) => {
46
52
  this.logger.debug(`Getting docs keys for ${(0, transformers_1.getUid)(nodeUid)}`);
47
- const keys = await this.sharingPublic.getNodeKeys((0, transformers_1.getUid)(nodeUid));
53
+ const keys = await this.sharingPublic.nodes.access.getNodeKeys((0, transformers_1.getUid)(nodeUid));
48
54
  if (!keys.contentKeyPacketSessionKey) {
49
55
  throw new Error('Node does not have a content key packet session key');
50
56
  }
@@ -57,16 +63,53 @@ class ProtonDrivePublicLinkClient {
57
63
  */
58
64
  async getRootNode() {
59
65
  this.logger.info(`Getting root node`);
60
- return (0, transformers_1.convertInternalNodePromise)(this.sharingPublic.getRootNode());
66
+ const { rootNodeUid } = await this.sharingPublic.shares.getOwnVolumeIDs();
67
+ return (0, transformers_1.convertInternalNodePromise)(this.sharingPublic.nodes.access.getNode(rootNodeUid));
61
68
  }
62
69
  /**
63
70
  * Iterates the children of the given parent node.
64
71
  *
65
72
  * See `ProtonDriveClient.iterateFolderChildren` for more information.
66
73
  */
67
- async *iterateFolderChildren(parentUid, signal) {
74
+ async *iterateFolderChildren(parentUid, filterOptions, signal) {
68
75
  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));
76
+ yield* (0, transformers_1.convertInternalNodeIterator)(this.sharingPublic.nodes.access.iterateFolderChildren((0, transformers_1.getUid)(parentUid), filterOptions, signal));
77
+ }
78
+ /**
79
+ * Iterates the nodes by their UIDs.
80
+ *
81
+ * See `ProtonDriveClient.iterateNodes` for more information.
82
+ */
83
+ async *iterateNodes(nodeUids, signal) {
84
+ this.logger.info(`Iterating ${nodeUids.length} nodes`);
85
+ yield* (0, transformers_1.convertInternalMissingNodeIterator)(this.sharingPublic.nodes.access.iterateNodes((0, transformers_1.getUids)(nodeUids), signal));
86
+ }
87
+ /**
88
+ * Get the node by its UID.
89
+ *
90
+ * See `ProtonDriveClient.getNode` for more information.
91
+ */
92
+ async getNode(nodeUid) {
93
+ this.logger.info(`Getting node ${(0, transformers_1.getUid)(nodeUid)}`);
94
+ return (0, transformers_1.convertInternalNodePromise)(this.sharingPublic.nodes.access.getNode((0, transformers_1.getUid)(nodeUid)));
95
+ }
96
+ /**
97
+ * Get the file downloader to download the node content.
98
+ *
99
+ * See `ProtonDriveClient.getFileDownloader` for more information.
100
+ */
101
+ async getFileDownloader(nodeUid, signal) {
102
+ this.logger.info(`Getting file downloader for ${(0, transformers_1.getUid)(nodeUid)}`);
103
+ return this.download.getFileDownloader((0, transformers_1.getUid)(nodeUid), signal);
104
+ }
105
+ /**
106
+ * Iterates the thumbnails of the given nodes.
107
+ *
108
+ * See `ProtonDriveClient.iterateThumbnails` for more information.
109
+ */
110
+ async *iterateThumbnails(nodeUids, thumbnailType, signal) {
111
+ this.logger.info(`Iterating ${nodeUids.length} thumbnails`);
112
+ yield* this.download.iterateThumbnails((0, transformers_1.getUids)(nodeUids), thumbnailType, signal);
70
113
  }
71
114
  }
72
115
  exports.ProtonDrivePublicLinkClient = ProtonDrivePublicLinkClient;
@@ -1 +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"}
1
+ {"version":3,"file":"protonDrivePublicLinkClient.js","sourceRoot":"","sources":["../src/protonDrivePublicLinkClient.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protontech/drive-sdk",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Proton Drive SDK",
5
5
  "license": "GPL-3.0",
6
6
  "main": "dist/index.js",
@@ -133,7 +133,7 @@ export class SDKDiagnostic implements Diagnostic {
133
133
  const claimedSizeInBytes = downloader.getClaimedSizeInBytes();
134
134
 
135
135
  const integrityVerificationStream = new IntegrityVerificationStream();
136
- const controller = downloader.writeToStream(integrityVerificationStream);
136
+ const controller = downloader.downloadToStream(integrityVerificationStream);
137
137
 
138
138
  try {
139
139
  await controller.completion();
@@ -15,14 +15,14 @@ export interface FileDownloader {
15
15
  *
16
16
  * @param onProgress - Callback that is called with the number of downloaded bytes
17
17
  */
18
- writeToStream(streamFactory: WritableStream, onProgress?: (downloadedBytes: number) => void): DownloadController;
18
+ downloadToStream(streamFactory: WritableStream, onProgress?: (downloadedBytes: number) => void): DownloadController;
19
19
 
20
20
  /**
21
- * Same as `writeToStream` but without verification checks.
21
+ * Same as `downloadToStream` but without verification checks.
22
22
  *
23
23
  * Use this only for debugging purposes.
24
24
  */
25
- unsafeWriteToStream(
25
+ unsafeDownloadToStream(
26
26
  streamFactory: WritableStream,
27
27
  onProgress?: (downloadedBytes: number) => void,
28
28
  ): DownloadController;
@@ -34,7 +34,7 @@ export interface FileDownloader {
34
34
  * need to download the entire file.
35
35
  *
36
36
  * Stream doesn't verify data integrity. For the full integrity of
37
- * the file, use `writeToStream` instead.
37
+ * the file, use `downloadToStream` instead.
38
38
  *
39
39
  * The stream is not opportunitistically downloading the data ahead of
40
40
  * the time. It will only download the data when it is requested. To
@@ -41,7 +41,7 @@ export interface FileRevisionUploader {
41
41
  *
42
42
  * The function will reject if the node with the given name already exists.
43
43
  */
44
- writeStream(
44
+ uploadFromStream(
45
45
  stream: ReadableStream,
46
46
  thumnbails: Thumbnail[],
47
47
  onProgress?: (uploadedBytes: number) => void,
@@ -57,7 +57,7 @@ export interface FileRevisionUploader {
57
57
  *
58
58
  * The function will reject if the node with the given name already exists.
59
59
  */
60
- writeFile(
60
+ uploadFromFile(
61
61
  fileObject: File,
62
62
  thumnbails: Thumbnail[],
63
63
  onProgress?: (uploadedBytes: number) => void,
@@ -79,5 +79,5 @@ export interface FileUploader extends FileRevisionUploader {
79
79
  export interface UploadController {
80
80
  pause(): void;
81
81
  resume(): void;
82
- completion(): Promise<string>;
82
+ completion(): Promise<{ nodeRevisionUid: string, nodeUid: string }>;
83
83
  }
@@ -1,3 +1,4 @@
1
+ import { AbortError } from '../../errors';
1
2
  import { ProtonDriveHTTPClient, SDKEvent } from '../../interface';
2
3
  import { getMockTelemetry } from '../../tests/telemetry';
3
4
  import { SDKEvents } from '../sdkEvents';
@@ -112,6 +113,16 @@ describe('DriveAPIService', () => {
112
113
  });
113
114
 
114
115
  describe('should throw', () => {
116
+ it('AbortError on aborted error from the provided HTTP client', async () => {
117
+ const abortError = new Error('AbortError');
118
+ abortError.name = 'AbortError';
119
+
120
+ httpClient.fetchJson = jest.fn(() => Promise.reject(abortError));
121
+
122
+ await expect(api.get('test')).rejects.toThrow(new AbortError('Request aborted'));
123
+ expectSDKEvents();
124
+ });
125
+
115
126
  it('APIHTTPError on 4xx response without JSON body', async () => {
116
127
  httpClient.fetchJson = jest.fn(() =>
117
128
  Promise.resolve(new Response('Not found', { status: 404, statusText: 'Not found' })),
@@ -314,5 +325,44 @@ describe('DriveAPIService', () => {
314
325
  expect(httpClient.fetchJson).toHaveBeenCalledTimes(35);
315
326
  expectSDKEvents();
316
327
  });
328
+
329
+ it('notify about offline error', async () => {
330
+ jest.useFakeTimers();
331
+ const offlineError = new Error('OfflineError');
332
+ offlineError.name = 'OfflineError';
333
+
334
+ let attempt = 0;
335
+ httpClient.fetchJson = jest.fn().mockImplementation(() => {
336
+ if (attempt++ >= 15) {
337
+ return generateOkResponse();
338
+ }
339
+ throw offlineError;
340
+ });
341
+
342
+ const promise = api.get('test');
343
+
344
+ // First 9 calls (first is immediate, then 8 with 5 second delay), no events are sent yet
345
+ await jest.advanceTimersByTimeAsync(5 * 8 * 1000);
346
+ expect(httpClient.fetchJson).toHaveBeenCalledTimes(9);
347
+ expectSDKEvents();
348
+
349
+ // 10th call, service sends TransfersPaused event
350
+ await jest.advanceTimersByTimeAsync(5 * 1000);
351
+ expect(httpClient.fetchJson).toHaveBeenCalledTimes(10);
352
+ expectSDKEvents(SDKEvent.TransfersPaused);
353
+
354
+ // Next 5 calls, still offline, no more events are sent
355
+ await jest.advanceTimersByTimeAsync(5 * 5 * 1000);
356
+ expect(httpClient.fetchJson).toHaveBeenCalledTimes(15);
357
+ expectSDKEvents(SDKEvent.TransfersPaused);
358
+
359
+ // 16th call, mock returns OK response, service sends TransfersResumed event
360
+ await jest.advanceTimersByTimeAsync(5 * 1000);
361
+ expect(httpClient.fetchJson).toHaveBeenCalledTimes(16);
362
+ expectSDKEvents(SDKEvent.TransfersPaused, SDKEvent.TransfersResumed);
363
+
364
+ await promise;
365
+ });
366
+
317
367
  });
318
368
  });
@@ -40,6 +40,11 @@ const TOO_MANY_SUBSEQUENT_SERVER_ERRORS = 10;
40
40
  */
41
41
  const TOO_MANY_SUBSEQUENT_SERVER_ERRORS_TIMEOUT_IN_SECONDS = 60;
42
42
 
43
+ /**
44
+ * How many subsequent offline errors are allowed before we consider the client offline.
45
+ */
46
+ const TOO_MANY_SUBSEQUENT_OFFLINE_ERRORS = 10;
47
+
43
48
  /**
44
49
  * After how long to re-try after 5xx or timeout error.
45
50
  */
@@ -88,6 +93,8 @@ export class DriveAPIService {
88
93
  private subsequentServerErrorsCounter = 0;
89
94
  private lastServerErrorAt?: number;
90
95
 
96
+ private subsequentOfflineErrorsCounter = 0;
97
+
91
98
  private logger: Logger;
92
99
 
93
100
  constructor(
@@ -219,7 +226,7 @@ export class DriveAPIService {
219
226
  if (error instanceof ProtonDriveError) {
220
227
  throw error;
221
228
  }
222
- throw apiErrorFactory({ response });
229
+ throw apiErrorFactory({ response, error });
223
230
  }
224
231
  }
225
232
  return response;
@@ -261,7 +268,13 @@ export class DriveAPIService {
261
268
  response = await callback();
262
269
  } catch (error: unknown) {
263
270
  if (error instanceof Error) {
271
+ if (error.name === 'AbortError') {
272
+ this.logger.debug(`${request.method} ${request.url}: Aborted`);
273
+ throw new AbortError(c('Error').t`Request aborted`);
274
+ }
275
+
264
276
  if (error.name === 'OfflineError') {
277
+ this.offlineErrorHappened();
265
278
  this.logger.info(`${request.method} ${request.url}: Offline error, retrying`);
266
279
  await waitSeconds(OFFLINE_RETRY_DELAY_SECONDS);
267
280
  return this.fetch(request, callback, attempt + 1);
@@ -282,6 +295,8 @@ export class DriveAPIService {
282
295
  throw error;
283
296
  }
284
297
 
298
+ this.clearSubsequentOfflineErrors();
299
+
285
300
  const end = Date.now();
286
301
  const duration = end - start;
287
302
 
@@ -342,7 +357,7 @@ export class DriveAPIService {
342
357
  // the client is very limited. This is generic event and it doesn't
343
358
  // take into account that various endpoints can be rate limited
344
359
  // independently.
345
- if (this.subsequentTooManyRequestsCounter >= TOO_MANY_SUBSEQUENT_429_ERRORS) {
360
+ if (this.subsequentTooManyRequestsCounter === TOO_MANY_SUBSEQUENT_429_ERRORS) {
346
361
  this.sdkEvents.requestsThrottled();
347
362
  }
348
363
  }
@@ -373,4 +388,20 @@ export class DriveAPIService {
373
388
  this.subsequentServerErrorsCounter = 0;
374
389
  this.lastServerErrorAt = undefined;
375
390
  }
391
+
392
+ private offlineErrorHappened() {
393
+ this.subsequentOfflineErrorsCounter++;
394
+
395
+ if (this.subsequentOfflineErrorsCounter === TOO_MANY_SUBSEQUENT_OFFLINE_ERRORS) {
396
+ this.sdkEvents.transfersPaused();
397
+ }
398
+ }
399
+
400
+ private clearSubsequentOfflineErrors() {
401
+ if (this.subsequentOfflineErrorsCounter >= TOO_MANY_SUBSEQUENT_OFFLINE_ERRORS) {
402
+ this.sdkEvents.transfersResumed();
403
+ }
404
+
405
+ this.subsequentOfflineErrorsCounter = 0;
406
+ }
376
407
  }
@@ -1540,10 +1540,16 @@ export interface paths {
1540
1540
  path?: never;
1541
1541
  cookie?: never;
1542
1542
  };
1543
- /** Get status of migration from legacy photo share on a regular volume into a new Photo Volume */
1543
+ /**
1544
+ * Get status of migration from legacy photo share on a regular volume into a new Photo Volume
1545
+ * @deprecated
1546
+ */
1544
1547
  get: operations['get_drive-photos-migrate-legacy'];
1545
1548
  put?: never;
1546
- /** Start migration from legacy photo share on a regular volume into a new Photo Volume */
1549
+ /**
1550
+ * DEPRECATED: All shares have been migrated, always returns share not found
1551
+ * @deprecated
1552
+ */
1547
1553
  post: operations['post_drive-photos-migrate-legacy'];
1548
1554
  delete?: never;
1549
1555
  options?: never;
@@ -3502,7 +3508,7 @@ export interface components {
3502
3508
  Thumbnail: number | null;
3503
3509
  /**
3504
3510
  * @deprecated
3505
- * @description Hash of thumbnail contents
3511
+ * @description sha256 hash of thumbnail contents
3506
3512
  * @default null
3507
3513
  */
3508
3514
  ThumbnailHash: string | null;
@@ -3679,7 +3685,6 @@ export interface components {
3679
3685
  */
3680
3686
  Code: 1000;
3681
3687
  };
3682
- MigrateFromLegacyRequest: Record<string, never>;
3683
3688
  RemoveTagsRequestDto: {
3684
3689
  Tags: components['schemas']['TagType'][];
3685
3690
  };
@@ -5097,7 +5102,10 @@ export interface components {
5097
5102
  File: components['schemas']['FileDto'];
5098
5103
  /** @default null */
5099
5104
  Sharing: components['schemas']['SharingDto'] | null;
5100
- /** @default null */
5105
+ /**
5106
+ * @description Will be null if the user is not a member or is the owner.
5107
+ * @default null
5108
+ */
5101
5109
  Membership: components['schemas']['MembershipDto'] | null;
5102
5110
  /** @default null */
5103
5111
  Folder: null | null;
@@ -5109,7 +5117,10 @@ export interface components {
5109
5117
  Folder: components['schemas']['FolderDto'];
5110
5118
  /** @default null */
5111
5119
  Sharing: components['schemas']['SharingDto'] | null;
5112
- /** @default null */
5120
+ /**
5121
+ * @description Will be null if the user is not a member or is the owner.
5122
+ * @default null
5123
+ */
5113
5124
  Membership: components['schemas']['MembershipDto'] | null;
5114
5125
  /** @default null */
5115
5126
  File: null | null;
@@ -5235,7 +5246,7 @@ export interface components {
5235
5246
  Size: number;
5236
5247
  /** @description Index of block in list (must be consecutive starting at 1) */
5237
5248
  Index: number;
5238
- /** @description Hash of encrypted block, base64 encoded */
5249
+ /** @description sha256 hash of encrypted block, base64 encoded */
5239
5250
  Hash: string;
5240
5251
  /** @default null */
5241
5252
  Verifier: components['schemas']['Verifier'] | null;
@@ -5249,7 +5260,7 @@ export interface components {
5249
5260
  /** @description Block size in bytes. WARNING: when type is NOT 2=HDPreview(1920) then the max size is 65536 */
5250
5261
  Size: number;
5251
5262
  Type: components['schemas']['ThumbnailType'];
5252
- /** @description Hash of encrypted block, base64 encoded */
5263
+ /** @description sha256 hash of encrypted block, base64 encoded */
5253
5264
  Hash: string;
5254
5265
  };
5255
5266
  BlockURL: {
@@ -5330,7 +5341,7 @@ export interface components {
5330
5341
  Size: number;
5331
5342
  /** @description Index of block in list (must be consecutive starting at 1) */
5332
5343
  Index: number;
5333
- /** @description Hash of encrypted block, base64 encoded */
5344
+ /** @description sha256 hash of encrypted block, base64 encoded */
5334
5345
  Hash: string;
5335
5346
  Verifier: components['schemas']['Verifier'];
5336
5347
  /**
@@ -5387,7 +5398,10 @@ export interface components {
5387
5398
  Folder: components['schemas']['FolderDto2'];
5388
5399
  /** @default null */
5389
5400
  Sharing: components['schemas']['SharingDto2'] | null;
5390
- /** @default null */
5401
+ /**
5402
+ * @description Will be null if the user is not a member or is the owner.
5403
+ * @default null
5404
+ */
5391
5405
  Membership: components['schemas']['MembershipDto2'] | null;
5392
5406
  /** @default null */
5393
5407
  File: null | null;
@@ -5400,10 +5414,10 @@ export interface components {
5400
5414
  */
5401
5415
  ShareType: 1 | 2 | 3 | 4;
5402
5416
  /**
5403
- * @description <p>1=Active, 3=Restored</p><details><summary>See values descriptions</summary><details><summary>See values descriptions</summary><table><tr><th>Value</th><th>Description</th></tr><tr><td>1</td><td>Active</td></tr><tr><td>2</td><td>Deleted</td></tr><tr><td>3</td><td>Restored</td></tr><tr><td>4</td><td>Migrating</td></tr><tr><td>5</td><td>Migrated</td></tr><tr><td>6</td><td>Locked</td></tr></table></details></details>
5417
+ * @description <p>1=Active, 3=Restored</p><details><summary>See values descriptions</summary><details><summary>See values descriptions</summary><table><tr><th>Value</th><th>Description</th></tr><tr><td>1</td><td>Active</td></tr><tr><td>2</td><td>Deleted</td></tr><tr><td>3</td><td>Restored</td></tr><tr><td>5</td><td>Migrated</td></tr><tr><td>6</td><td>Locked</td></tr></table></details></details>
5404
5418
  * @enum {integer}
5405
5419
  */
5406
- ShareState: 1 | 2 | 3 | 4 | 5 | 6;
5420
+ ShareState: 1 | 2 | 3 | 5 | 6;
5407
5421
  /**
5408
5422
  * @description <p>1=Regular, 2=Photo</p><details><summary>See values descriptions</summary><details><summary>See values descriptions</summary><table><tr><th>Value</th><th>Description</th></tr><tr><td>1</td><td>Regular</td></tr><tr><td>2</td><td>Photo</td></tr></table></details></details>
5409
5423
  * @enum {integer}
@@ -5518,6 +5532,7 @@ export interface components {
5518
5532
  */
5519
5533
  Token: string;
5520
5534
  LinkType: components['schemas']['NodeType3'];
5535
+ VolumeID: components['schemas']['Id2'];
5521
5536
  LinkID: components['schemas']['Id2'];
5522
5537
  SharePasswordSalt: components['schemas']['BinaryString2'];
5523
5538
  SharePassphrase: components['schemas']['PGPMessage2'];
@@ -6921,7 +6936,6 @@ export interface operations {
6921
6936
  'application/json': {
6922
6937
  /** @description Potential codes and their meaning:
6923
6938
  * - 2500: A volume is already active
6924
- * - 2500: Cannot create the new Photo volume. Should be migrated from current Photo stream
6925
6939
  * - 2001: Invalid PGP message
6926
6940
  * - 200501: Operation failed: Please retry
6927
6941
  * - 200200: Address not found
@@ -8717,6 +8731,7 @@ export interface operations {
8717
8731
  | {
8718
8732
  /** @description Potential codes and their meaning:
8719
8733
  * - 200003: Max file size limited to 100 MB on your plan. Please upgrade.
8734
+ * - 200303: Cannot commit related photo with main already in album
8720
8735
  * */
8721
8736
  Code: number;
8722
8737
  }
@@ -8837,6 +8852,7 @@ export interface operations {
8837
8852
  | {
8838
8853
  /** @description Potential codes and their meaning:
8839
8854
  * - 200003: Max file size limited to 100 MB on your plan. Please upgrade.
8855
+ * - 200303: Cannot commit related photo with main already in album
8840
8856
  * */
8841
8857
  Code: number;
8842
8858
  }
@@ -10257,22 +10273,8 @@ export interface operations {
10257
10273
  path?: never;
10258
10274
  cookie?: never;
10259
10275
  };
10260
- requestBody?: {
10261
- content: {
10262
- 'application/json': components['schemas']['MigrateFromLegacyRequest'];
10263
- };
10264
- };
10276
+ requestBody?: never;
10265
10277
  responses: {
10266
- /** @description Accepted */
10267
- 202: {
10268
- headers: {
10269
- 'x-pm-code': 1002;
10270
- [name: string]: unknown;
10271
- };
10272
- content: {
10273
- 'application/json': components['schemas']['AcceptedResponse'];
10274
- };
10275
- };
10276
10278
  /** @description Unprocessable Entity */
10277
10279
  422: {
10278
10280
  headers: {
@@ -10281,32 +10283,12 @@ export interface operations {
10281
10283
  content: {
10282
10284
  'application/json': {
10283
10285
  /** @description Potential codes and their meaning:
10284
- * - 2500: Migration in progress
10285
10286
  * - 2501: Share not found
10286
- * - 2501: Volume not found
10287
- * - 2501: Address not found
10288
10287
  * */
10289
10288
  Code: number;
10290
10289
  };
10291
10290
  };
10292
10291
  };
10293
- /** @description Failed dependency */
10294
- 424: {
10295
- headers: {
10296
- [name: string]: unknown;
10297
- };
10298
- content: {
10299
- 'application/json': {
10300
- /**
10301
- * @description Potential codes:
10302
- * - 2032
10303
- *
10304
- * @enum {integer}
10305
- */
10306
- Code: 2032;
10307
- };
10308
- };
10309
- };
10310
10292
  };
10311
10293
  };
10312
10294
  'get_drive-volumes-{volumeID}-photos': {
@@ -10482,6 +10464,7 @@ export interface operations {
10482
10464
  /** @description Potential codes and their meaning:
10483
10465
  * - 2011: The current ShareURL does not have read+write permissions.
10484
10466
  * - 200003: Max file size limited to 100 MB on your plan. Please upgrade.
10467
+ * - 200303: Cannot commit related photo with main already in album
10485
10468
  * */
10486
10469
  Code: number;
10487
10470
  }
@@ -1,3 +1,4 @@
1
+ import { AbortError } from '../../errors';
1
2
  import { apiErrorFactory } from './errors';
2
3
  import * as errors from './errors';
3
4
  import { ErrorCode } from './errorCodes';
@@ -17,6 +18,15 @@ function mockAPIResponseAndResult(options: {
17
18
  }
18
19
 
19
20
  describe('apiErrorFactory should return', () => {
21
+ it('AbortError on aborted error', () => {
22
+ const abortError = new Error('AbortError');
23
+ abortError.name = 'AbortError';
24
+
25
+ const error = apiErrorFactory({ response: new Response(), error: abortError });
26
+ expect(error).toBeInstanceOf(AbortError);
27
+ expect(error.message).toBe('Request aborted');
28
+ });
29
+
20
30
  it('generic APIHTTPError when there is no specifc body', () => {
21
31
  const response = new Response('', { status: 404, statusText: 'Not found' });
22
32
  const error = apiErrorFactory({ response });
@@ -1,6 +1,6 @@
1
1
  import { c } from 'ttag';
2
2
 
3
- import { ServerError, ValidationError } from '../../errors';
3
+ import { AbortError, ServerError, ValidationError } from '../../errors';
4
4
  import { ErrorCode, HTTPErrorCode } from './errorCodes';
5
5
 
6
6
  export function apiErrorFactory({
@@ -12,6 +12,10 @@ export function apiErrorFactory({
12
12
  result?: unknown;
13
13
  error?: unknown;
14
14
  }): ServerError {
15
+ if (error && error instanceof Error && error.name === 'AbortError') {
16
+ return new AbortError(c('Error').t`Request aborted`);
17
+ }
18
+
15
19
  // Backend responses with 404 both in the response and body code.
16
20
  // In such a case we want to stick to APIHTTPError to be very clear
17
21
  // it is not NotFoundAPIError.
@@ -1,3 +1,4 @@
1
+ import { AbortError } from '../errors';
1
2
  import { asyncIteratorMap } from './asyncIteratorMap';
2
3
 
3
4
  // Helper function to create an async generator from array
@@ -147,4 +148,15 @@ describe('asyncIteratorMap', () => {
147
148
  expect(maxConcurrentExecutions).toBe(concurrencyLimit);
148
149
  expect(results).toEqual([2, 4, 6, 8, 10, 12, 14, 16]);
149
150
  });
151
+
152
+ test('throws AbortError if signal is aborted', async () => {
153
+ const inputGen = createAsyncGenerator([1, 2, 3, 4, 5]);
154
+ const mapper = async (x: number) => x * 2;
155
+
156
+ const ac = new AbortController();
157
+ ac.abort();
158
+
159
+ const mappedGen = asyncIteratorMap(inputGen, mapper, 1, ac.signal);
160
+ await expect(collectResults(mappedGen)).rejects.toThrow(AbortError);
161
+ });
150
162
  });
@@ -1,3 +1,7 @@
1
+ import { c } from 'ttag';
2
+
3
+ import { AbortError } from '../errors';
4
+
1
5
  const DEFAULT_CONCURRENCY = 10;
2
6
 
3
7
  /**
@@ -18,6 +22,7 @@ export async function* asyncIteratorMap<I, O>(
18
22
  inputIterator: AsyncGenerator<I>,
19
23
  mapper: (item: I) => Promise<O>,
20
24
  concurrency: number = DEFAULT_CONCURRENCY,
25
+ signal?: AbortSignal,
21
26
  ): AsyncGenerator<O> {
22
27
  let done = false;
23
28
 
@@ -50,6 +55,9 @@ export async function* asyncIteratorMap<I, O>(
50
55
  };
51
56
 
52
57
  while (!done || executing.size > 0 || results.length > 0) {
58
+ if (signal?.aborted) {
59
+ throw new AbortError(c('Error').t`Operation aborted`);
60
+ }
53
61
  while (!done && executing.size < concurrency) {
54
62
  await pump();
55
63
  }