@protontech/drive-sdk 0.6.1 → 0.7.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 (119) hide show
  1. package/dist/featureFlags.d.ts +7 -0
  2. package/dist/featureFlags.js +14 -0
  3. package/dist/featureFlags.js.map +1 -0
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +3 -1
  6. package/dist/index.js.map +1 -1
  7. package/dist/interface/featureFlags.d.ts +7 -0
  8. package/dist/interface/featureFlags.js +3 -0
  9. package/dist/interface/featureFlags.js.map +1 -0
  10. package/dist/interface/index.d.ts +3 -0
  11. package/dist/interface/index.js.map +1 -1
  12. package/dist/internal/apiService/apiService.d.ts +2 -2
  13. package/dist/internal/apiService/apiService.js.map +1 -1
  14. package/dist/internal/apiService/errors.js +4 -3
  15. package/dist/internal/apiService/errors.js.map +1 -1
  16. package/dist/internal/download/cryptoService.js +8 -6
  17. package/dist/internal/download/cryptoService.js.map +1 -1
  18. package/dist/internal/download/fileDownloader.d.ts +2 -1
  19. package/dist/internal/download/fileDownloader.js +6 -3
  20. package/dist/internal/download/fileDownloader.js.map +1 -1
  21. package/dist/internal/download/index.d.ts +1 -1
  22. package/dist/internal/download/index.js +3 -3
  23. package/dist/internal/download/index.js.map +1 -1
  24. package/dist/internal/nodes/apiService.d.ts +9 -8
  25. package/dist/internal/nodes/apiService.js +14 -5
  26. package/dist/internal/nodes/apiService.js.map +1 -1
  27. package/dist/internal/nodes/apiService.test.js +5 -5
  28. package/dist/internal/nodes/apiService.test.js.map +1 -1
  29. package/dist/internal/nodes/cryptoReporter.d.ts +3 -3
  30. package/dist/internal/nodes/cryptoReporter.js.map +1 -1
  31. package/dist/internal/nodes/cryptoService.d.ts +12 -21
  32. package/dist/internal/nodes/cryptoService.js +45 -14
  33. package/dist/internal/nodes/cryptoService.js.map +1 -1
  34. package/dist/internal/nodes/cryptoService.test.js +262 -17
  35. package/dist/internal/nodes/cryptoService.test.js.map +1 -1
  36. package/dist/internal/nodes/interface.d.ts +13 -3
  37. package/dist/internal/nodes/nodesAccess.d.ts +8 -1
  38. package/dist/internal/nodes/nodesAccess.js +13 -0
  39. package/dist/internal/nodes/nodesAccess.js.map +1 -1
  40. package/dist/internal/nodes/nodesManagement.d.ts +4 -4
  41. package/dist/internal/nodes/nodesManagement.js +16 -20
  42. package/dist/internal/nodes/nodesManagement.js.map +1 -1
  43. package/dist/internal/nodes/nodesManagement.test.js +21 -10
  44. package/dist/internal/nodes/nodesManagement.test.js.map +1 -1
  45. package/dist/internal/photos/upload.d.ts +2 -2
  46. package/dist/internal/photos/upload.js +1 -1
  47. package/dist/internal/photos/upload.js.map +1 -1
  48. package/dist/internal/sharingPublic/index.d.ts +6 -6
  49. package/dist/internal/sharingPublic/index.js +8 -7
  50. package/dist/internal/sharingPublic/index.js.map +1 -1
  51. package/dist/internal/sharingPublic/nodes.d.ts +16 -3
  52. package/dist/internal/sharingPublic/nodes.js +34 -2
  53. package/dist/internal/sharingPublic/nodes.js.map +1 -1
  54. package/dist/internal/sharingPublic/unauthApiService.d.ts +17 -0
  55. package/dist/internal/sharingPublic/unauthApiService.js +31 -0
  56. package/dist/internal/sharingPublic/unauthApiService.js.map +1 -0
  57. package/dist/internal/sharingPublic/unauthApiService.test.d.ts +1 -0
  58. package/dist/internal/sharingPublic/unauthApiService.test.js +27 -0
  59. package/dist/internal/sharingPublic/unauthApiService.test.js.map +1 -0
  60. package/dist/internal/upload/apiService.d.ts +4 -3
  61. package/dist/internal/upload/apiService.js.map +1 -1
  62. package/dist/internal/upload/cryptoService.d.ts +8 -3
  63. package/dist/internal/upload/cryptoService.js +45 -9
  64. package/dist/internal/upload/cryptoService.js.map +1 -1
  65. package/dist/internal/upload/fileUploader.d.ts +5 -2
  66. package/dist/internal/upload/fileUploader.js +7 -4
  67. package/dist/internal/upload/fileUploader.js.map +1 -1
  68. package/dist/internal/upload/fileUploader.test.js +1 -1
  69. package/dist/internal/upload/fileUploader.test.js.map +1 -1
  70. package/dist/internal/upload/interface.d.ts +25 -13
  71. package/dist/internal/upload/manager.js +7 -4
  72. package/dist/internal/upload/manager.js.map +1 -1
  73. package/dist/internal/upload/manager.test.js +5 -4
  74. package/dist/internal/upload/manager.test.js.map +1 -1
  75. package/dist/internal/upload/streamUploader.js +9 -4
  76. package/dist/internal/upload/streamUploader.js.map +1 -1
  77. package/dist/internal/upload/streamUploader.test.js +8 -5
  78. package/dist/internal/upload/streamUploader.test.js.map +1 -1
  79. package/dist/protonDriveClient.d.ts +2 -2
  80. package/dist/protonDriveClient.js +7 -2
  81. package/dist/protonDriveClient.js.map +1 -1
  82. package/dist/protonDrivePublicLinkClient.d.ts +2 -1
  83. package/dist/protonDrivePublicLinkClient.js +7 -5
  84. package/dist/protonDrivePublicLinkClient.js.map +1 -1
  85. package/package.json +1 -1
  86. package/src/featureFlags.ts +11 -0
  87. package/src/index.ts +1 -0
  88. package/src/interface/featureFlags.ts +7 -0
  89. package/src/interface/index.ts +3 -0
  90. package/src/internal/apiService/apiService.ts +2 -2
  91. package/src/internal/apiService/errors.ts +5 -4
  92. package/src/internal/download/cryptoService.ts +13 -6
  93. package/src/internal/download/fileDownloader.ts +4 -2
  94. package/src/internal/download/index.ts +3 -0
  95. package/src/internal/nodes/apiService.test.ts +5 -5
  96. package/src/internal/nodes/apiService.ts +23 -10
  97. package/src/internal/nodes/cryptoReporter.ts +3 -3
  98. package/src/internal/nodes/cryptoService.test.ts +370 -18
  99. package/src/internal/nodes/cryptoService.ts +73 -32
  100. package/src/internal/nodes/interface.ts +16 -2
  101. package/src/internal/nodes/nodesAccess.ts +17 -0
  102. package/src/internal/nodes/nodesManagement.test.ts +21 -10
  103. package/src/internal/nodes/nodesManagement.ts +20 -24
  104. package/src/internal/photos/upload.ts +3 -3
  105. package/src/internal/sharingPublic/index.ts +7 -3
  106. package/src/internal/sharingPublic/nodes.ts +43 -2
  107. package/src/internal/sharingPublic/unauthApiService.test.ts +29 -0
  108. package/src/internal/sharingPublic/unauthApiService.ts +32 -0
  109. package/src/internal/upload/apiService.ts +4 -3
  110. package/src/internal/upload/cryptoService.ts +73 -12
  111. package/src/internal/upload/fileUploader.test.ts +1 -1
  112. package/src/internal/upload/fileUploader.ts +18 -11
  113. package/src/internal/upload/interface.ts +24 -13
  114. package/src/internal/upload/manager.test.ts +5 -4
  115. package/src/internal/upload/manager.ts +7 -4
  116. package/src/internal/upload/streamUploader.test.ts +8 -5
  117. package/src/internal/upload/streamUploader.ts +10 -4
  118. package/src/protonDriveClient.ts +12 -2
  119. package/src/protonDrivePublicLinkClient.ts +8 -3
@@ -3,6 +3,7 @@ import { OpenPGPCrypto, PrivateKey, SessionKey, SRPModule } from '../crypto';
3
3
  import { LatestEventIdProvider } from '../internal/events/interface';
4
4
  import { ProtonDriveAccount } from './account';
5
5
  import { ProtonDriveConfig } from './config';
6
+ import { FeatureFlagProvider } from './featureFlags';
6
7
  import { ProtonDriveHTTPClient } from './httpClient';
7
8
  import { Telemetry, MetricEvent } from './telemetry';
8
9
 
@@ -12,6 +13,7 @@ export type { ProtonDriveAccount, ProtonDriveAccountAddress } from './account';
12
13
  export type { Author, UnverifiedAuthorError, AnonymousUser } from './author';
13
14
  export type { ProtonDriveConfig } from './config';
14
15
  export type { Device, DeviceOrUid } from './devices';
16
+ export type { FeatureFlagProvider } from './featureFlags';
15
17
  export { DeviceType } from './devices';
16
18
  export type { FileDownloader, DownloadController, SeekableReadableStream } from './download';
17
19
  export type {
@@ -117,5 +119,6 @@ export interface ProtonDriveClientContructorParameters {
117
119
  srpModule: SRPModule;
118
120
  config?: ProtonDriveConfig;
119
121
  telemetry?: ProtonDriveTelemetry;
122
+ featureFlagProvider?: FeatureFlagProvider;
120
123
  latestEventIdProvider?: LatestEventIdProvider;
121
124
  }
@@ -136,7 +136,7 @@ export class DriveAPIService {
136
136
  return this.makeRequest(url, 'DELETE', undefined, signal);
137
137
  }
138
138
 
139
- private async makeRequest<RequestPayload, ResponsePayload>(
139
+ protected async makeRequest<RequestPayload, ResponsePayload>(
140
140
  url: string,
141
141
  method = 'GET',
142
142
  data?: RequestPayload,
@@ -194,7 +194,7 @@ export class DriveAPIService {
194
194
  await this.makeStorageRequest('POST', baseUrl, token, data, onProgress, signal);
195
195
  }
196
196
 
197
- private async makeStorageRequest(
197
+ protected async makeStorageRequest(
198
198
  method: 'GET' | 'POST',
199
199
  url: string,
200
200
  token: string,
@@ -30,7 +30,7 @@ export function apiErrorFactory({
30
30
  Code?: number;
31
31
  Error?: string;
32
32
  Details?: object;
33
- exception?: string;
33
+ Exception?: string;
34
34
  message?: string;
35
35
  file?: string;
36
36
  line?: number;
@@ -43,9 +43,10 @@ export function apiErrorFactory({
43
43
  typedResult.Details,
44
44
  ];
45
45
 
46
- const debug = typedResult.exception
46
+ const debug = typedResult.Exception
47
47
  ? {
48
- exception: typedResult.exception,
48
+ details: typedResult.Details,
49
+ exception: typedResult.Exception,
49
50
  message: typedResult.message,
50
51
  file: typedResult.file,
51
52
  line: typedResult.line,
@@ -82,7 +83,7 @@ export function apiErrorFactory({
82
83
  case ErrorCode.INSUFFICIENT_BOOKMARKS_QUOTA:
83
84
  return new ValidationError(message, code, details);
84
85
  default:
85
- return new APICodeError(message, code, debug);
86
+ return new APICodeError(message, code, debug || details);
86
87
  }
87
88
  }
88
89
 
@@ -27,7 +27,7 @@ export class DownloadCryptoService {
27
27
  nodeKey: { key: PrivateKey; contentKeyPacketSessionKey: SessionKey },
28
28
  revision: Revision,
29
29
  ): Promise<RevisionKeys> {
30
- const verificationKeys = await this.getRevisionVerificationKeys(revision);
30
+ const verificationKeys = await this.getRevisionVerificationKeys(revision, nodeKey.key);
31
31
  return {
32
32
  ...nodeKey,
33
33
  verificationKeys,
@@ -90,23 +90,30 @@ export class DownloadCryptoService {
90
90
  allBlockHashes: Uint8Array[],
91
91
  armoredManifestSignature?: string,
92
92
  ): Promise<void> {
93
- const verificationKeys = (await this.getRevisionVerificationKeys(revision)) || nodeKey;
93
+ const verificationKeys = await this.getRevisionVerificationKeys(revision, nodeKey);
94
94
  const hash = mergeUint8Arrays(allBlockHashes);
95
95
 
96
96
  if (!armoredManifestSignature) {
97
97
  throw new IntegrityError(c('Error').t`Missing integrity signature`);
98
98
  }
99
99
 
100
- const { verified } = await this.driveCrypto.verifyManifest(hash, armoredManifestSignature, verificationKeys);
100
+ const { verified, verificationErrors } = await this.driveCrypto.verifyManifest(
101
+ hash,
102
+ armoredManifestSignature,
103
+ verificationKeys,
104
+ );
105
+
101
106
  if (verified !== VERIFICATION_STATUS.SIGNED_AND_VALID) {
102
- throw new IntegrityError(c('Error').t`Data integrity check failed`);
107
+ throw new IntegrityError(c('Error').t`Data integrity check failed`, {
108
+ verificationErrors,
109
+ });
103
110
  }
104
111
  }
105
112
 
106
- private async getRevisionVerificationKeys(revision: Revision): Promise<PublicKey[] | undefined> {
113
+ private async getRevisionVerificationKeys(revision: Revision, nodeKey: PrivateKey): Promise<PublicKey[]> {
107
114
  const signatureEmail = revision.contentAuthor.ok
108
115
  ? revision.contentAuthor.value
109
116
  : revision.contentAuthor.error.claimedAuthor;
110
- return signatureEmail ? await this.account.getPublicKeys(signatureEmail) : undefined;
117
+ return signatureEmail ? await this.account.getPublicKeys(signatureEmail) : [nodeKey];
111
118
  }
112
119
  }
@@ -42,6 +42,7 @@ export class FileDownloader {
42
42
  private revision: DecryptedRevision,
43
43
  private signal?: AbortSignal,
44
44
  private onFinish?: () => void,
45
+ private ignoreManifestVerification = false,
45
46
  ) {
46
47
  this.telemetry = telemetry;
47
48
  this.logger = telemetry.getLoggerForRevision(revision.uid);
@@ -51,6 +52,7 @@ export class FileDownloader {
51
52
  this.revision = revision;
52
53
  this.signal = signal;
53
54
  this.onFinish = onFinish;
55
+ this.ignoreManifestVerification = ignoreManifestVerification;
54
56
  this.controller = new DownloadController(this.signal);
55
57
  }
56
58
 
@@ -219,7 +221,7 @@ export class FileDownloader {
219
221
  throw new Error(`Some blocks were not downloaded`);
220
222
  }
221
223
 
222
- if (ignoreIntegrityErrors) {
224
+ if (ignoreIntegrityErrors || this.ignoreManifestVerification) {
223
225
  this.logger.warn('Skipping manifest check');
224
226
  } else {
225
227
  this.logger.debug(`Verifying manifest`);
@@ -237,7 +239,7 @@ export class FileDownloader {
237
239
  } catch (error: unknown) {
238
240
  this.logger.error(`Download failed`, error);
239
241
  void this.telemetry.downloadFailed(this.revision.uid, error, fileProgress, this.getClaimedSizeInBytes());
240
- await writer.abort();
242
+ await writer.abort?.();
241
243
  throw error;
242
244
  } finally {
243
245
  this.logger.debug(`Download cleanup`);
@@ -21,6 +21,7 @@ export function initDownloadModule(
21
21
  sharesService: SharesService,
22
22
  nodesService: NodesService,
23
23
  revisionsService: RevisionsService,
24
+ ignoreManifestVerification = false,
24
25
  ) {
25
26
  const queue = new DownloadQueue();
26
27
  const api = new DownloadAPIService(apiService);
@@ -63,6 +64,7 @@ export function initDownloadModule(
63
64
  node.activeRevision.value,
64
65
  signal,
65
66
  onFinish,
67
+ ignoreManifestVerification,
66
68
  );
67
69
  }
68
70
 
@@ -102,6 +104,7 @@ export function initDownloadModule(
102
104
  revision,
103
105
  signal,
104
106
  onFinish,
107
+ ignoreManifestVerification,
105
108
  );
106
109
  }
107
110
 
@@ -577,8 +577,8 @@ describe('nodeAPIService', () => {
577
577
  });
578
578
  });
579
579
 
580
- describe('deleteNodes', () => {
581
- it('should delete nodes', async () => {
580
+ describe('deleteTrashedNodes', () => {
581
+ it('should delete trashed nodes', async () => {
582
582
  // @ts-expect-error Mocking for testing purposes
583
583
  apiMock.post = jest.fn(async () =>
584
584
  Promise.resolve({
@@ -600,14 +600,14 @@ describe('nodeAPIService', () => {
600
600
  }),
601
601
  );
602
602
 
603
- const result = await Array.fromAsync(api.deleteNodes(['volumeId~nodeId1', 'volumeId~nodeId2']));
603
+ const result = await Array.fromAsync(api.deleteTrashedNodes(['volumeId~nodeId1', 'volumeId~nodeId2']));
604
604
  expect(result).toEqual([
605
605
  { uid: 'volumeId~nodeId1', ok: true },
606
606
  { uid: 'volumeId~nodeId2', ok: false, error: 'INSUFFICIENT_SCOPE' },
607
607
  ]);
608
608
  });
609
609
 
610
- it('should delete nodes from multiple volumes', async () => {
610
+ it('should delete trashed nodes from multiple volumes', async () => {
611
611
  // @ts-expect-error Mocking for testing purposes
612
612
  apiMock.post = jest.fn(async (_, { LinkIDs }) =>
613
613
  Promise.resolve({
@@ -620,7 +620,7 @@ describe('nodeAPIService', () => {
620
620
  }),
621
621
  );
622
622
 
623
- const result = await Array.fromAsync(api.deleteNodes(['volumeId1~nodeId1', 'volumeId2~nodeId2']));
623
+ const result = await Array.fromAsync(api.deleteTrashedNodes(['volumeId1~nodeId1', 'volumeId2~nodeId2']));
624
624
  expect(result).toEqual([
625
625
  { uid: 'volumeId1~nodeId1', ok: true },
626
626
  { uid: 'volumeId2~nodeId2', ok: true },
@@ -1,8 +1,7 @@
1
1
  import { c } from 'ttag';
2
2
 
3
3
  import { NodeWithSameNameExistsValidationError, ProtonDriveError, ValidationError } from '../../errors';
4
- import { Logger, NodeResult } from '../../interface';
5
- import { MemberRole, RevisionState } from '../../interface/nodes';
4
+ import { Logger, NodeResult, MemberRole, RevisionState, AnonymousUser } from '../../interface';
6
5
  import {
7
6
  DriveAPIService,
8
7
  drivePaths,
@@ -102,7 +101,6 @@ type PostRestoreRevisionResponse =
102
101
  type DeleteRevisionResponse =
103
102
  drivePaths['/drive/v2/volumes/{volumeID}/files/{linkID}/revisions/{revisionID}']['delete']['responses']['200']['content']['application/json'];
104
103
 
105
-
106
104
  type PostCheckAvailableHashesRequest = Extract<
107
105
  drivePaths['/drive/v2/volumes/{volumeID}/links/{linkID}/checkAvailableHashes']['post']['requestBody'],
108
106
  { content: object }
@@ -292,7 +290,7 @@ export class NodeAPIService {
292
290
  },
293
291
  newNode: {
294
292
  encryptedName: string;
295
- nameSignatureEmail: string;
293
+ nameSignatureEmail: string | AnonymousUser;
296
294
  hash?: string;
297
295
  },
298
296
  signal?: AbortSignal,
@@ -332,9 +330,9 @@ export class NodeAPIService {
332
330
  parentUid: string;
333
331
  armoredNodePassphrase: string;
334
332
  armoredNodePassphraseSignature?: string;
335
- signatureEmail?: string;
333
+ signatureEmail?: string | AnonymousUser;
336
334
  encryptedName: string;
337
- nameSignatureEmail?: string;
335
+ nameSignatureEmail?: string | AnonymousUser;
338
336
  hash: string;
339
337
  contentHash?: string;
340
338
  },
@@ -369,9 +367,9 @@ export class NodeAPIService {
369
367
  parentUid: string;
370
368
  armoredNodePassphrase: string;
371
369
  armoredNodePassphraseSignature?: string;
372
- signatureEmail?: string;
370
+ signatureEmail?: string | AnonymousUser;
373
371
  encryptedName: string;
374
- nameSignatureEmail?: string;
372
+ nameSignatureEmail?: string | AnonymousUser;
375
373
  hash: string;
376
374
  },
377
375
  signal?: AbortSignal,
@@ -430,7 +428,7 @@ export class NodeAPIService {
430
428
  }
431
429
  }
432
430
 
433
- async *deleteNodes(nodeUids: string[], signal?: AbortSignal): AsyncGenerator<NodeResult> {
431
+ async *deleteTrashedNodes(nodeUids: string[], signal?: AbortSignal): AsyncGenerator<NodeResult> {
434
432
  for (const { volumeId, batchNodeIds, batchNodeUids } of groupNodeUidsByVolumeAndIteratePerBatch(nodeUids)) {
435
433
  const response = await this.apiService.post<PostDeleteNodesRequest, PostDeleteNodesResponse>(
436
434
  `drive/v2/volumes/${volumeId}/trash/delete_multiple`,
@@ -445,6 +443,21 @@ export class NodeAPIService {
445
443
  }
446
444
  }
447
445
 
446
+ async *deleteExistingNodes(nodeUids: string[], signal?: AbortSignal): AsyncGenerator<NodeResult> {
447
+ for (const { volumeId, batchNodeIds, batchNodeUids } of groupNodeUidsByVolumeAndIteratePerBatch(nodeUids)) {
448
+ const response = await this.apiService.post<PostDeleteNodesRequest, PostDeleteNodesResponse>(
449
+ `drive/v2/volumes/${volumeId}/delete_multiple`,
450
+ {
451
+ LinkIDs: batchNodeIds,
452
+ },
453
+ signal,
454
+ );
455
+
456
+ // TODO: remove `as` when backend fixes OpenAPI schema.
457
+ yield* handleResponseErrors(batchNodeUids, volumeId, response.Responses as LinkResponse[]);
458
+ }
459
+ }
460
+
448
461
  async createFolder(
449
462
  parentUid: string,
450
463
  newNode: {
@@ -452,7 +465,7 @@ export class NodeAPIService {
452
465
  armoredHashKey: string;
453
466
  armoredNodePassphrase: string;
454
467
  armoredNodePassphraseSignature: string;
455
- signatureEmail: string;
468
+ signatureEmail: string | AnonymousUser;
456
469
  encryptedName: string;
457
470
  hash: string;
458
471
  armoredExtendedAttributes?: string;
@@ -34,7 +34,7 @@ export class NodesCryptoReporter {
34
34
  signatureType: string,
35
35
  verified: VERIFICATION_STATUS,
36
36
  verificationErrors?: Error[],
37
- claimedAuthor?: string,
37
+ claimedAuthor?: string | AnonymousUser,
38
38
  notAvailableVerificationKeys = false,
39
39
  ): Promise<Author> {
40
40
  const author = handleClaimedAuthor(
@@ -54,7 +54,7 @@ export class NodesCryptoReporter {
54
54
  node: { uid: string; creationTime: Date },
55
55
  field: MetricVerificationErrorField,
56
56
  verificationErrors?: Error[],
57
- claimedAuthor?: string,
57
+ claimedAuthor?: string | AnonymousUser,
58
58
  ) {
59
59
  if (this.reportedVerificationErrors.has(node.uid)) {
60
60
  return;
@@ -128,7 +128,7 @@ function handleClaimedAuthor(
128
128
  signatureType: string,
129
129
  verified: VERIFICATION_STATUS,
130
130
  verificationErrors?: Error[],
131
- claimedAuthor?: string,
131
+ claimedAuthor?: string | AnonymousUser,
132
132
  notAvailableVerificationKeys = false,
133
133
  ): Author {
134
134
  if (!claimedAuthor && notAvailableVerificationKeys) {