@protontech/drive-sdk 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/dist/diagnostic/diagnostic.d.ts +7 -4
  2. package/dist/diagnostic/diagnostic.js +16 -8
  3. package/dist/diagnostic/diagnostic.js.map +1 -1
  4. package/dist/diagnostic/index.d.ts +1 -1
  5. package/dist/diagnostic/index.js +9 -1
  6. package/dist/diagnostic/index.js.map +1 -1
  7. package/dist/diagnostic/interface.d.ts +24 -9
  8. package/dist/diagnostic/nodeUtils.d.ts +13 -0
  9. package/dist/diagnostic/nodeUtils.js +90 -0
  10. package/dist/diagnostic/nodeUtils.js.map +1 -0
  11. package/dist/diagnostic/sdkDiagnosticBase.d.ts +36 -0
  12. package/dist/diagnostic/sdkDiagnosticBase.js +305 -0
  13. package/dist/diagnostic/sdkDiagnosticBase.js.map +1 -0
  14. package/dist/diagnostic/sdkDiagnosticMain.d.ts +16 -0
  15. package/dist/diagnostic/sdkDiagnosticMain.js +79 -0
  16. package/dist/diagnostic/sdkDiagnosticMain.js.map +1 -0
  17. package/dist/diagnostic/sdkDiagnosticPhotos.d.ts +13 -0
  18. package/dist/diagnostic/sdkDiagnosticPhotos.js +65 -0
  19. package/dist/diagnostic/sdkDiagnosticPhotos.js.map +1 -0
  20. package/dist/featureFlags.d.ts +7 -0
  21. package/dist/featureFlags.js +14 -0
  22. package/dist/featureFlags.js.map +1 -0
  23. package/dist/index.d.ts +1 -0
  24. package/dist/index.js +3 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/interface/featureFlags.d.ts +7 -0
  27. package/dist/interface/featureFlags.js +3 -0
  28. package/dist/interface/featureFlags.js.map +1 -0
  29. package/dist/interface/index.d.ts +3 -0
  30. package/dist/interface/index.js.map +1 -1
  31. package/dist/internal/devices/interface.d.ts +1 -1
  32. package/dist/internal/devices/manager.js +1 -1
  33. package/dist/internal/devices/manager.js.map +1 -1
  34. package/dist/internal/devices/manager.test.js +3 -3
  35. package/dist/internal/devices/manager.test.js.map +1 -1
  36. package/dist/internal/errors.d.ts +5 -0
  37. package/dist/internal/errors.js +23 -0
  38. package/dist/internal/errors.js.map +1 -1
  39. package/dist/internal/errors.test.js +53 -2
  40. package/dist/internal/errors.test.js.map +1 -1
  41. package/dist/internal/nodes/cryptoReporter.js +3 -0
  42. package/dist/internal/nodes/cryptoReporter.js.map +1 -1
  43. package/dist/internal/nodes/index.test.js +1 -1
  44. package/dist/internal/nodes/index.test.js.map +1 -1
  45. package/dist/internal/nodes/interface.d.ts +1 -1
  46. package/dist/internal/nodes/nodesAccess.d.ts +1 -1
  47. package/dist/internal/nodes/nodesAccess.js +4 -4
  48. package/dist/internal/nodes/nodesAccess.js.map +1 -1
  49. package/dist/internal/nodes/nodesAccess.test.js +2 -2
  50. package/dist/internal/nodes/nodesAccess.test.js.map +1 -1
  51. package/dist/internal/photos/albums.js +1 -1
  52. package/dist/internal/photos/albums.js.map +1 -1
  53. package/dist/internal/photos/apiService.d.ts +6 -0
  54. package/dist/internal/photos/apiService.js +16 -0
  55. package/dist/internal/photos/apiService.js.map +1 -1
  56. package/dist/internal/photos/index.d.ts +1 -1
  57. package/dist/internal/photos/index.js +2 -2
  58. package/dist/internal/photos/index.js.map +1 -1
  59. package/dist/internal/photos/interface.d.ts +4 -1
  60. package/dist/internal/photos/shares.d.ts +1 -1
  61. package/dist/internal/photos/shares.js +3 -3
  62. package/dist/internal/photos/shares.js.map +1 -1
  63. package/dist/internal/photos/timeline.d.ts +8 -1
  64. package/dist/internal/photos/timeline.js +36 -2
  65. package/dist/internal/photos/timeline.js.map +1 -1
  66. package/dist/internal/photos/timeline.test.d.ts +1 -0
  67. package/dist/internal/photos/timeline.test.js +99 -0
  68. package/dist/internal/photos/timeline.test.js.map +1 -0
  69. package/dist/internal/photos/upload.js +1 -1
  70. package/dist/internal/photos/upload.js.map +1 -1
  71. package/dist/internal/shares/cryptoService.js +3 -0
  72. package/dist/internal/shares/cryptoService.js.map +1 -1
  73. package/dist/internal/shares/manager.d.ts +1 -1
  74. package/dist/internal/shares/manager.js +4 -4
  75. package/dist/internal/shares/manager.js.map +1 -1
  76. package/dist/internal/shares/manager.test.js +7 -7
  77. package/dist/internal/shares/manager.test.js.map +1 -1
  78. package/dist/internal/sharing/interface.d.ts +1 -1
  79. package/dist/internal/sharing/sharingAccess.js +1 -1
  80. package/dist/internal/sharing/sharingAccess.js.map +1 -1
  81. package/dist/internal/sharing/sharingAccess.test.js +1 -1
  82. package/dist/internal/sharing/sharingAccess.test.js.map +1 -1
  83. package/dist/internal/sharing/sharingManagement.js +32 -14
  84. package/dist/internal/sharing/sharingManagement.js.map +1 -1
  85. package/dist/internal/sharing/sharingManagement.test.js +46 -1
  86. package/dist/internal/sharing/sharingManagement.test.js.map +1 -1
  87. package/dist/internal/sharingPublic/cryptoReporter.js +3 -0
  88. package/dist/internal/sharingPublic/cryptoReporter.js.map +1 -1
  89. package/dist/internal/sharingPublic/index.d.ts +3 -0
  90. package/dist/internal/sharingPublic/index.js +3 -0
  91. package/dist/internal/sharingPublic/index.js.map +1 -1
  92. package/dist/internal/sharingPublic/shares.d.ts +1 -1
  93. package/dist/internal/sharingPublic/shares.js +1 -2
  94. package/dist/internal/sharingPublic/shares.js.map +1 -1
  95. package/dist/internal/upload/fileUploader.d.ts +5 -2
  96. package/dist/internal/upload/fileUploader.js +7 -4
  97. package/dist/internal/upload/fileUploader.js.map +1 -1
  98. package/dist/protonDriveClient.d.ts +1 -1
  99. package/dist/protonDriveClient.js +5 -1
  100. package/dist/protonDriveClient.js.map +1 -1
  101. package/dist/protonDrivePhotosClient.d.ts +19 -0
  102. package/dist/protonDrivePhotosClient.js +23 -1
  103. package/dist/protonDrivePhotosClient.js.map +1 -1
  104. package/dist/protonDrivePublicLinkClient.d.ts +33 -1
  105. package/dist/protonDrivePublicLinkClient.js +51 -2
  106. package/dist/protonDrivePublicLinkClient.js.map +1 -1
  107. package/package.json +1 -1
  108. package/src/diagnostic/diagnostic.ts +27 -8
  109. package/src/diagnostic/index.ts +17 -2
  110. package/src/diagnostic/interface.ts +35 -9
  111. package/src/diagnostic/nodeUtils.ts +100 -0
  112. package/src/diagnostic/{sdkDiagnostic.ts → sdkDiagnosticBase.ts} +204 -204
  113. package/src/diagnostic/sdkDiagnosticMain.ts +95 -0
  114. package/src/diagnostic/sdkDiagnosticPhotos.ts +70 -0
  115. package/src/featureFlags.ts +11 -0
  116. package/src/index.ts +1 -0
  117. package/src/interface/featureFlags.ts +7 -0
  118. package/src/interface/index.ts +3 -0
  119. package/src/internal/devices/interface.ts +1 -1
  120. package/src/internal/devices/manager.test.ts +3 -3
  121. package/src/internal/devices/manager.ts +1 -1
  122. package/src/internal/errors.test.ts +62 -1
  123. package/src/internal/errors.ts +27 -0
  124. package/src/internal/nodes/cryptoReporter.ts +6 -5
  125. package/src/internal/nodes/index.test.ts +1 -1
  126. package/src/internal/nodes/interface.ts +1 -1
  127. package/src/internal/nodes/nodesAccess.test.ts +2 -2
  128. package/src/internal/nodes/nodesAccess.ts +5 -5
  129. package/src/internal/photos/albums.ts +1 -1
  130. package/src/internal/photos/apiService.ts +40 -0
  131. package/src/internal/photos/index.ts +9 -1
  132. package/src/internal/photos/interface.ts +4 -1
  133. package/src/internal/photos/shares.ts +3 -3
  134. package/src/internal/photos/timeline.test.ts +116 -0
  135. package/src/internal/photos/timeline.ts +47 -2
  136. package/src/internal/photos/upload.ts +1 -1
  137. package/src/internal/shares/cryptoService.ts +5 -1
  138. package/src/internal/shares/manager.test.ts +7 -7
  139. package/src/internal/shares/manager.ts +4 -4
  140. package/src/internal/sharing/interface.ts +1 -1
  141. package/src/internal/sharing/sharingAccess.test.ts +1 -1
  142. package/src/internal/sharing/sharingAccess.ts +1 -1
  143. package/src/internal/sharing/sharingManagement.test.ts +59 -1
  144. package/src/internal/sharing/sharingManagement.ts +33 -14
  145. package/src/internal/sharingPublic/cryptoReporter.ts +5 -1
  146. package/src/internal/sharingPublic/index.ts +3 -0
  147. package/src/internal/sharingPublic/shares.ts +1 -2
  148. package/src/internal/upload/fileUploader.ts +18 -11
  149. package/src/protonDriveClient.ts +5 -0
  150. package/src/protonDrivePhotosClient.ts +24 -1
  151. package/src/protonDrivePublicLinkClient.ts +77 -2
  152. package/dist/diagnostic/sdkDiagnostic.d.ts +0 -23
  153. package/dist/diagnostic/sdkDiagnostic.js +0 -320
  154. package/dist/diagnostic/sdkDiagnostic.js.map +0 -1
@@ -50,7 +50,7 @@ describe('SharesManager', () => {
50
50
  manager = new SharesManager(getMockLogger(), apiService, cache, cryptoCache, cryptoService, account);
51
51
  });
52
52
 
53
- describe('getOwnVolumeIDs', () => {
53
+ describe('getRootIDs', () => {
54
54
  const myFilesShare = {
55
55
  shareId: 'myFilesShareId',
56
56
  volumeId: 'myFilesVolumeId',
@@ -71,8 +71,8 @@ describe('SharesManager', () => {
71
71
  cryptoService.decryptRootShare = jest.fn().mockResolvedValue({ share: myFilesShare, key });
72
72
 
73
73
  // Calling twice to check if it loads only once.
74
- await manager.getOwnVolumeIDs();
75
- const result = await manager.getOwnVolumeIDs();
74
+ await manager.getRootIDs();
75
+ const result = await manager.getRootIDs();
76
76
 
77
77
  expect(result).toStrictEqual(myFilesShare);
78
78
  expect(apiService.getMyFiles).toHaveBeenCalledTimes(1);
@@ -103,7 +103,7 @@ describe('SharesManager', () => {
103
103
  });
104
104
  apiService.createVolume = jest.fn().mockResolvedValue(myFilesShare);
105
105
 
106
- const result = await manager.getOwnVolumeIDs();
106
+ const result = await manager.getRootIDs();
107
107
 
108
108
  expect(result).toStrictEqual(myFilesShare);
109
109
  expect(cryptoService.decryptRootShare).not.toHaveBeenCalled();
@@ -113,7 +113,7 @@ describe('SharesManager', () => {
113
113
  it('should throw on unknown error', async () => {
114
114
  apiService.getMyFiles = jest.fn().mockRejectedValue(new Error('Some error'));
115
115
 
116
- await expect(manager.getOwnVolumeIDs()).rejects.toThrow('Some error');
116
+ await expect(manager.getRootIDs()).rejects.toThrow('Some error');
117
117
  expect(cryptoService.decryptRootShare).not.toHaveBeenCalled();
118
118
  expect(apiService.createVolume).not.toHaveBeenCalled();
119
119
  });
@@ -142,7 +142,7 @@ describe('SharesManager', () => {
142
142
 
143
143
  describe('getMyFilesShareMemberEmailKey', () => {
144
144
  it('should return cached volume email key', async () => {
145
- jest.spyOn(manager, 'getOwnVolumeIDs').mockResolvedValue({ volumeId: 'volumeId' } as VolumeShareNodeIDs);
145
+ jest.spyOn(manager, 'getRootIDs').mockResolvedValue({ volumeId: 'volumeId' } as VolumeShareNodeIDs);
146
146
  cache.getVolume = jest.fn().mockResolvedValue({ addressId: 'addressId' });
147
147
  account.getOwnAddress = jest
148
148
  .fn()
@@ -158,7 +158,7 @@ describe('SharesManager', () => {
158
158
  });
159
159
 
160
160
  it('should load volume email key if not in cache', async () => {
161
- jest.spyOn(manager, 'getOwnVolumeIDs').mockResolvedValue({ volumeId: 'volumeId' } as VolumeShareNodeIDs);
161
+ jest.spyOn(manager, 'getRootIDs').mockResolvedValue({ volumeId: 'volumeId' } as VolumeShareNodeIDs);
162
162
  const share = {
163
163
  volumeId: 'volumeId',
164
164
  shareId: 'shareId',
@@ -46,7 +46,7 @@ export class SharesManager {
46
46
  *
47
47
  * If the default volume or My files section doesn't exist, it creates it.
48
48
  */
49
- async getOwnVolumeIDs(): Promise<VolumeShareNodeIDs> {
49
+ async getRootIDs(): Promise<VolumeShareNodeIDs> {
50
50
  if (this.myFilesIds) {
51
51
  return this.myFilesIds;
52
52
  }
@@ -140,7 +140,7 @@ export class SharesManager {
140
140
  addressKey: PrivateKey;
141
141
  addressKeyId: string;
142
142
  }> {
143
- const { volumeId } = await this.getOwnVolumeIDs();
143
+ const { volumeId } = await this.getRootIDs();
144
144
 
145
145
  try {
146
146
  const { addressId } = await this.cache.getVolume(volumeId);
@@ -196,11 +196,11 @@ export class SharesManager {
196
196
  }
197
197
 
198
198
  async isOwnVolume(volumeId: string): Promise<boolean> {
199
- return (await this.getOwnVolumeIDs()).volumeId === volumeId;
199
+ return (await this.getRootIDs()).volumeId === volumeId;
200
200
  }
201
201
 
202
202
  async getVolumeMetricContext(volumeId: string): Promise<MetricVolumeType> {
203
- const { volumeId: myVolumeId } = await this.getOwnVolumeIDs();
203
+ const { volumeId: myVolumeId } = await this.getRootIDs();
204
204
 
205
205
  // SDK doesn't support public sharing yet, also public sharing
206
206
  // doesn't use a volume but shareURL, thus we can simplify and
@@ -142,7 +142,7 @@ export interface PublicLinkWithCreatorEmail extends PublicLink {
142
142
  * Interface describing the dependencies to the shares module.
143
143
  */
144
144
  export interface SharesService {
145
- getOwnVolumeIDs(): Promise<{ volumeId: string }>;
145
+ getRootIDs(): Promise<{ volumeId: string }>;
146
146
  loadEncryptedShare(shareId: string): Promise<EncryptedShare>;
147
147
  getMyFilesShareMemberEmailKey(): Promise<{
148
148
  email: string;
@@ -93,7 +93,7 @@ describe('SharingAccess', () => {
93
93
 
94
94
  // @ts-expect-error No need to implement all methods for mocking
95
95
  sharesService = {
96
- getOwnVolumeIDs: jest.fn().mockResolvedValue({ volumeId: 'volumeId' }),
96
+ getRootIDs: jest.fn().mockResolvedValue({ volumeId: 'volumeId' }),
97
97
  loadEncryptedShare: jest.fn().mockResolvedValue({
98
98
  id: 'shareId',
99
99
  membership: { memberUid: 'memberUid' },
@@ -40,7 +40,7 @@ export class SharingAccess {
40
40
  const nodeUids = await this.cache.getSharedByMeNodeUids();
41
41
  yield* this.iterateSharedNodesFromCache(nodeUids, signal);
42
42
  } catch {
43
- const { volumeId } = await this.sharesService.getOwnVolumeIDs();
43
+ const { volumeId } = await this.sharesService.getRootIDs();
44
44
  const nodeUidsIterator = this.apiService.iterateSharedNodeUids(volumeId, signal);
45
45
  yield* this.iterateSharedNodesFromAPI(
46
46
  nodeUidsIterator,
@@ -1,5 +1,6 @@
1
1
  import { getMockLogger } from '../../tests/logger';
2
2
  import {
3
+ Logger,
3
4
  Member,
4
5
  MemberRole,
5
6
  NonProtonInvitation,
@@ -14,8 +15,11 @@ import { SharingCache } from './cache';
14
15
  import { SharingCryptoService } from './cryptoService';
15
16
  import { SharesService, NodesService } from './interface';
16
17
  import { SharingManagement } from './sharingManagement';
18
+ import { ValidationError } from '../../errors';
19
+ import { ErrorCode } from '../apiService';
17
20
 
18
21
  describe('SharingManagement', () => {
22
+ let logger: Logger;
19
23
  let apiService: SharingAPIService;
20
24
  let cache: SharingCache;
21
25
  let cryptoService: SharingCryptoService;
@@ -26,6 +30,8 @@ describe('SharingManagement', () => {
26
30
  let sharingManagement: SharingManagement;
27
31
 
28
32
  beforeEach(() => {
33
+ logger = getMockLogger();
34
+
29
35
  // @ts-expect-error No need to implement all methods for mocking
30
36
  apiService = {
31
37
  createStandardShare: jest.fn().mockReturnValue('newShareId'),
@@ -110,7 +116,7 @@ describe('SharingManagement', () => {
110
116
  };
111
117
 
112
118
  sharingManagement = new SharingManagement(
113
- getMockLogger(),
119
+ logger,
114
120
  apiService,
115
121
  cache,
116
122
  cryptoService,
@@ -225,6 +231,58 @@ describe('SharingManagement', () => {
225
231
  expect(nodesService.notifyNodeChanged).toHaveBeenCalledWith(nodeUid);
226
232
  expect(cache.addSharedByMeNodeUid).toHaveBeenCalledWith(nodeUid);
227
233
  });
234
+
235
+ it('should refresh node info if share already exists', async () => {
236
+ nodesService.getNode = jest
237
+ .fn()
238
+ .mockImplementationOnce((nodeUid) => ({
239
+ nodeUid,
240
+ parentUid: 'parentUid',
241
+ name: { ok: true, value: 'name' },
242
+ }))
243
+ .mockImplementation((nodeUid) => ({
244
+ nodeUid,
245
+ shareId: 'shareId',
246
+ parentUid: 'parentUid',
247
+ name: { ok: true, value: 'name' },
248
+ }));
249
+ apiService.createStandardShare = jest
250
+ .fn()
251
+ .mockRejectedValue(new ValidationError('Share already exists', ErrorCode.ALREADY_EXISTS));
252
+
253
+ const sharingInfo = await sharingManagement.shareNode(nodeUid, { users: ['email'] });
254
+
255
+ expect(sharingInfo).toEqual({
256
+ protonInvitations: [
257
+ {
258
+ uid: 'created-invitation',
259
+ addedByEmail: { ok: true, value: 'volume-email' },
260
+ inviteeEmail: 'email',
261
+ role: 'viewer',
262
+ },
263
+ ],
264
+ nonProtonInvitations: [],
265
+ members: [],
266
+ publicLink: undefined,
267
+ });
268
+
269
+ expect(nodesService.notifyNodeChanged).toHaveBeenCalledWith(nodeUid);
270
+ expect(logger.debug).toHaveBeenCalledWith(
271
+ 'Share already exists for node volumeId~nodeUid, refreshing node',
272
+ );
273
+ expect(apiService.inviteProtonUser).toHaveBeenCalledWith(
274
+ 'shareId',
275
+ {
276
+ addedByEmail: 'volume-email',
277
+ inviteeEmail: 'email',
278
+ role: 'viewer',
279
+ },
280
+ {
281
+ message: undefined,
282
+ nodeName: undefined,
283
+ },
284
+ );
285
+ });
228
286
  });
229
287
 
230
288
  describe('shareNode with share re-use', () => {
@@ -15,6 +15,7 @@ import {
15
15
  ProtonDriveAccount,
16
16
  SharePublicLinkSettingsObject,
17
17
  } from '../../interface';
18
+ import { ErrorCode } from '../apiService';
18
19
  import { splitNodeUid } from '../uids';
19
20
  import { getErrorMessage } from '../errors';
20
21
  import { SharingAPIService } from './apiService';
@@ -147,22 +148,40 @@ export class SharingManagement {
147
148
  throw new ValidationError(c('Error').t`Expiration date cannot be in the past`);
148
149
  }
149
150
 
150
- let contextShareAddress: ContextShareAddress;
151
+ let contextShareAddress: ContextShareAddress | undefined;
151
152
  let currentSharing = await this.getInternalSharingInfo(nodeUid);
152
- if (currentSharing) {
153
- contextShareAddress = await this.nodesService.getRootNodeEmailKey(nodeUid);
154
- } else {
153
+ if (!currentSharing) {
155
154
  const node = await this.nodesService.getNode(nodeUid);
156
- const result = await this.createShare(nodeUid);
157
- currentSharing = {
158
- share: result.share,
159
- nodeName: node.name.ok ? node.name.value : node.name.error.name,
160
- protonInvitations: [],
161
- nonProtonInvitations: [],
162
- members: [],
163
- publicLink: undefined,
164
- };
165
- contextShareAddress = result.contextShareAddress;
155
+ try {
156
+ const result = await this.createShare(nodeUid);
157
+ currentSharing = {
158
+ share: result.share,
159
+ nodeName: node.name.ok ? node.name.value : node.name.error.name,
160
+ protonInvitations: [],
161
+ nonProtonInvitations: [],
162
+ members: [],
163
+ publicLink: undefined,
164
+ };
165
+ contextShareAddress = result.contextShareAddress;
166
+ } catch (error: unknown) {
167
+ // If the share already exists, notify that the node has
168
+ // changed to force refresh and get the latest sharing info
169
+ // again.
170
+ if (error instanceof ValidationError && error.code === ErrorCode.ALREADY_EXISTS) {
171
+ this.logger.debug(`Share already exists for node ${nodeUid}, refreshing node`);
172
+ await this.nodesService.notifyNodeChanged(nodeUid);
173
+ currentSharing = await this.getInternalSharingInfo(nodeUid);
174
+ } else {
175
+ throw error;
176
+ }
177
+ }
178
+ }
179
+
180
+ if (!currentSharing) {
181
+ throw new ValidationError(c('Error').t`Failed to get sharing info for node ${nodeUid}`);
182
+ }
183
+ if (!contextShareAddress) {
184
+ contextShareAddress = await this.nodesService.getRootNodeEmailKey(nodeUid);
166
185
  }
167
186
 
168
187
  const emailOptions: EmailOptions = {
@@ -1,7 +1,7 @@
1
1
  import { c } from 'ttag';
2
2
 
3
3
  import { VERIFICATION_STATUS } from '../../crypto';
4
- import { getVerificationMessage } from '../errors';
4
+ import { getVerificationMessage, isNotApplicationError } from '../errors';
5
5
  import {
6
6
  resultOk,
7
7
  resultError,
@@ -49,6 +49,10 @@ export class SharingPublicCryptoReporter {
49
49
  field: MetricsDecryptionErrorField,
50
50
  error: unknown,
51
51
  ) {
52
+ if (isNotApplicationError(error)) {
53
+ return;
54
+ }
55
+
52
56
  const fromBefore2024 = node.creationTime < new Date('2024-01-01');
53
57
 
54
58
  this.logger.error(
@@ -10,6 +10,7 @@ import { NodeAPIService } from '../nodes/apiService';
10
10
  import { NodesCache } from '../nodes/cache';
11
11
  import { NodesCryptoCache } from '../nodes/cryptoCache';
12
12
  import { NodesCryptoService } from '../nodes/cryptoService';
13
+ import { NodesManagement } from '../nodes/nodesManagement';
13
14
  import { NodesRevisons } from '../nodes/nodesRevisions';
14
15
  import { SharingPublicCryptoReporter } from './cryptoReporter';
15
16
  import { SharingPublicNodesAccess } from './nodes';
@@ -96,10 +97,12 @@ export function initSharingPublicNodesModule(
96
97
  publicShareKey,
97
98
  publicRootNodeUid,
98
99
  );
100
+ const nodesManagement = new NodesManagement(api, cryptoCache, cryptoService, nodesAccess);
99
101
  const nodesRevisions = new NodesRevisons(telemetry.getLogger('nodes'), api, cryptoService, nodesAccess);
100
102
 
101
103
  return {
102
104
  access: nodesAccess,
105
+ management: nodesManagement,
103
106
  revisions: nodesRevisions,
104
107
  };
105
108
  }
@@ -19,8 +19,7 @@ export class SharingPublicSharesManager {
19
19
  this.publicRootNodeUid = publicRootNodeUid;
20
20
  }
21
21
 
22
- // TODO: Rename to getRootIDs everywhere.
23
- async getOwnVolumeIDs(): Promise<{ volumeId: string; rootNodeId: string; rootNodeUid: string }> {
22
+ async getRootIDs(): Promise<{ volumeId: string; rootNodeId: string; rootNodeUid: string }> {
24
23
  const { volumeId, nodeId: rootNodeId } = splitNodeUid(this.publicRootNodeUid);
25
24
  return { volumeId, rootNodeId, rootNodeUid: this.publicRootNodeUid };
26
25
  }
@@ -15,7 +15,7 @@ import { UploadTelemetry } from './telemetry';
15
15
  * This class is not meant to be used directly, but rather to be extended
16
16
  * by `FileUploader` and `FileRevisionUploader`.
17
17
  */
18
- class Uploader {
18
+ abstract class Uploader {
19
19
  protected controller: UploadController;
20
20
  protected abortController: AbortController;
21
21
 
@@ -83,7 +83,7 @@ class Uploader {
83
83
  stream: ReadableStream,
84
84
  thumbnails: Thumbnail[],
85
85
  onProgress?: (uploadedBytes: number) => void,
86
- ): Promise<{ nodeRevisionUid: string, nodeUid: string }> {
86
+ ): Promise<{ nodeRevisionUid: string; nodeUid: string }> {
87
87
  const uploader = await this.initStreamUploader();
88
88
  return uploader.start(stream, thumbnails, onProgress);
89
89
  }
@@ -94,15 +94,11 @@ class Uploader {
94
94
  const onFinish = async (failure: boolean) => {
95
95
  this.onFinish();
96
96
  if (failure) {
97
- await this.manager.deleteDraftNode(revisionDraft.nodeUid);
97
+ await this.deleteRevisionDraft(revisionDraft);
98
98
  }
99
99
  };
100
100
 
101
- return this.newStreamUploader(
102
- blockVerifier,
103
- revisionDraft,
104
- onFinish,
105
- );
101
+ return this.newStreamUploader(blockVerifier, revisionDraft, onFinish);
106
102
  }
107
103
 
108
104
  protected async newStreamUploader(
@@ -124,9 +120,12 @@ class Uploader {
124
120
  );
125
121
  }
126
122
 
127
- protected async createRevisionDraft(): Promise<{ revisionDraft: NodeRevisionDraft; blockVerifier: BlockVerifier }> {
128
- throw new Error('Not implemented');
129
- }
123
+ protected abstract createRevisionDraft(): Promise<{
124
+ revisionDraft: NodeRevisionDraft;
125
+ blockVerifier: BlockVerifier;
126
+ }>;
127
+
128
+ protected abstract deleteRevisionDraft(revisionDraft: NodeRevisionDraft): Promise<void>;
130
129
  }
131
130
 
132
131
  /**
@@ -176,6 +175,10 @@ export class FileUploader extends Uploader {
176
175
  blockVerifier,
177
176
  };
178
177
  }
178
+
179
+ protected async deleteRevisionDraft(revisionDraft: NodeRevisionDraft): Promise<void> {
180
+ await this.manager.deleteDraftNode(revisionDraft.nodeUid);
181
+ }
179
182
  }
180
183
 
181
184
  /**
@@ -223,4 +226,8 @@ export class FileRevisionUploader extends Uploader {
223
226
  blockVerifier,
224
227
  };
225
228
  }
229
+
230
+ protected async deleteRevisionDraft(revisionDraft: NodeRevisionDraft): Promise<void> {
231
+ await this.manager.deleteDraftRevision(revisionDraft.nodeRevisionUid);
232
+ }
226
233
  }
@@ -1,5 +1,6 @@
1
1
  import { getConfig } from './config';
2
2
  import { DriveCrypto, SessionKey } from './crypto';
3
+ import { NullFeatureFlagProvider } from './featureFlags';
3
4
  import {
4
5
  Logger,
5
6
  ProtonDriveClientContructorParameters,
@@ -117,11 +118,15 @@ export class ProtonDriveClient {
117
118
  srpModule,
118
119
  config,
119
120
  telemetry,
121
+ featureFlagProvider,
120
122
  latestEventIdProvider,
121
123
  }: ProtonDriveClientContructorParameters) {
122
124
  if (!telemetry) {
123
125
  telemetry = new Telemetry();
124
126
  }
127
+ if (!featureFlagProvider) {
128
+ featureFlagProvider = new NullFeatureFlagProvider();
129
+ }
125
130
  this.logger = telemetry.getLogger('interface');
126
131
 
127
132
  const fullConfig = getConfig(config);
@@ -117,7 +117,7 @@ export class ProtonDrivePhotosClient {
117
117
  this.photoShares,
118
118
  fullConfig.clientUid,
119
119
  );
120
- this.photos = initPhotosModule(apiService, this.photoShares, this.nodes.access);
120
+ this.photos = initPhotosModule(telemetry, apiService, cryptoModule, this.photoShares, this.nodes.access);
121
121
  this.sharing = initSharingModule(
122
122
  telemetry,
123
123
  apiService,
@@ -454,6 +454,29 @@ export class ProtonDrivePhotosClient {
454
454
  return this.upload.getFileUploader(getUid(parentFolderUid), name, metadata, signal);
455
455
  }
456
456
 
457
+ /**
458
+ * Check if the photo is a duplicate.
459
+ *
460
+ * For given photo name, find existing photos with the same name
461
+ * in the timeline and check if the photo content is also the same.
462
+ * Only the same name is not considered as duplicate photo because
463
+ * it is expected that there are photos with the same name (e.g.,
464
+ * date as a name from multiple cameras, or rolling number).
465
+ *
466
+ * The function accepts a callback to generate the SHA1 and it is
467
+ * called only when there is any matching node name hash to avoid
468
+ * computation for every node if its not necessary.
469
+ *
470
+ * @param name - The name of the photo to check for duplicates.
471
+ * @param generateSha1 - A callback to generate the hex string representation of the SHA1 of the photo content.
472
+ * @param signal - An optional abort signal to cancel the operation.
473
+ * @returns True if the photo already exists in the timeline, false otherwise.
474
+ */
475
+ async isDuplicatePhoto(name: string, generateSha1: () => Promise<string>, signal?: AbortSignal): Promise<boolean> {
476
+ this.logger.info(`Checking if photo is a duplicate`);
477
+ return this.photos.timeline.isDuplicatePhoto(name, generateSha1, signal);
478
+ }
479
+
457
480
  /**
458
481
  * Iterates the albums.
459
482
  *
@@ -15,6 +15,9 @@ import {
15
15
  FileDownloader,
16
16
  ThumbnailType,
17
17
  ThumbnailResult,
18
+ UploadMetadata,
19
+ FileUploader,
20
+ NodeResult,
18
21
  } from './interface';
19
22
  import { Telemetry } from './telemetry';
20
23
  import {
@@ -28,6 +31,7 @@ import { DriveAPIService } from './internal/apiService';
28
31
  import { initDownloadModule } from './internal/download';
29
32
  import { SDKEvents } from './internal/sdkEvents';
30
33
  import { initSharingPublicModule } from './internal/sharingPublic';
34
+ import { initUploadModule } from './internal/upload';
31
35
 
32
36
  /**
33
37
  * ProtonDrivePublicLinkClient is the interface for the public link client.
@@ -47,6 +51,7 @@ export class ProtonDrivePublicLinkClient {
47
51
  private sdkEvents: SDKEvents;
48
52
  private sharingPublic: ReturnType<typeof initSharingPublicModule>;
49
53
  private download: ReturnType<typeof initDownloadModule>;
54
+ private upload: ReturnType<typeof initUploadModule>;
50
55
 
51
56
  public experimental: {
52
57
  /**
@@ -91,7 +96,7 @@ export class ProtonDrivePublicLinkClient {
91
96
  if (!telemetry) {
92
97
  telemetry = new Telemetry();
93
98
  }
94
- this.logger = telemetry.getLogger('interface');
99
+ this.logger = telemetry.getLogger('publicLink-interface');
95
100
 
96
101
  // Use only in memory cache for public link as there are no events to keep it up to date if persisted.
97
102
  const entitiesCache = new MemoryCache<string>();
@@ -129,6 +134,14 @@ export class ProtonDrivePublicLinkClient {
129
134
  this.sharingPublic.nodes.access,
130
135
  this.sharingPublic.nodes.revisions,
131
136
  );
137
+ this.upload = initUploadModule(
138
+ telemetry,
139
+ apiService,
140
+ cryptoModule,
141
+ this.sharingPublic.shares,
142
+ this.sharingPublic.nodes.access,
143
+ fullConfig.clientUid,
144
+ );
132
145
 
133
146
  this.experimental = {
134
147
  getNodeUrl: async (nodeUid: NodeOrUid) => {
@@ -151,7 +164,7 @@ export class ProtonDrivePublicLinkClient {
151
164
  */
152
165
  async getRootNode(): Promise<MaybeNode> {
153
166
  this.logger.info(`Getting root node`);
154
- const { rootNodeUid } = await this.sharingPublic.shares.getOwnVolumeIDs();
167
+ const { rootNodeUid } = await this.sharingPublic.shares.getRootIDs();
155
168
  return convertInternalNodePromise(this.sharingPublic.nodes.access.getNode(rootNodeUid));
156
169
  }
157
170
 
@@ -193,6 +206,38 @@ export class ProtonDrivePublicLinkClient {
193
206
  return convertInternalNodePromise(this.sharingPublic.nodes.access.getNode(getUid(nodeUid)));
194
207
  }
195
208
 
209
+ /**
210
+ * Rename the node.
211
+ *
212
+ * See `ProtonDriveClient.renameNode` for more information.
213
+ */
214
+ async renameNode(nodeUid: NodeOrUid, newName: string): Promise<MaybeNode> {
215
+ this.logger.info(`Renaming node ${getUid(nodeUid)}`);
216
+ return convertInternalNodePromise(this.sharingPublic.nodes.management.renameNode(getUid(nodeUid), newName));
217
+ }
218
+
219
+ /**
220
+ * Delete the nodes permanently.
221
+ *
222
+ * See `ProtonDriveClient.deleteNodes` for more information.
223
+ */
224
+ async *deleteNodes(nodeUids: NodeOrUid[], signal?: AbortSignal): AsyncGenerator<NodeResult> {
225
+ this.logger.info(`Deleting ${nodeUids.length} nodes`);
226
+ yield* this.sharingPublic.nodes.management.deleteNodes(getUids(nodeUids), signal);
227
+ }
228
+
229
+ /**
230
+ * Create a new folder.
231
+ *
232
+ * See `ProtonDriveClient.createFolder` for more information.
233
+ */
234
+ async createFolder(parentNodeUid: NodeOrUid, name: string, modificationTime?: Date): Promise<MaybeNode> {
235
+ this.logger.info(`Creating folder in ${getUid(parentNodeUid)}`);
236
+ return convertInternalNodePromise(
237
+ this.sharingPublic.nodes.management.createFolder(getUid(parentNodeUid), name, modificationTime),
238
+ );
239
+ }
240
+
196
241
  /**
197
242
  * Get the file downloader to download the node content.
198
243
  *
@@ -216,4 +261,34 @@ export class ProtonDrivePublicLinkClient {
216
261
  this.logger.info(`Iterating ${nodeUids.length} thumbnails`);
217
262
  yield* this.download.iterateThumbnails(getUids(nodeUids), thumbnailType, signal);
218
263
  }
264
+
265
+ /**
266
+ * Get the file uploader to upload a new file. For uploading a new
267
+ * revision, use `getFileRevisionUploader` instead.
268
+ *
269
+ * See `ProtonDriveClient.getFileUploader` for more information.
270
+ */
271
+ async getFileUploader(
272
+ parentFolderUid: NodeOrUid,
273
+ name: string,
274
+ metadata: UploadMetadata,
275
+ signal?: AbortSignal,
276
+ ): Promise<FileUploader> {
277
+ this.logger.info(`Getting file uploader for parent ${getUid(parentFolderUid)}`);
278
+ return this.upload.getFileUploader(getUid(parentFolderUid), name, metadata, signal);
279
+ }
280
+
281
+ /**
282
+ * Same as `getFileUploader`, but for a uploading new revision of the file.
283
+ *
284
+ * See `ProtonDriveClient.getFileRevisionUploader` for more information.
285
+ */
286
+ async getFileRevisionUploader(
287
+ nodeUid: NodeOrUid,
288
+ metadata: UploadMetadata,
289
+ signal?: AbortSignal,
290
+ ): Promise<FileUploader> {
291
+ this.logger.info(`Getting file revision uploader for ${getUid(nodeUid)}`);
292
+ return this.upload.getFileRevisionUploader(getUid(nodeUid), metadata, signal);
293
+ }
219
294
  }
@@ -1,23 +0,0 @@
1
- import { MaybeNode } from '../interface';
2
- import { ProtonDriveClient } from '../protonDriveClient';
3
- import { DiagnosticOptions, DiagnosticResult } from './interface';
4
- /**
5
- * Diagnostic tool that uses SDK to traverse the node tree and verify
6
- * the integrity of the node tree.
7
- *
8
- * It produces only events that can be read by direct SDK invocation.
9
- * To get the full diagnostic, use {@link FullSDKDiagnostic}.
10
- */
11
- export declare class SDKDiagnostic {
12
- private protonDriveClient;
13
- constructor(protonDriveClient: ProtonDriveClient);
14
- verifyMyFiles(options?: DiagnosticOptions): AsyncGenerator<DiagnosticResult>;
15
- verifyNodeTree(node: MaybeNode, options?: DiagnosticOptions): AsyncGenerator<DiagnosticResult>;
16
- private verifyNode;
17
- private verifyAuthor;
18
- private verifyFileExtendedAttributes;
19
- private verifyContent;
20
- private verifyThumbnails;
21
- private verifyNodeChildren;
22
- private verifyExpectedNodeChildren;
23
- }