@protontech/drive-sdk 0.5.1 → 0.6.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 (88) hide show
  1. package/dist/interface/index.d.ts +1 -1
  2. package/dist/interface/upload.d.ts +1 -12
  3. package/dist/internal/nodes/apiService.d.ts +11 -1
  4. package/dist/internal/nodes/apiService.js +20 -1
  5. package/dist/internal/nodes/apiService.js.map +1 -1
  6. package/dist/internal/nodes/apiService.test.js +1 -1
  7. package/dist/internal/nodes/apiService.test.js.map +1 -1
  8. package/dist/internal/nodes/cryptoService.d.ts +4 -0
  9. package/dist/internal/nodes/cryptoService.js +6 -0
  10. package/dist/internal/nodes/cryptoService.js.map +1 -1
  11. package/dist/internal/nodes/index.d.ts +1 -1
  12. package/dist/internal/nodes/index.js +2 -2
  13. package/dist/internal/nodes/index.js.map +1 -1
  14. package/dist/internal/nodes/index.test.js +1 -1
  15. package/dist/internal/nodes/index.test.js.map +1 -1
  16. package/dist/internal/nodes/nodeName.d.ts +8 -0
  17. package/dist/internal/nodes/nodeName.js +30 -0
  18. package/dist/internal/nodes/nodeName.js.map +1 -0
  19. package/dist/internal/nodes/nodeName.test.d.ts +1 -0
  20. package/dist/internal/nodes/nodeName.test.js +50 -0
  21. package/dist/internal/nodes/nodeName.test.js.map +1 -0
  22. package/dist/internal/nodes/nodesManagement.d.ts +1 -0
  23. package/dist/internal/nodes/nodesManagement.js +30 -1
  24. package/dist/internal/nodes/nodesManagement.js.map +1 -1
  25. package/dist/internal/nodes/nodesManagement.test.js +61 -0
  26. package/dist/internal/nodes/nodesManagement.test.js.map +1 -1
  27. package/dist/internal/photos/index.d.ts +2 -0
  28. package/dist/internal/photos/index.js +4 -0
  29. package/dist/internal/photos/index.js.map +1 -1
  30. package/dist/internal/shares/index.d.ts +1 -0
  31. package/dist/internal/shares/index.js +3 -0
  32. package/dist/internal/shares/index.js.map +1 -1
  33. package/dist/internal/shares/interface.d.ts +8 -0
  34. package/dist/internal/shares/interface.js +10 -1
  35. package/dist/internal/shares/interface.js.map +1 -1
  36. package/dist/internal/sharing/apiService.d.ts +3 -1
  37. package/dist/internal/sharing/apiService.js +16 -12
  38. package/dist/internal/sharing/apiService.js.map +1 -1
  39. package/dist/internal/sharing/index.d.ts +2 -1
  40. package/dist/internal/sharing/index.js +6 -2
  41. package/dist/internal/sharing/index.js.map +1 -1
  42. package/dist/internal/sharingPublic/index.js +2 -1
  43. package/dist/internal/sharingPublic/index.js.map +1 -1
  44. package/dist/internal/upload/apiService.d.ts +0 -9
  45. package/dist/internal/upload/apiService.js +0 -16
  46. package/dist/internal/upload/apiService.js.map +1 -1
  47. package/dist/internal/upload/cryptoService.d.ts +0 -4
  48. package/dist/internal/upload/cryptoService.js +0 -6
  49. package/dist/internal/upload/cryptoService.js.map +1 -1
  50. package/dist/internal/upload/fileUploader.d.ts +0 -1
  51. package/dist/internal/upload/fileUploader.js +0 -4
  52. package/dist/internal/upload/fileUploader.js.map +1 -1
  53. package/dist/internal/upload/manager.d.ts +0 -1
  54. package/dist/internal/upload/manager.js +0 -51
  55. package/dist/internal/upload/manager.js.map +1 -1
  56. package/dist/internal/upload/manager.test.js +0 -61
  57. package/dist/internal/upload/manager.test.js.map +1 -1
  58. package/dist/protonDriveClient.d.ts +17 -2
  59. package/dist/protonDriveClient.js +19 -1
  60. package/dist/protonDriveClient.js.map +1 -1
  61. package/dist/protonDrivePhotosClient.d.ts +100 -4
  62. package/dist/protonDrivePhotosClient.js +160 -9
  63. package/dist/protonDrivePhotosClient.js.map +1 -1
  64. package/package.json +1 -1
  65. package/src/interface/index.ts +1 -1
  66. package/src/interface/upload.ts +1 -13
  67. package/src/internal/nodes/apiService.test.ts +1 -1
  68. package/src/internal/nodes/apiService.ts +42 -0
  69. package/src/internal/nodes/cryptoService.ts +9 -0
  70. package/src/internal/nodes/index.test.ts +1 -0
  71. package/src/internal/nodes/index.ts +2 -1
  72. package/src/internal/nodes/nodeName.test.ts +57 -0
  73. package/src/internal/nodes/nodeName.ts +26 -0
  74. package/src/internal/nodes/nodesManagement.test.ts +65 -0
  75. package/src/internal/nodes/nodesManagement.ts +43 -1
  76. package/src/internal/photos/index.ts +4 -0
  77. package/src/internal/shares/index.ts +1 -0
  78. package/src/internal/shares/interface.ts +9 -0
  79. package/src/internal/sharing/apiService.ts +15 -12
  80. package/src/internal/sharing/index.ts +7 -1
  81. package/src/internal/sharingPublic/index.ts +2 -1
  82. package/src/internal/upload/apiService.ts +0 -39
  83. package/src/internal/upload/cryptoService.ts +0 -9
  84. package/src/internal/upload/fileUploader.ts +0 -5
  85. package/src/internal/upload/manager.test.ts +0 -65
  86. package/src/internal/upload/manager.ts +0 -64
  87. package/src/protonDriveClient.ts +21 -2
  88. package/src/protonDrivePhotosClient.ts +193 -8
@@ -7,6 +7,7 @@ import {
7
7
  permissionsToMemberRole,
8
8
  memberRoleToPermission,
9
9
  } from '../apiService';
10
+ import { ShareTargetType } from '../shares';
10
11
  import {
11
12
  makeNodeUid,
12
13
  splitNodeUid,
@@ -119,14 +120,6 @@ type PutShareUrlRequest = Extract<
119
120
  type PutShareUrlResponse =
120
121
  drivePaths['/drive/shares/{shareID}/urls/{urlID}']['put']['responses']['200']['content']['application/json'];
121
122
 
122
- // We do not support photos and albums yet.
123
- const SUPPORTED_SHARE_TARGET_TYPES = [
124
- 0, // Root
125
- 1, // Folder
126
- 2, // File
127
- 5, // Proton vendor (documents and sheets)
128
- ];
129
-
130
123
  /**
131
124
  * Provides API communication for fetching and managing sharing.
132
125
  *
@@ -137,9 +130,11 @@ export class SharingAPIService {
137
130
  constructor(
138
131
  private logger: Logger,
139
132
  private apiService: DriveAPIService,
133
+ private shareTargetTypes: ShareTargetType[],
140
134
  ) {
141
135
  this.logger = logger;
142
136
  this.apiService = apiService;
137
+ this.shareTargetTypes = shareTargetTypes;
143
138
  }
144
139
 
145
140
  async *iterateSharedNodeUids(volumeId: string, signal?: AbortSignal): AsyncGenerator<string> {
@@ -163,6 +158,7 @@ export class SharingAPIService {
163
158
  async *iterateSharedWithMeNodeUids(signal?: AbortSignal): AsyncGenerator<string> {
164
159
  let anchor = '';
165
160
  while (true) {
161
+ // TODO: Use ShareTargetTypes filter when it is supported by the API.
166
162
  const response = await this.apiService.get<GetSharedWithMeNodesResponse>(
167
163
  `drive/v2/sharedwithme?${anchor ? `AnchorID=${anchor}` : ''}`,
168
164
  signal,
@@ -170,8 +166,8 @@ export class SharingAPIService {
170
166
  for (const link of response.Links) {
171
167
  const nodeUid = makeNodeUid(link.VolumeID, link.LinkID);
172
168
 
173
- if (!SUPPORTED_SHARE_TARGET_TYPES.includes(link.ShareTargetType)) {
174
- this.logger.warn(`Unsupported share target type ${link.ShareTargetType} for node ${nodeUid}`);
169
+ if (!this.shareTargetTypes.includes(link.ShareTargetType)) {
170
+ this.logger.debug(`Unsupported share target type ${link.ShareTargetType} for node ${nodeUid}`);
175
171
  continue;
176
172
  }
177
173
 
@@ -188,14 +184,21 @@ export class SharingAPIService {
188
184
  async *iterateInvitationUids(signal?: AbortSignal): AsyncGenerator<string> {
189
185
  let anchor = '';
190
186
  while (true) {
187
+ const params = new URLSearchParams();
188
+ this.shareTargetTypes.forEach((type) => {
189
+ params.append('ShareTargetTypes[]', type.toString());
190
+ });
191
+ if (anchor) {
192
+ params.append('AnchorID', anchor);
193
+ }
191
194
  const response = await this.apiService.get<GetInvitationsResponse>(
192
- `drive/v2/shares/invitations?${anchor ? `AnchorID=${anchor}` : ''}`,
195
+ `drive/v2/shares/invitations?${params.toString()}`,
193
196
  signal,
194
197
  );
195
198
  for (const invitation of response.Invitations) {
196
199
  const invitationUid = makeInvitationUid(invitation.ShareID, invitation.InvitationID);
197
200
 
198
- if (!SUPPORTED_SHARE_TARGET_TYPES.includes(invitation.ShareTargetType)) {
201
+ if (!this.shareTargetTypes.includes(invitation.ShareTargetType)) {
199
202
  this.logger.warn(
200
203
  `Unsupported share target type ${invitation.ShareTargetType} for invitation ${invitationUid}`,
201
204
  );
@@ -1,6 +1,7 @@
1
1
  import { ProtonDriveAccount, ProtonDriveEntitiesCache, ProtonDriveTelemetry } from '../../interface';
2
2
  import { DriveCrypto } from '../../crypto';
3
3
  import { DriveAPIService } from '../apiService';
4
+ import { ShareTargetType } from '../shares';
4
5
  import { SharingAPIService } from './apiService';
5
6
  import { SharingCache } from './cache';
6
7
  import { SharingCryptoService } from './cryptoService';
@@ -9,6 +10,10 @@ import { SharingManagement } from './sharingManagement';
9
10
  import { SharesService, NodesService } from './interface';
10
11
  import { SharingEventHandler } from './events';
11
12
 
13
+ // Root shares are not allowed to be shared.
14
+ // Photos and Albums are not supported in main volume (core Drive).
15
+ const DEFAULT_SHARE_TARGET_TYPES = [ShareTargetType.Folder, ShareTargetType.File, ShareTargetType.ProtonVendor];
16
+
12
17
  /**
13
18
  * Provides facade for the whole sharing module.
14
19
  *
@@ -24,8 +29,9 @@ export function initSharingModule(
24
29
  crypto: DriveCrypto,
25
30
  sharesService: SharesService,
26
31
  nodesService: NodesService,
32
+ shareTargetTypes: ShareTargetType[] = DEFAULT_SHARE_TARGET_TYPES,
27
33
  ) {
28
- const api = new SharingAPIService(telemetry.getLogger('sharing-api'), apiService);
34
+ const api = new SharingAPIService(telemetry.getLogger('sharing-api'), apiService, shareTargetTypes);
29
35
  const cache = new SharingCache(driveEntitiesCache);
30
36
  const cryptoService = new SharingCryptoService(telemetry, crypto, account, sharesService);
31
37
  const sharingAccess = new SharingAccess(api, cache, cryptoService, sharesService, nodesService);
@@ -78,7 +78,8 @@ export function initSharingPublicNodesModule(
78
78
  publicShareKey: PrivateKey,
79
79
  publicRootNodeUid: string,
80
80
  ) {
81
- const api = new NodeAPIService(telemetry.getLogger('nodes-api'), apiService);
81
+ const clientUid = undefined; // No client UID for public context yet.
82
+ const api = new NodeAPIService(telemetry.getLogger('nodes-api'), apiService, clientUid);
82
83
  const cache = new NodesCache(telemetry.getLogger('nodes-cache'), driveEntitiesCache);
83
84
  const cryptoCache = new NodesCryptoCache(telemetry.getLogger('nodes-cache'), driveCryptoCache);
84
85
  const cryptoReporter = new SharingPublicCryptoReporter(telemetry);
@@ -6,13 +6,6 @@ import { splitNodeUid, makeNodeUid, splitNodeRevisionUid, makeNodeRevisionUid }
6
6
  import { UploadTokens } from './interface';
7
7
  import { ThumbnailType } from '../../interface';
8
8
 
9
- type PostCheckAvailableHashesRequest = Extract<
10
- drivePaths['/drive/v2/volumes/{volumeID}/links/{linkID}/checkAvailableHashes']['post']['requestBody'],
11
- { content: object }
12
- >['content']['application/json'];
13
- type PostCheckAvailableHashesResponse =
14
- drivePaths['/drive/v2/volumes/{volumeID}/links/{linkID}/checkAvailableHashes']['post']['responses']['200']['content']['application/json'];
15
-
16
9
  type PostCreateDraftRequest = Extract<
17
10
  drivePaths['/drive/v2/volumes/{volumeID}/files']['post']['requestBody'],
18
11
  { content: object }
@@ -60,38 +53,6 @@ export class UploadAPIService {
60
53
  this.clientUid = clientUid;
61
54
  }
62
55
 
63
- async checkAvailableHashes(
64
- parentNodeUid: string,
65
- hashes: string[],
66
- ): Promise<{
67
- availalbleHashes: string[];
68
- pendingHashes: {
69
- hash: string;
70
- nodeUid: string;
71
- revisionUid: string;
72
- clientUid?: string;
73
- }[];
74
- }> {
75
- const { volumeId, nodeId: parentNodeId } = splitNodeUid(parentNodeUid);
76
- const result = await this.apiService.post<PostCheckAvailableHashesRequest, PostCheckAvailableHashesResponse>(
77
- `drive/v2/volumes/${volumeId}/links/${parentNodeId}/checkAvailableHashes`,
78
- {
79
- Hashes: hashes,
80
- ClientUID: this.clientUid ? [this.clientUid] : null,
81
- },
82
- );
83
-
84
- return {
85
- availalbleHashes: result.AvailableHashes,
86
- pendingHashes: result.PendingHashes.map((hash) => ({
87
- hash: hash.Hash,
88
- nodeUid: makeNodeUid(volumeId, hash.LinkID),
89
- revisionUid: makeNodeRevisionUid(volumeId, hash.LinkID, hash.RevisionID),
90
- clientUid: hash.ClientUID || undefined,
91
- })),
92
- };
93
- }
94
-
95
56
  async createDraft(
96
57
  parentNodeUid: string,
97
58
  node: {
@@ -40,15 +40,6 @@ export class UploadCryptoService {
40
40
  };
41
41
  }
42
42
 
43
- async generateNameHashes(parentHashKey: Uint8Array, names: string[]): Promise<{ name: string; hash: string }[]> {
44
- return Promise.all(
45
- names.map(async (name) => ({
46
- name,
47
- hash: await this.driveCrypto.generateLookupHash(name, parentHashKey),
48
- })),
49
- );
50
- }
51
-
52
43
  async encryptThumbnail(
53
44
  nodeRevisionDraftKeys: NodeRevisionDraftKeys,
54
45
  thumbnail: Thumbnail,
@@ -176,11 +176,6 @@ export class FileUploader extends Uploader {
176
176
  blockVerifier,
177
177
  };
178
178
  }
179
-
180
- async getAvailableName(): Promise<string> {
181
- const availableName = await this.manager.findAvailableName(this.parentFolderUid, this.name);
182
- return availableName;
183
- }
184
179
  }
185
180
 
186
181
  /**
@@ -26,10 +26,6 @@ describe('UploadManager', () => {
26
26
  nodeRevisionUid: 'newNode:nodeRevisionUid',
27
27
  }),
28
28
  deleteDraft: jest.fn(),
29
- checkAvailableHashes: jest.fn().mockResolvedValue({
30
- availalbleHashes: ['name1Hash'],
31
- pendingHashes: [],
32
- }),
33
29
  commitDraftRevision: jest.fn(),
34
30
  };
35
31
  // @ts-expect-error No need to implement all methods for mocking
@@ -58,20 +54,6 @@ describe('UploadManager', () => {
58
54
  email: 'signatureEmail',
59
55
  },
60
56
  }),
61
- generateNameHashes: jest.fn().mockResolvedValue([
62
- {
63
- name: 'name1',
64
- hash: 'name1Hash',
65
- },
66
- {
67
- name: 'name2',
68
- hash: 'name2Hash',
69
- },
70
- {
71
- name: 'name3',
72
- hash: 'name3Hash',
73
- },
74
- ]),
75
57
  commitFile: jest.fn().mockResolvedValue({
76
58
  armoredManifestSignature: 'newNode:armoredManifestSignature',
77
59
  signatureEmail: 'signatureEmail',
@@ -280,53 +262,6 @@ describe('UploadManager', () => {
280
262
  });
281
263
  });
282
264
 
283
- describe('findAvailableName', () => {
284
- it('should find available name', async () => {
285
- apiService.checkAvailableHashes = jest.fn().mockImplementation(() => {
286
- return {
287
- availalbleHashes: ['name3Hash'],
288
- pendingHashes: [],
289
- };
290
- });
291
-
292
- const result = await manager.findAvailableName('parentUid', 'name');
293
- expect(result).toBe('name3');
294
- expect(apiService.checkAvailableHashes).toHaveBeenCalledTimes(1);
295
- expect(apiService.checkAvailableHashes).toHaveBeenCalledWith('parentUid', [
296
- 'name1Hash',
297
- 'name2Hash',
298
- 'name3Hash',
299
- ]);
300
- });
301
-
302
- it('should find available name with multiple pages', async () => {
303
- let firstCall = false;
304
- apiService.checkAvailableHashes = jest.fn().mockImplementation(() => {
305
- if (!firstCall) {
306
- firstCall = true;
307
- return {
308
- // First page has no available hashes
309
- availalbleHashes: [],
310
- pendingHashes: [],
311
- };
312
- }
313
- return {
314
- availalbleHashes: ['name3Hash'],
315
- pendingHashes: [],
316
- };
317
- });
318
-
319
- const result = await manager.findAvailableName('parentUid', 'name');
320
- expect(result).toBe('name3');
321
- expect(apiService.checkAvailableHashes).toHaveBeenCalledTimes(2);
322
- expect(apiService.checkAvailableHashes).toHaveBeenCalledWith('parentUid', [
323
- 'name1Hash',
324
- 'name2Hash',
325
- 'name3Hash',
326
- ]);
327
- });
328
- });
329
-
330
265
  describe('commit draft', () => {
331
266
  const nodeRevisionDraft = {
332
267
  nodeUid: 'newNode:nodeUid',
@@ -173,43 +173,6 @@ export class UploadManager {
173
173
  }
174
174
  }
175
175
 
176
- async findAvailableName(parentFolderUid: string, name: string): Promise<string> {
177
- const { hashKey: parentHashKey } = await this.nodesService.getNodeKeys(parentFolderUid);
178
- if (!parentHashKey) {
179
- throw new ValidationError(c('Error').t`Creating files in non-folders is not allowed`);
180
- }
181
-
182
- const [namePart, extension] = splitExtension(name);
183
-
184
- const batchSize = 10;
185
- let startIndex = 1;
186
- while (true) {
187
- const namesToCheck = [];
188
- for (let i = startIndex; i < startIndex + batchSize; i++) {
189
- namesToCheck.push(joinNameAndExtension(namePart, i, extension));
190
- }
191
-
192
- const hashesToCheck = await this.cryptoService.generateNameHashes(parentHashKey, namesToCheck);
193
-
194
- const { availalbleHashes } = await this.apiService.checkAvailableHashes(
195
- parentFolderUid,
196
- hashesToCheck.map(({ hash }) => hash),
197
- );
198
-
199
- if (!availalbleHashes.length) {
200
- startIndex += batchSize;
201
- continue;
202
- }
203
-
204
- const availableHash = hashesToCheck.find(({ hash }) => hash === availalbleHashes[0]);
205
- if (!availableHash) {
206
- throw Error('Backend returned unexpected hash');
207
- }
208
-
209
- return availableHash.name;
210
- }
211
- }
212
-
213
176
  async deleteDraftNode(nodeUid: string): Promise<void> {
214
177
  try {
215
178
  await this.apiService.deleteDraft(nodeUid);
@@ -294,30 +257,3 @@ export class UploadManager {
294
257
  }
295
258
  }
296
259
  }
297
-
298
- /**
299
- * Split a filename into `[name, extension]`
300
- */
301
- function splitExtension(filename = ''): [string, string] {
302
- const endIdx = filename.lastIndexOf('.');
303
- if (endIdx === -1 || endIdx === filename.length - 1) {
304
- return [filename, ''];
305
- }
306
- return [filename.slice(0, endIdx), filename.slice(endIdx + 1)];
307
- }
308
-
309
- /**
310
- * Join a filename into `name (index).extension`
311
- */
312
- function joinNameAndExtension(name: string, index: number, extension: string): string {
313
- if (!name && !extension) {
314
- return `(${index})`;
315
- }
316
- if (!name) {
317
- return `(${index}).${extension}`;
318
- }
319
- if (!extension) {
320
- return `${name} (${index})`;
321
- }
322
- return `${name} (${index}).${extension}`;
323
- }
@@ -24,7 +24,6 @@ import {
24
24
  UploadMetadata,
25
25
  FileDownloader,
26
26
  FileUploader,
27
- FileRevisionUploader,
28
27
  ThumbnailType,
29
28
  ThumbnailResult,
30
29
  SDKEvent,
@@ -144,6 +143,7 @@ export class ProtonDriveClient {
144
143
  account,
145
144
  cryptoModule,
146
145
  this.shares,
146
+ fullConfig.clientUid,
147
147
  );
148
148
  this.sharing = initSharingModule(
149
149
  telemetry,
@@ -703,6 +703,12 @@ export class ProtonDriveClient {
703
703
  return this.sharing.management.unshareNode(getUid(nodeUid), settings);
704
704
  }
705
705
 
706
+ /**
707
+ * Resend the invitation email to shared node.
708
+ *
709
+ * @param nodeUid - Node entity or its UID string.
710
+ * @param invitationUid - Invitation entity or its UID string.
711
+ */
706
712
  async resendInvitation(
707
713
  nodeUid: NodeOrUid,
708
714
  invitationUid: ProtonInvitationOrUid | NonProtonInvitationOrUid,
@@ -829,11 +835,24 @@ export class ProtonDriveClient {
829
835
  nodeUid: NodeOrUid,
830
836
  metadata: UploadMetadata,
831
837
  signal?: AbortSignal,
832
- ): Promise<FileRevisionUploader> {
838
+ ): Promise<FileUploader> {
833
839
  this.logger.info(`Getting file revision uploader for ${getUid(nodeUid)}`);
834
840
  return this.upload.getFileRevisionUploader(getUid(nodeUid), metadata, signal);
835
841
  }
836
842
 
843
+ /**
844
+ * Returns the available name for the file in the given parent folder.
845
+ *
846
+ * The function will return a name that includes the original name with the
847
+ * available index. The name is guaranteed to be unique in the parent folder.
848
+ *
849
+ * Example new name: `file (2).txt`.
850
+ */
851
+ async getAvailableName(parentFolderUid: NodeOrUid, name: string): Promise<string> {
852
+ this.logger.info(`Getting available name in folder ${getUid(parentFolderUid)}`);
853
+ return this.nodes.management.findAvailableName(getUid(parentFolderUid), name);
854
+ }
855
+
837
856
  /**
838
857
  * Iterates the devices of the user.
839
858
  *
@@ -10,12 +10,20 @@ import {
10
10
  MaybeNode,
11
11
  ThumbnailType,
12
12
  ThumbnailResult,
13
+ ShareNodeSettings,
14
+ ShareResult,
15
+ UnshareNodeSettings,
16
+ ProtonInvitationOrUid,
17
+ NonProtonInvitationOrUid,
18
+ ProtonInvitationWithNode,
19
+ NodeResult,
13
20
  } from './interface';
14
21
  import { getConfig } from './config';
15
22
  import { DriveCrypto } from './crypto';
16
23
  import { Telemetry } from './telemetry';
17
24
  import {
18
25
  convertInternalMissingNodeIterator,
26
+ convertInternalNode,
19
27
  convertInternalNodeIterator,
20
28
  convertInternalNodePromise,
21
29
  getUid,
@@ -25,7 +33,12 @@ import { DriveAPIService } from './internal/apiService';
25
33
  import { initDownloadModule } from './internal/download';
26
34
  import { DriveEventsService, DriveListener, EventSubscription } from './internal/events';
27
35
  import { initNodesModule } from './internal/nodes';
28
- import { initPhotosModule, initPhotoSharesModule, initPhotoUploadModule } from './internal/photos';
36
+ import {
37
+ PHOTOS_SHARE_TARGET_TYPES,
38
+ initPhotosModule,
39
+ initPhotoSharesModule,
40
+ initPhotoUploadModule,
41
+ } from './internal/photos';
29
42
  import { SDKEvents } from './internal/sdkEvents';
30
43
  import { initSharesModule } from './internal/shares';
31
44
  import { initSharingModule } from './internal/sharing';
@@ -72,7 +85,7 @@ export class ProtonDrivePhotosClient {
72
85
  if (!telemetry) {
73
86
  telemetry = new Telemetry();
74
87
  }
75
- this.logger = telemetry.getLogger('interface');
88
+ this.logger = telemetry.getLogger('photos-interface');
76
89
 
77
90
  const fullConfig = getConfig(config);
78
91
  this.sdkEvents = new SDKEvents(telemetry);
@@ -102,6 +115,7 @@ export class ProtonDrivePhotosClient {
102
115
  account,
103
116
  cryptoModule,
104
117
  this.photoShares,
118
+ fullConfig.clientUid,
105
119
  );
106
120
  this.photos = initPhotosModule(apiService, this.photoShares, this.nodes.access);
107
121
  this.sharing = initSharingModule(
@@ -112,6 +126,7 @@ export class ProtonDrivePhotosClient {
112
126
  cryptoModule,
113
127
  this.photoShares,
114
128
  this.nodes.access,
129
+ PHOTOS_SHARE_TARGET_TYPES,
115
130
  );
116
131
  this.download = initDownloadModule(
117
132
  telemetry,
@@ -204,6 +219,16 @@ export class ProtonDrivePhotosClient {
204
219
  yield* this.photos.timeline.iterateTimeline(signal);
205
220
  }
206
221
 
222
+ /**
223
+ * Iterates the trashed nodes.
224
+ *
225
+ * See `ProtonDriveClient.iterateTrashedNodes` for more information.
226
+ */
227
+ async *iterateTrashedNodes(signal?: AbortSignal): AsyncGenerator<MaybeNode> {
228
+ this.logger.info('Iterating trashed nodes');
229
+ yield* convertInternalNodeIterator(this.nodes.access.iterateTrashedNodes(signal));
230
+ }
231
+
207
232
  /**
208
233
  * Iterates the nodes by their UIDs.
209
234
  *
@@ -226,14 +251,163 @@ export class ProtonDrivePhotosClient {
226
251
  }
227
252
 
228
253
  /**
229
- * Iterates the albums.
254
+ * Rename the node.
230
255
  *
231
- * The output is not sorted and the order of the nodes is not guaranteed.
256
+ * See `ProtonDriveClient.renameNode` for more information.
232
257
  */
233
- async *iterateAlbums(signal?: AbortSignal): AsyncGenerator<MaybeNode> {
234
- this.logger.info('Iterating albums');
235
- // TODO: expose album type
236
- yield* convertInternalNodeIterator(this.photos.albums.iterateAlbums(signal));
258
+ async renameNode(nodeUid: NodeOrUid, newName: string): Promise<MaybeNode> {
259
+ this.logger.info(`Renaming node ${getUid(nodeUid)}`);
260
+ return convertInternalNodePromise(this.nodes.management.renameNode(getUid(nodeUid), newName));
261
+ }
262
+
263
+ /**
264
+ * Trash the nodes.
265
+ *
266
+ * See `ProtonDriveClient.trashNodes` for more information.
267
+ */
268
+ async *trashNodes(nodeUids: NodeOrUid[], signal?: AbortSignal): AsyncGenerator<NodeResult> {
269
+ this.logger.info(`Trashing ${nodeUids.length} nodes`);
270
+ yield* this.nodes.management.trashNodes(getUids(nodeUids), signal);
271
+ }
272
+
273
+ /**
274
+ * Restore the nodes from the trash to their original place.
275
+ *
276
+ * See `ProtonDriveClient.restoreNodes` for more information.
277
+ */
278
+ async *restoreNodes(nodeUids: NodeOrUid[], signal?: AbortSignal): AsyncGenerator<NodeResult> {
279
+ this.logger.info(`Restoring ${nodeUids.length} nodes`);
280
+ yield* this.nodes.management.restoreNodes(getUids(nodeUids), signal);
281
+ }
282
+
283
+ /**
284
+ * Delete the nodes permanently.
285
+ *
286
+ * See `ProtonDriveClient.deleteNodes` for more information.
287
+ */
288
+ async *deleteNodes(nodeUids: NodeOrUid[], signal?: AbortSignal): AsyncGenerator<NodeResult> {
289
+ this.logger.info(`Deleting ${nodeUids.length} nodes`);
290
+ yield* this.nodes.management.deleteNodes(getUids(nodeUids), signal);
291
+ }
292
+
293
+ /**
294
+ * Empty the trash.
295
+ *
296
+ * See `ProtonDriveClient.emptyTrash` for more information.
297
+ */
298
+ async emptyTrash(): Promise<void> {
299
+ this.logger.info('Emptying trash');
300
+ throw new Error('Method not implemented');
301
+ }
302
+
303
+ /**
304
+ * Iterates the nodes shared by the user.
305
+ *
306
+ * See `ProtonDriveClient.iterateSharedNodes` for more information.
307
+ */
308
+ async *iterateSharedNodes(signal?: AbortSignal): AsyncGenerator<MaybeNode> {
309
+ this.logger.info('Iterating shared nodes by me');
310
+ yield* convertInternalNodeIterator(this.sharing.access.iterateSharedNodes(signal));
311
+ }
312
+
313
+ /**
314
+ * Iterates the nodes shared with the user.
315
+ *
316
+ * See `ProtonDriveClient.iterateSharedNodesWithMe` for more information.
317
+ */
318
+ async *iterateSharedNodesWithMe(signal?: AbortSignal): AsyncGenerator<MaybeNode> {
319
+ this.logger.info('Iterating shared nodes with me');
320
+
321
+ for await (const node of this.sharing.access.iterateSharedNodesWithMe(signal)) {
322
+ yield convertInternalNode(node);
323
+ }
324
+ }
325
+
326
+ /**
327
+ * Leave shared node that was previously shared with the user.
328
+ *
329
+ * See `ProtonDriveClient.leaveSharedNode` for more information.
330
+ */
331
+ async leaveSharedNode(nodeUid: NodeOrUid): Promise<void> {
332
+ this.logger.info(`Leaving shared node with me ${getUid(nodeUid)}`);
333
+ await this.sharing.access.removeSharedNodeWithMe(getUid(nodeUid));
334
+ }
335
+
336
+ /**
337
+ * Iterates the invitations to shared nodes.
338
+ *
339
+ * See `ProtonDriveClient.iterateInvitations` for more information.
340
+ */
341
+ async *iterateInvitations(signal?: AbortSignal): AsyncGenerator<ProtonInvitationWithNode> {
342
+ this.logger.info('Iterating invitations');
343
+ yield* this.sharing.access.iterateInvitations(signal);
344
+ }
345
+
346
+ /**
347
+ * Accept the invitation to the shared node.
348
+ *
349
+ * See `ProtonDriveClient.acceptInvitation` for more information.
350
+ */
351
+ async acceptInvitation(invitationUid: ProtonInvitationOrUid): Promise<void> {
352
+ this.logger.info(`Accepting invitation ${getUid(invitationUid)}`);
353
+ await this.sharing.access.acceptInvitation(getUid(invitationUid));
354
+ }
355
+
356
+ /**
357
+ * Reject the invitation to the shared node.
358
+ *
359
+ * See `ProtonDriveClient.rejectInvitation` for more information.
360
+ */
361
+ async rejectInvitation(invitationUid: ProtonInvitationOrUid): Promise<void> {
362
+ this.logger.info(`Rejecting invitation ${getUid(invitationUid)}`);
363
+ await this.sharing.access.rejectInvitation(getUid(invitationUid));
364
+ }
365
+
366
+ /**
367
+ * Get sharing info of the node.
368
+ *
369
+ * See `ProtonDriveClient.getSharingInfo` for more information.
370
+ */
371
+ async getSharingInfo(nodeUid: NodeOrUid): Promise<ShareResult | undefined> {
372
+ this.logger.info(`Getting sharing info for ${getUid(nodeUid)}`);
373
+ return this.sharing.management.getSharingInfo(getUid(nodeUid));
374
+ }
375
+
376
+ /**
377
+ * Share or update sharing of the node.
378
+ *
379
+ * See `ProtonDriveClient.shareNode` for more information.
380
+ */
381
+ async shareNode(nodeUid: NodeOrUid, settings: ShareNodeSettings): Promise<ShareResult> {
382
+ this.logger.info(`Sharing node ${getUid(nodeUid)}`);
383
+ return this.sharing.management.shareNode(getUid(nodeUid), settings);
384
+ }
385
+
386
+ /**
387
+ * Unshare the node, completely or partially.
388
+ *
389
+ * See `ProtonDriveClient.unshareNode` for more information.
390
+ */
391
+ async unshareNode(nodeUid: NodeOrUid, settings?: UnshareNodeSettings): Promise<ShareResult | undefined> {
392
+ if (!settings) {
393
+ this.logger.info(`Unsharing node ${getUid(nodeUid)}`);
394
+ } else {
395
+ this.logger.info(`Partially unsharing ${getUid(nodeUid)}`);
396
+ }
397
+ return this.sharing.management.unshareNode(getUid(nodeUid), settings);
398
+ }
399
+
400
+ /**
401
+ * Resend the invitation email to shared node.
402
+ *
403
+ * See `ProtonDriveClient.resendInvitation` for more information.
404
+ */
405
+ async resendInvitation(
406
+ nodeUid: NodeOrUid,
407
+ invitationUid: ProtonInvitationOrUid | NonProtonInvitationOrUid,
408
+ ): Promise<void> {
409
+ this.logger.info(`Resending invitation ${getUid(invitationUid)}`);
410
+ return this.sharing.management.resendInvitationEmail(getUid(nodeUid), getUid(invitationUid));
237
411
  }
238
412
 
239
413
  /**
@@ -279,4 +453,15 @@ export class ProtonDrivePhotosClient {
279
453
  const parentFolderUid = await this.nodes.access.getVolumeRootFolder();
280
454
  return this.upload.getFileUploader(getUid(parentFolderUid), name, metadata, signal);
281
455
  }
456
+
457
+ /**
458
+ * Iterates the albums.
459
+ *
460
+ * The output is not sorted and the order of the nodes is not guaranteed.
461
+ */
462
+ async *iterateAlbums(signal?: AbortSignal): AsyncGenerator<MaybeNode> {
463
+ this.logger.info('Iterating albums');
464
+ // TODO: expose album type
465
+ yield* convertInternalNodeIterator(this.photos.albums.iterateAlbums(signal));
466
+ }
282
467
  }