@protontech/drive-sdk 0.4.1 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/dist/diagnostic/{sdkDiagnosticFull.d.ts → diagnostic.d.ts} +5 -4
  2. package/dist/diagnostic/{sdkDiagnosticFull.js → diagnostic.js} +13 -10
  3. package/dist/diagnostic/diagnostic.js.map +1 -0
  4. package/dist/diagnostic/index.js +2 -4
  5. package/dist/diagnostic/index.js.map +1 -1
  6. package/dist/diagnostic/interface.d.ts +22 -1
  7. package/dist/diagnostic/sdkDiagnostic.d.ts +3 -2
  8. package/dist/diagnostic/sdkDiagnostic.js +80 -8
  9. package/dist/diagnostic/sdkDiagnostic.js.map +1 -1
  10. package/dist/interface/download.d.ts +4 -4
  11. package/dist/interface/index.d.ts +1 -1
  12. package/dist/interface/index.js.map +1 -1
  13. package/dist/interface/nodes.d.ts +9 -0
  14. package/dist/interface/telemetry.d.ts +4 -1
  15. package/dist/interface/telemetry.js.map +1 -1
  16. package/dist/interface/upload.d.ts +6 -3
  17. package/dist/internal/apiService/apiService.d.ts +3 -0
  18. package/dist/internal/apiService/apiService.js +25 -2
  19. package/dist/internal/apiService/apiService.js.map +1 -1
  20. package/dist/internal/apiService/apiService.test.js +38 -0
  21. package/dist/internal/apiService/apiService.test.js.map +1 -1
  22. package/dist/internal/apiService/driveTypes.d.ts +2595 -2397
  23. package/dist/internal/apiService/errors.js +3 -0
  24. package/dist/internal/apiService/errors.js.map +1 -1
  25. package/dist/internal/apiService/errors.test.js +15 -7
  26. package/dist/internal/apiService/errors.test.js.map +1 -1
  27. package/dist/internal/asyncIteratorMap.d.ts +1 -1
  28. package/dist/internal/asyncIteratorMap.js +6 -1
  29. package/dist/internal/asyncIteratorMap.js.map +1 -1
  30. package/dist/internal/asyncIteratorMap.test.js +9 -0
  31. package/dist/internal/asyncIteratorMap.test.js.map +1 -1
  32. package/dist/internal/download/controller.d.ts +2 -0
  33. package/dist/internal/download/controller.js +15 -1
  34. package/dist/internal/download/controller.js.map +1 -1
  35. package/dist/internal/download/fileDownloader.d.ts +3 -3
  36. package/dist/internal/download/fileDownloader.js +11 -6
  37. package/dist/internal/download/fileDownloader.js.map +1 -1
  38. package/dist/internal/download/fileDownloader.test.js +8 -8
  39. package/dist/internal/download/fileDownloader.test.js.map +1 -1
  40. package/dist/internal/nodes/apiService.d.ts +6 -1
  41. package/dist/internal/nodes/apiService.js +71 -44
  42. package/dist/internal/nodes/apiService.js.map +1 -1
  43. package/dist/internal/nodes/apiService.test.js +204 -15
  44. package/dist/internal/nodes/apiService.test.js.map +1 -1
  45. package/dist/internal/nodes/debouncer.d.ts +24 -0
  46. package/dist/internal/nodes/debouncer.js +92 -0
  47. package/dist/internal/nodes/debouncer.js.map +1 -0
  48. package/dist/internal/nodes/debouncer.test.d.ts +1 -0
  49. package/dist/internal/nodes/debouncer.test.js +108 -0
  50. package/dist/internal/nodes/debouncer.test.js.map +1 -0
  51. package/dist/internal/nodes/extendedAttributes.js +2 -2
  52. package/dist/internal/nodes/extendedAttributes.js.map +1 -1
  53. package/dist/internal/nodes/index.js +1 -1
  54. package/dist/internal/nodes/index.js.map +1 -1
  55. package/dist/internal/nodes/nodesAccess.d.ts +6 -4
  56. package/dist/internal/nodes/nodesAccess.js +29 -9
  57. package/dist/internal/nodes/nodesAccess.js.map +1 -1
  58. package/dist/internal/nodes/nodesAccess.test.js +19 -7
  59. package/dist/internal/nodes/nodesAccess.test.js.map +1 -1
  60. package/dist/internal/nodes/nodesManagement.d.ts +2 -2
  61. package/dist/internal/nodes/nodesManagement.js +5 -3
  62. package/dist/internal/nodes/nodesManagement.js.map +1 -1
  63. package/dist/internal/nodes/nodesManagement.test.js +3 -1
  64. package/dist/internal/nodes/nodesManagement.test.js.map +1 -1
  65. package/dist/internal/photos/apiService.js +9 -20
  66. package/dist/internal/photos/apiService.js.map +1 -1
  67. package/dist/internal/photos/upload.d.ts +2 -1
  68. package/dist/internal/photos/upload.js +9 -3
  69. package/dist/internal/photos/upload.js.map +1 -1
  70. package/dist/internal/sharing/apiService.d.ts +1 -1
  71. package/dist/internal/sharing/apiService.js +2 -2
  72. package/dist/internal/sharing/apiService.js.map +1 -1
  73. package/dist/internal/sharing/sharingManagement.d.ts +4 -1
  74. package/dist/internal/sharing/sharingManagement.js +7 -4
  75. package/dist/internal/sharing/sharingManagement.js.map +1 -1
  76. package/dist/internal/sharingPublic/apiService.d.ts +8 -10
  77. package/dist/internal/sharingPublic/apiService.js +9 -125
  78. package/dist/internal/sharingPublic/apiService.js.map +1 -1
  79. package/dist/internal/sharingPublic/cryptoReporter.d.ts +16 -0
  80. package/dist/internal/sharingPublic/{cryptoService.js → cryptoReporter.js} +3 -16
  81. package/dist/internal/sharingPublic/cryptoReporter.js.map +1 -0
  82. package/dist/internal/sharingPublic/index.d.ts +22 -4
  83. package/dist/internal/sharingPublic/index.js +37 -12
  84. package/dist/internal/sharingPublic/index.js.map +1 -1
  85. package/dist/internal/sharingPublic/nodes.d.ts +18 -0
  86. package/dist/internal/sharingPublic/nodes.js +46 -0
  87. package/dist/internal/sharingPublic/nodes.js.map +1 -0
  88. package/dist/internal/sharingPublic/session/apiService.d.ts +7 -5
  89. package/dist/internal/sharingPublic/session/apiService.js +25 -4
  90. package/dist/internal/sharingPublic/session/apiService.js.map +1 -1
  91. package/dist/internal/sharingPublic/session/interface.d.ts +17 -0
  92. package/dist/internal/sharingPublic/session/manager.d.ts +12 -4
  93. package/dist/internal/sharingPublic/session/manager.js +14 -4
  94. package/dist/internal/sharingPublic/session/manager.js.map +1 -1
  95. package/dist/internal/sharingPublic/session/session.d.ts +7 -4
  96. package/dist/internal/sharingPublic/session/session.js +7 -3
  97. package/dist/internal/sharingPublic/session/session.js.map +1 -1
  98. package/dist/internal/sharingPublic/session/url.test.js +3 -3
  99. package/dist/internal/sharingPublic/shares.d.ts +27 -0
  100. package/dist/internal/sharingPublic/shares.js +46 -0
  101. package/dist/internal/sharingPublic/shares.js.map +1 -0
  102. package/dist/internal/upload/apiService.js +10 -1
  103. package/dist/internal/upload/apiService.js.map +1 -1
  104. package/dist/internal/upload/controller.d.ts +11 -3
  105. package/dist/internal/upload/controller.js +16 -2
  106. package/dist/internal/upload/controller.js.map +1 -1
  107. package/dist/internal/upload/fileUploader.d.ts +6 -3
  108. package/dist/internal/upload/fileUploader.js +4 -4
  109. package/dist/internal/upload/fileUploader.js.map +1 -1
  110. package/dist/internal/upload/fileUploader.test.js +23 -11
  111. package/dist/internal/upload/fileUploader.test.js.map +1 -1
  112. package/dist/internal/upload/streamUploader.d.ts +9 -4
  113. package/dist/internal/upload/streamUploader.js +67 -20
  114. package/dist/internal/upload/streamUploader.js.map +1 -1
  115. package/dist/internal/upload/streamUploader.test.js +43 -13
  116. package/dist/internal/upload/streamUploader.test.js.map +1 -1
  117. package/dist/protonDriveClient.d.ts +11 -6
  118. package/dist/protonDriveClient.js +11 -10
  119. package/dist/protonDriveClient.js.map +1 -1
  120. package/dist/protonDrivePublicLinkClient.d.ts +34 -6
  121. package/dist/protonDrivePublicLinkClient.js +52 -9
  122. package/dist/protonDrivePublicLinkClient.js.map +1 -1
  123. package/dist/tests/telemetry.d.ts +4 -2
  124. package/dist/tests/telemetry.js +3 -1
  125. package/dist/tests/telemetry.js.map +1 -1
  126. package/dist/transformers.d.ts +3 -2
  127. package/dist/transformers.js +6 -0
  128. package/dist/transformers.js.map +1 -1
  129. package/package.json +1 -1
  130. package/src/diagnostic/{sdkDiagnosticFull.ts → diagnostic.ts} +10 -6
  131. package/src/diagnostic/index.ts +3 -5
  132. package/src/diagnostic/interface.ts +39 -0
  133. package/src/diagnostic/sdkDiagnostic.ts +111 -10
  134. package/src/interface/download.ts +4 -4
  135. package/src/interface/index.ts +1 -0
  136. package/src/interface/nodes.ts +3 -0
  137. package/src/interface/telemetry.ts +5 -0
  138. package/src/interface/upload.ts +3 -3
  139. package/src/internal/apiService/apiService.test.ts +50 -0
  140. package/src/internal/apiService/apiService.ts +33 -2
  141. package/src/internal/apiService/driveTypes.ts +2713 -2561
  142. package/src/internal/apiService/errors.test.ts +10 -0
  143. package/src/internal/apiService/errors.ts +5 -1
  144. package/src/internal/asyncIteratorMap.test.ts +12 -0
  145. package/src/internal/asyncIteratorMap.ts +8 -0
  146. package/src/internal/download/controller.ts +13 -1
  147. package/src/internal/download/fileDownloader.test.ts +8 -8
  148. package/src/internal/download/fileDownloader.ts +13 -6
  149. package/src/internal/nodes/apiService.test.ts +261 -14
  150. package/src/internal/nodes/apiService.ts +99 -65
  151. package/src/internal/nodes/debouncer.test.ts +141 -0
  152. package/src/internal/nodes/debouncer.ts +109 -0
  153. package/src/internal/nodes/extendedAttributes.ts +2 -2
  154. package/src/internal/nodes/index.ts +1 -8
  155. package/src/internal/nodes/nodesAccess.test.ts +19 -7
  156. package/src/internal/nodes/nodesAccess.ts +44 -9
  157. package/src/internal/nodes/nodesManagement.test.ts +3 -1
  158. package/src/internal/nodes/nodesManagement.ts +11 -5
  159. package/src/internal/photos/apiService.ts +12 -29
  160. package/src/internal/photos/upload.ts +22 -1
  161. package/src/internal/sharing/apiService.ts +2 -2
  162. package/src/internal/sharing/sharingManagement.ts +7 -4
  163. package/src/internal/sharingPublic/apiService.ts +23 -160
  164. package/src/internal/sharingPublic/{cryptoService.ts → cryptoReporter.ts} +2 -27
  165. package/src/internal/sharingPublic/index.ts +76 -13
  166. package/src/internal/sharingPublic/nodes.ts +59 -0
  167. package/src/internal/sharingPublic/session/apiService.ts +32 -10
  168. package/src/internal/sharingPublic/session/interface.ts +20 -0
  169. package/src/internal/sharingPublic/session/manager.ts +31 -8
  170. package/src/internal/sharingPublic/session/session.ts +12 -7
  171. package/src/internal/sharingPublic/session/url.test.ts +3 -3
  172. package/src/internal/sharingPublic/shares.ts +50 -0
  173. package/src/internal/upload/apiService.ts +12 -1
  174. package/src/internal/upload/controller.ts +16 -4
  175. package/src/internal/upload/fileUploader.test.ts +25 -11
  176. package/src/internal/upload/fileUploader.ts +6 -5
  177. package/src/internal/upload/streamUploader.test.ts +56 -12
  178. package/src/internal/upload/streamUploader.ts +78 -20
  179. package/src/protonDriveClient.ts +29 -11
  180. package/src/protonDrivePublicLinkClient.ts +100 -16
  181. package/src/tests/telemetry.ts +6 -3
  182. package/src/transformers.ts +8 -0
  183. package/dist/diagnostic/sdkDiagnosticFull.js.map +0 -1
  184. package/dist/internal/sharingPublic/cryptoCache.d.ts +0 -19
  185. package/dist/internal/sharingPublic/cryptoCache.js +0 -72
  186. package/dist/internal/sharingPublic/cryptoCache.js.map +0 -1
  187. package/dist/internal/sharingPublic/cryptoService.d.ts +0 -9
  188. package/dist/internal/sharingPublic/cryptoService.js.map +0 -1
  189. package/dist/internal/sharingPublic/interface.d.ts +0 -6
  190. package/dist/internal/sharingPublic/interface.js +0 -3
  191. package/dist/internal/sharingPublic/interface.js.map +0 -1
  192. package/dist/internal/sharingPublic/manager.d.ts +0 -19
  193. package/dist/internal/sharingPublic/manager.js +0 -81
  194. package/dist/internal/sharingPublic/manager.js.map +0 -1
  195. package/src/internal/sharingPublic/cryptoCache.ts +0 -79
  196. package/src/internal/sharingPublic/interface.ts +0 -14
  197. package/src/internal/sharingPublic/manager.ts +0 -86
@@ -1,7 +1,16 @@
1
1
  import { c } from 'ttag';
2
2
 
3
3
  import { PrivateKey, SessionKey } from '../../crypto';
4
- import { InvalidNameError, Logger, MissingNode, NodeType, Result, resultError, resultOk } from '../../interface';
4
+ import {
5
+ InvalidNameError,
6
+ Logger,
7
+ MissingNode,
8
+ NodeType,
9
+ ProtonDriveTelemetry,
10
+ Result,
11
+ resultError,
12
+ resultOk,
13
+ } from '../../interface';
5
14
  import { DecryptionError, ProtonDriveError } from '../../errors';
6
15
  import { asyncIteratorMap } from '../asyncIteratorMap';
7
16
  import { getErrorMessage } from '../errors';
@@ -11,6 +20,7 @@ import { NodeAPIService } from './apiService';
11
20
  import { NodesCache } from './cache';
12
21
  import { NodesCryptoCache } from './cryptoCache';
13
22
  import { NodesCryptoService } from './cryptoService';
23
+ import { NodesDebouncer } from './debouncer';
14
24
  import { parseFileExtendedAttributes, parseFolderExtendedAttributes } from './extendedAttributes';
15
25
  import {
16
26
  SharesService,
@@ -40,20 +50,27 @@ const DECRYPTION_CONCURRENCY = 30;
40
50
  * nodes metadata.
41
51
  */
42
52
  export class NodesAccess {
53
+ private logger: Logger;
54
+ private debouncer: NodesDebouncer;
55
+
43
56
  constructor(
44
- private logger: Logger,
57
+ private telemetry: ProtonDriveTelemetry,
45
58
  private apiService: NodeAPIService,
46
59
  private cache: NodesCache,
47
60
  private cryptoCache: NodesCryptoCache,
48
61
  private cryptoService: NodesCryptoService,
49
- private shareService: SharesService,
62
+ private shareService: Pick<
63
+ SharesService,
64
+ 'getOwnVolumeIDs' | 'getSharePrivateKey' | 'getContextShareMemberEmailKey'
65
+ >,
50
66
  ) {
51
- this.logger = logger;
67
+ this.logger = telemetry.getLogger('nodes');
52
68
  this.apiService = apiService;
53
69
  this.cache = cache;
54
70
  this.cryptoCache = cryptoCache;
55
71
  this.cryptoService = cryptoService;
56
72
  this.shareService = shareService;
73
+ this.debouncer = new NodesDebouncer(this.telemetry);
57
74
  }
58
75
 
59
76
  async getVolumeRootFolder() {
@@ -65,6 +82,7 @@ export class NodesAccess {
65
82
  async getNode(nodeUid: string): Promise<DecryptedNode> {
66
83
  let cachedNode;
67
84
  try {
85
+ await this.debouncer.waitForLoadingNode(nodeUid);
68
86
  cachedNode = await this.cache.getNode(nodeUid);
69
87
  } catch {}
70
88
 
@@ -112,6 +130,7 @@ export class NodesAccess {
112
130
  for await (const nodeUid of this.apiService.iterateChildrenNodeUids(parentNode.uid, onlyFolders, signal)) {
113
131
  let node;
114
132
  try {
133
+ await this.debouncer.waitForLoadingNode(nodeUid);
115
134
  node = await this.cache.getNode(nodeUid);
116
135
  } catch {}
117
136
 
@@ -143,6 +162,7 @@ export class NodesAccess {
143
162
  for await (const nodeUid of this.apiService.iterateTrashedNodeUids(volumeId, signal)) {
144
163
  let node;
145
164
  try {
165
+ await this.debouncer.waitForLoadingNode(nodeUid);
146
166
  node = await this.cache.getNode(nodeUid);
147
167
  } catch {}
148
168
 
@@ -208,9 +228,14 @@ export class NodesAccess {
208
228
  }
209
229
 
210
230
  private async loadNode(nodeUid: string): Promise<{ node: DecryptedNode; keys?: DecryptedNodeKeys }> {
211
- const { volumeId: ownVolumeId } = await this.shareService.getOwnVolumeIDs();
212
- const encryptedNode = await this.apiService.getNode(nodeUid, ownVolumeId);
213
- return this.decryptNode(encryptedNode);
231
+ this.debouncer.loadingNode(nodeUid);
232
+ try {
233
+ const { volumeId: ownVolumeId } = await this.shareService.getOwnVolumeIDs();
234
+ const encryptedNode = await this.apiService.getNode(nodeUid, ownVolumeId);
235
+ return this.decryptNode(encryptedNode);
236
+ } finally {
237
+ this.debouncer.finishedLoadingNode(nodeUid);
238
+ }
214
239
  }
215
240
 
216
241
  private async *loadNodes(
@@ -236,7 +261,14 @@ export class NodesAccess {
236
261
 
237
262
  const { volumeId: ownVolumeId } = await this.shareService.getOwnVolumeIDs();
238
263
 
239
- const encryptedNodesIterator = this.apiService.iterateNodes(nodeUids, ownVolumeId, filterOptions, signal);
264
+ const apiNodesIterator = this.apiService.iterateNodes(nodeUids, ownVolumeId, filterOptions, signal);
265
+
266
+ const debouncedNodeMapper = async (encryptedNode: EncryptedNode): Promise<EncryptedNode> => {
267
+ this.debouncer.loadingNode(encryptedNode.uid);
268
+ return encryptedNode;
269
+ };
270
+ const encryptedNodesIterator = asyncIteratorMap(apiNodesIterator, debouncedNodeMapper, 1);
271
+
240
272
  const decryptNodeMapper = async (encryptedNode: EncryptedNode): Promise<Result<DecryptedNode, unknown>> => {
241
273
  returnedNodeUids.push(encryptedNode.uid);
242
274
  try {
@@ -250,6 +282,7 @@ export class NodesAccess {
250
282
  encryptedNodesIterator,
251
283
  decryptNodeMapper,
252
284
  DECRYPTION_CONCURRENCY,
285
+ signal,
253
286
  );
254
287
  for await (const node of decryptedNodesIterator) {
255
288
  if (node.ok) {
@@ -329,11 +362,12 @@ export class NodesAccess {
329
362
  this.logger.error(`Failed to cache node keys ${node.uid}`, error);
330
363
  }
331
364
  }
365
+ this.debouncer.finishedLoadingNode(node.uid);
332
366
  return { node, keys };
333
367
  }
334
368
 
335
369
  async getParentKeys(
336
- node: Pick<DecryptedNode, 'parentUid' | 'shareId'>,
370
+ node: Pick<DecryptedNode, 'uid' | 'parentUid' | 'shareId'>,
337
371
  ): Promise<Pick<DecryptedNodeKeys, 'key' | 'hashKey'>> {
338
372
  if (node.parentUid) {
339
373
  try {
@@ -360,6 +394,7 @@ export class NodesAccess {
360
394
 
361
395
  async getNodeKeys(nodeUid: string): Promise<DecryptedNodeKeys> {
362
396
  try {
397
+ await this.debouncer.waitForLoadingNode(nodeUid);
363
398
  return await this.cryptoCache.getNodeKeys(nodeUid);
364
399
  } catch {
365
400
  const { keys } = await this.loadNode(nodeUid);
@@ -50,7 +50,7 @@ describe('NodesManagement', () => {
50
50
  apiService = {
51
51
  renameNode: jest.fn(),
52
52
  moveNode: jest.fn(),
53
- copyNode: jest.fn(),
53
+ copyNode: jest.fn().mockResolvedValue('newCopiedNodeUid'),
54
54
  trashNodes: jest.fn(async function* (uids) {
55
55
  yield* uids.map((uid) => ({ ok: true, uid }) as NodeResult);
56
56
  }),
@@ -251,6 +251,7 @@ describe('NodesManagement', () => {
251
251
 
252
252
  expect(newNode).toEqual({
253
253
  ...nodes.nodeUid,
254
+ uid: 'newCopiedNodeUid',
254
255
  parentUid: 'newParentNodeUid',
255
256
  encryptedName: 'copiedArmoredNodeName',
256
257
  hash: 'copiedHash',
@@ -307,6 +308,7 @@ describe('NodesManagement', () => {
307
308
  );
308
309
  expect(newNode).toEqual({
309
310
  ...nodes.anonymousNodeUid,
311
+ uid: 'newCopiedNodeUid',
310
312
  parentUid: 'newParentNodeUid',
311
313
  encryptedName: 'copiedArmoredNodeName',
312
314
  hash: 'copiedHash',
@@ -1,6 +1,6 @@
1
1
  import { c } from 'ttag';
2
2
 
3
- import { MemberRole, NodeType, NodeResult, resultOk } from '../../interface';
3
+ import { MemberRole, NodeType, NodeResult, NodeResultWithNewUid, resultOk } from '../../interface';
4
4
  import { AbortError, ValidationError } from '../../errors';
5
5
  import { getErrorMessage } from '../errors';
6
6
  import { splitNodeUid } from '../uids';
@@ -182,16 +182,21 @@ export class NodesManagement {
182
182
  return newNode;
183
183
  }
184
184
 
185
- // Improvement requested: copy nodes in parallel
186
- async *copyNodes(nodeUids: string[], newParentNodeUid: string, signal?: AbortSignal): AsyncGenerator<NodeResult> {
185
+ // Improvement requested: copy nodes in parallel using copy_multiple endpoint
186
+ async *copyNodes(
187
+ nodeUids: string[],
188
+ newParentNodeUid: string,
189
+ signal?: AbortSignal,
190
+ ): AsyncGenerator<NodeResultWithNewUid> {
187
191
  for (const nodeUid of nodeUids) {
188
192
  if (signal?.aborted) {
189
193
  throw new AbortError(c('Error').t`Copy operation aborted`);
190
194
  }
191
195
  try {
192
- await this.copyNode(nodeUid, newParentNodeUid);
196
+ const { uid: newNodeUid } = await this.copyNode(nodeUid, newParentNodeUid);
193
197
  yield {
194
198
  uid: nodeUid,
199
+ newUid: newNodeUid,
195
200
  ok: true,
196
201
  };
197
202
  } catch (error: unknown) {
@@ -237,7 +242,7 @@ export class NodesManagement {
237
242
  signatureEmail: encryptedCrypto.signatureEmail,
238
243
  armoredNodePassphraseSignature: encryptedCrypto.armoredNodePassphraseSignature,
239
244
  };
240
- await this.apiService.copyNode(nodeUid, {
245
+ const newNodeUid = await this.apiService.copyNode(nodeUid, {
241
246
  ...keySignatureProperties,
242
247
  parentUid: newParentUid,
243
248
  armoredNodePassphrase: encryptedCrypto.armoredNodePassphrase,
@@ -247,6 +252,7 @@ export class NodesManagement {
247
252
  });
248
253
  const newNode: DecryptedNode = {
249
254
  ...node,
255
+ uid: newNodeUid,
250
256
  encryptedName: encryptedCrypto.encryptedName,
251
257
  parentUid: newParentUid,
252
258
  hash: encryptedCrypto.hash,
@@ -1,12 +1,9 @@
1
- import { c } from 'ttag';
2
-
3
- import { DriveAPIService, drivePaths, NotFoundAPIError } from '../apiService';
1
+ import { DriveAPIService, drivePaths } from '../apiService';
4
2
  import { EncryptedRootShare, EncryptedShareCrypto, ShareType } from '../shares/interface';
5
3
  import { makeNodeUid } from '../uids';
6
4
 
7
- type GetVolumesResponse = drivePaths['/drive/volumes']['get']['responses']['200']['content']['application/json'];
8
-
9
- type GetShareResponse = drivePaths['/drive/shares/{shareID}']['get']['responses']['200']['content']['application/json'];
5
+ type GetPhotoShareResponse =
6
+ drivePaths['/drive/v2/shares/photos']['get']['responses']['200']['content']['application/json'];
10
7
 
11
8
  type PostCreateVolumeRequest = Extract<
12
9
  drivePaths['/drive/photos/volumes']['post']['requestBody'],
@@ -34,33 +31,19 @@ export class PhotosAPIService {
34
31
  }
35
32
 
36
33
  async getPhotoShare(): Promise<EncryptedRootShare> {
37
- // TODO: Switch to drive/v2/shares/photos once available.
38
-
39
- const volumesResponse = await this.apiService.get<GetVolumesResponse>('drive/volumes');
40
-
41
- const photoVolume = volumesResponse.Volumes.find((volume) => volume.Type === 2);
42
-
43
- if (!photoVolume) {
44
- throw new NotFoundAPIError(c('Error').t`Photo volume not found`);
45
- }
46
-
47
- const response = await this.apiService.get<GetShareResponse>(`drive/shares/${photoVolume.Share.ShareID}`);
48
-
49
- if (!response.AddressID) {
50
- throw new Error('Photo root share has not address ID set');
51
- }
34
+ const response = await this.apiService.get<GetPhotoShareResponse>('drive/v2/shares/photos');
52
35
 
53
36
  return {
54
- volumeId: response.VolumeID,
55
- shareId: response.ShareID,
56
- rootNodeId: response.LinkID,
57
- creatorEmail: response.Creator,
37
+ volumeId: response.Volume.VolumeID,
38
+ shareId: response.Share.ShareID,
39
+ rootNodeId: response.Link.Link.LinkID,
40
+ creatorEmail: response.Share.CreatorEmail,
58
41
  encryptedCrypto: {
59
- armoredKey: response.Key,
60
- armoredPassphrase: response.Passphrase,
61
- armoredPassphraseSignature: response.PassphraseSignature,
42
+ armoredKey: response.Share.Key,
43
+ armoredPassphrase: response.Share.Passphrase,
44
+ armoredPassphraseSignature: response.Share.PassphraseSignature,
62
45
  },
63
- addressId: response.AddressID,
46
+ addressId: response.Share.AddressID,
64
47
  type: ShareType.Photo,
65
48
  };
66
49
  }
@@ -5,6 +5,7 @@ import { generateFileExtendedAttributes } from '../nodes';
5
5
  import { splitNodeRevisionUid } from '../uids';
6
6
  import { UploadAPIService } from '../upload/apiService';
7
7
  import { BlockVerifier } from '../upload/blockVerifier';
8
+ import { UploadController } from '../upload/controller';
8
9
  import { UploadCryptoService } from '../upload/cryptoService';
9
10
  import { FileUploader } from '../upload/fileUploader';
10
11
  import { NodeRevisionDraft, NodesService } from '../upload/interface';
@@ -62,6 +63,7 @@ export class PhotoFileUploader extends FileUploader {
62
63
  revisionDraft,
63
64
  this.photoMetadata,
64
65
  onFinish,
66
+ this.controller,
65
67
  this.signal,
66
68
  );
67
69
  }
@@ -80,9 +82,28 @@ export class PhotoStreamUploader extends StreamUploader {
80
82
  revisionDraft: NodeRevisionDraft,
81
83
  metadata: PhotoUploadMetadata,
82
84
  onFinish: (failure: boolean) => Promise<void>,
85
+ controller: UploadController,
83
86
  signal?: AbortSignal,
84
87
  ) {
85
- super(telemetry, apiService, cryptoService, uploadManager, blockVerifier, revisionDraft, metadata, onFinish, signal);
88
+ const abortController = new AbortController();
89
+ if (signal) {
90
+ signal.addEventListener('abort', () => {
91
+ abortController.abort();
92
+ });
93
+ }
94
+
95
+ super(
96
+ telemetry,
97
+ apiService,
98
+ cryptoService,
99
+ uploadManager,
100
+ blockVerifier,
101
+ revisionDraft,
102
+ metadata,
103
+ onFinish,
104
+ controller,
105
+ abortController,
106
+ );
86
107
  this.photoUploadManager = uploadManager;
87
108
  this.photoMetadata = metadata;
88
109
  }
@@ -347,8 +347,8 @@ export class SharingAPIService {
347
347
  return response.Share.ID;
348
348
  }
349
349
 
350
- async deleteShare(shareId: string): Promise<void> {
351
- await this.apiService.delete(`drive/shares/${shareId}?Force=1`);
350
+ async deleteShare(shareId: string, force: boolean = false): Promise<void> {
351
+ await this.apiService.delete(`drive/shares/${shareId}?Force=${force ? 1 : 0}`);
352
352
  }
353
353
 
354
354
  async inviteProtonUser(
@@ -286,7 +286,7 @@ export class SharingManagement {
286
286
 
287
287
  if (!settings) {
288
288
  this.logger.info(`Unsharing node ${nodeUid}`);
289
- await this.deleteShare(currentSharing.share.shareId, nodeUid);
289
+ await this.deleteShareWithForce(currentSharing.share.shareId, nodeUid);
290
290
  return;
291
291
  }
292
292
 
@@ -348,7 +348,7 @@ export class SharingManagement {
348
348
  // update local state immediately.
349
349
  this.logger.info(`Deleting share ${currentSharing.share.shareId} for node ${nodeUid}`);
350
350
  try {
351
- await this.deleteShare(currentSharing.share.shareId, nodeUid);
351
+ await this.deleteShareWithForce(currentSharing.share.shareId, nodeUid);
352
352
  } catch (error: unknown) {
353
353
  // If deleting the share fails, we don't want to throw an error
354
354
  // as it might be a race condition that other client updated
@@ -433,8 +433,11 @@ export class SharingManagement {
433
433
  };
434
434
  }
435
435
 
436
- private async deleteShare(shareId: string, nodeUid: string): Promise<void> {
437
- await this.apiService.deleteShare(shareId);
436
+ /**
437
+ * Deletes the share even if it is not empty.
438
+ */
439
+ private async deleteShareWithForce(shareId: string, nodeUid: string): Promise<void> {
440
+ await this.apiService.deleteShare(shareId, true);
438
441
  await this.nodesService.notifyNodeChanged(nodeUid);
439
442
  if (await this.cache.hasSharedByMeNodeUidsLoaded()) {
440
443
  await this.cache.removeSharedByMeNodeUid(nodeUid);
@@ -1,175 +1,38 @@
1
- import { DriveAPIService, drivePaths, nodeTypeNumberToNodeType } from '../apiService';
2
- import { Logger, MemberRole } from '../../interface';
3
- import { makeNodeUid, splitNodeUid } from '../uids';
4
- import { EncryptedShareCrypto, EncryptedNode } from './interface';
1
+ import { DriveAPIService, drivePaths } from '../apiService';
5
2
 
6
- const PAGE_SIZE = 50;
7
-
8
- type GetTokenInfoResponse = drivePaths['/drive/urls/{token}']['get']['responses']['200']['content']['application/json'];
9
-
10
- type GetTokenFolderChildrenResponse =
11
- drivePaths['/drive/urls/{token}/folders/{linkID}/children']['get']['responses']['200']['content']['application/json'];
3
+ type PostTokenInfoRequest = Extract<
4
+ drivePaths['/drive/v2/urls/{token}/bookmark']['post']['requestBody'],
5
+ { content: object }
6
+ >['content']['application/json'];
7
+ type PostTokenInfoResponse =
8
+ drivePaths['/drive/v2/urls/{token}/bookmark']['post']['responses']['200']['content']['application/json'];
12
9
 
13
10
  /**
14
- * Provides API communication for accessing public link data.
11
+ * Provides API communication for actions on the public link.
15
12
  *
16
13
  * The service is responsible for transforming local objects to API payloads
17
14
  * and vice versa. It should not contain any business logic.
18
15
  */
19
16
  export class SharingPublicAPIService {
20
- constructor(
21
- private logger: Logger,
22
- private apiService: DriveAPIService,
23
- ) {
24
- this.logger = logger;
17
+ constructor(private apiService: DriveAPIService) {
25
18
  this.apiService = apiService;
26
19
  }
27
20
 
28
- async getPublicLinkRoot(token: string): Promise<{
29
- encryptedNode: EncryptedNode;
30
- encryptedShare: EncryptedShareCrypto;
31
- }> {
32
- const response = await this.apiService.get<GetTokenInfoResponse>(`drive/urls/${token}`);
33
- const encryptedNode = tokenToEncryptedNode(this.logger, response.Token);
34
-
35
- return {
36
- encryptedNode: encryptedNode,
37
- encryptedShare: {
38
- base64UrlPasswordSalt: response.Token.SharePasswordSalt,
39
- armoredKey: response.Token.ShareKey,
40
- armoredPassphrase: response.Token.SharePassphrase,
41
- },
42
- };
43
- }
44
-
45
- async *iterateFolderChildren(parentUid: string, signal?: AbortSignal): AsyncGenerator<EncryptedNode> {
46
- const { volumeId: token, nodeId } = splitNodeUid(parentUid);
47
-
48
- let page = 0;
49
- while (true) {
50
- const response = await this.apiService.get<GetTokenFolderChildrenResponse>(
51
- `drive/urls/${token}/folders/${nodeId}/children?Page=${page}&PageSize=${PAGE_SIZE}`,
52
- signal,
53
- );
54
-
55
- for (const link of response.Links) {
56
- yield linkToEncryptedNode(this.logger, token, link);
57
- }
58
-
59
- if (response.Links.length < PAGE_SIZE) {
60
- break;
61
- }
62
- page++;
63
- }
64
- }
65
- }
66
-
67
- function tokenToEncryptedNode(logger: Logger, token: GetTokenInfoResponse['Token']): EncryptedNode {
68
- const baseNodeMetadata = {
69
- // Internal metadata
70
- encryptedName: token.Name,
71
-
72
- // Basic node metadata
73
- uid: makeNodeUid(token.Token, token.LinkID),
74
- parentUid: undefined,
75
- type: nodeTypeNumberToNodeType(logger, token.LinkType),
76
- creationTime: new Date(), // TODO
77
-
78
- isShared: false,
79
- isSharedPublicly: false,
80
- directRole: MemberRole.Viewer, // TODO
81
- };
82
-
83
- const baseCryptoNodeMetadata = {
84
- signatureEmail: token.SignatureEmail || undefined,
85
- armoredKey: token.NodeKey,
86
- armoredNodePassphrase: token.NodePassphrase,
87
- armoredNodePassphraseSignature: token.NodePassphraseSignature || undefined,
88
- };
89
-
90
- if (token.LinkType === 1 && token.NodeHashKey) {
91
- return {
92
- ...baseNodeMetadata,
93
- encryptedCrypto: {
94
- ...baseCryptoNodeMetadata,
95
- folder: {
96
- armoredHashKey: token.NodeHashKey as string,
21
+ async bookmarkPublicLink(bookmark: {
22
+ token: string;
23
+ encryptedUrlPassword: string;
24
+ addressId: string;
25
+ addressKeyId: string;
26
+ }): Promise<void> {
27
+ await this.apiService.post<PostTokenInfoRequest, PostTokenInfoResponse>(
28
+ `drive/v2/urls/${bookmark.token}/bookmark`,
29
+ {
30
+ BookmarkShareURL: {
31
+ EncryptedUrlPassword: bookmark.encryptedUrlPassword,
32
+ AddressID: bookmark.addressId,
33
+ AddressKeyID: bookmark.addressKeyId,
97
34
  },
98
35
  },
99
- };
36
+ );
100
37
  }
101
-
102
- if (token.LinkType === 2 && token.ContentKeyPacket) {
103
- return {
104
- ...baseNodeMetadata,
105
- totalStorageSize: token.Size || undefined,
106
- mediaType: token.MIMEType || undefined,
107
- encryptedCrypto: {
108
- ...baseCryptoNodeMetadata,
109
- file: {
110
- base64ContentKeyPacket: token.ContentKeyPacket,
111
- },
112
- },
113
- };
114
- }
115
-
116
- throw new Error(`Unknown node type: ${token.LinkType}`);
117
- }
118
-
119
- function linkToEncryptedNode(
120
- logger: Logger,
121
- token: string,
122
- link: GetTokenFolderChildrenResponse['Links'][0],
123
- ): EncryptedNode {
124
- const baseNodeMetadata = {
125
- // Internal metadata
126
- hash: link.Hash || undefined,
127
- encryptedName: link.Name,
128
-
129
- // Basic node metadata
130
- uid: makeNodeUid(token, link.LinkID),
131
- parentUid: link.ParentLinkID ? makeNodeUid(token, link.ParentLinkID) : undefined,
132
- type: nodeTypeNumberToNodeType(logger, link.Type),
133
- creationTime: new Date(), // TODO
134
- totalStorageSize: link.TotalSize,
135
-
136
- isShared: false,
137
- isSharedPublicly: false,
138
- directRole: MemberRole.Viewer, // TODO
139
- };
140
-
141
- const baseCryptoNodeMetadata = {
142
- signatureEmail: link.SignatureEmail || undefined,
143
- armoredKey: link.NodeKey,
144
- armoredNodePassphrase: link.NodePassphrase,
145
- armoredNodePassphraseSignature: link.NodePassphraseSignature || undefined,
146
- };
147
-
148
- if (link.Type === 1 && link.FolderProperties) {
149
- return {
150
- ...baseNodeMetadata,
151
- encryptedCrypto: {
152
- ...baseCryptoNodeMetadata,
153
- folder: {
154
- armoredHashKey: link.FolderProperties.NodeHashKey as string,
155
- },
156
- },
157
- };
158
- }
159
-
160
- if (link.Type === 2 && link.FileProperties?.ContentKeyPacket) {
161
- return {
162
- ...baseNodeMetadata,
163
- totalStorageSize: link.FileProperties.ActiveRevision?.Size || undefined,
164
- mediaType: link.MIMEType || undefined,
165
- encryptedCrypto: {
166
- ...baseCryptoNodeMetadata,
167
- file: {
168
- base64ContentKeyPacket: link.FileProperties.ContentKeyPacket,
169
- },
170
- },
171
- };
172
- }
173
-
174
- throw new Error(`Unknown node type: ${link.Type}`);
175
38
  }
@@ -1,6 +1,6 @@
1
1
  import { c } from 'ttag';
2
2
 
3
- import { DriveCrypto, PrivateKey, VERIFICATION_STATUS } from '../../crypto';
3
+ import { VERIFICATION_STATUS } from '../../crypto';
4
4
  import { getVerificationMessage } from '../errors';
5
5
  import {
6
6
  resultOk,
@@ -12,34 +12,9 @@ import {
12
12
  MetricVolumeType,
13
13
  MetricsDecryptionErrorField,
14
14
  Logger,
15
- ProtonDriveAccount,
16
15
  } from '../../interface';
17
- import { NodesCryptoService } from '../nodes/cryptoService';
18
- import { EncryptedShareCrypto } from './interface';
19
16
 
20
- export class SharingPublicCryptoService extends NodesCryptoService {
21
- constructor(
22
- telemetry: ProtonDriveTelemetry,
23
- driveCrypto: DriveCrypto,
24
- account: ProtonDriveAccount,
25
- private password: string,
26
- ) {
27
- super(telemetry, driveCrypto, account, new SharingPublicCryptoReporter(telemetry));
28
- this.password = password;
29
- }
30
-
31
- async decryptPublicLinkShareKey(encryptedShare: EncryptedShareCrypto): Promise<PrivateKey> {
32
- const { key: shareKey } = await this.driveCrypto.decryptKeyWithSrpPassword(
33
- this.password,
34
- encryptedShare.base64UrlPasswordSalt,
35
- encryptedShare.armoredKey,
36
- encryptedShare.armoredPassphrase,
37
- );
38
- return shareKey;
39
- }
40
- }
41
-
42
- class SharingPublicCryptoReporter {
17
+ export class SharingPublicCryptoReporter {
43
18
  private logger: Logger;
44
19
  private telemetry: ProtonDriveTelemetry;
45
20