@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.
- package/dist/interface/index.d.ts +1 -1
- package/dist/interface/upload.d.ts +1 -12
- package/dist/internal/nodes/apiService.d.ts +11 -1
- package/dist/internal/nodes/apiService.js +20 -1
- package/dist/internal/nodes/apiService.js.map +1 -1
- package/dist/internal/nodes/apiService.test.js +1 -1
- package/dist/internal/nodes/apiService.test.js.map +1 -1
- package/dist/internal/nodes/cryptoService.d.ts +4 -0
- package/dist/internal/nodes/cryptoService.js +6 -0
- package/dist/internal/nodes/cryptoService.js.map +1 -1
- package/dist/internal/nodes/index.d.ts +1 -1
- package/dist/internal/nodes/index.js +2 -2
- package/dist/internal/nodes/index.js.map +1 -1
- package/dist/internal/nodes/index.test.js +1 -1
- package/dist/internal/nodes/index.test.js.map +1 -1
- package/dist/internal/nodes/nodeName.d.ts +8 -0
- package/dist/internal/nodes/nodeName.js +30 -0
- package/dist/internal/nodes/nodeName.js.map +1 -0
- package/dist/internal/nodes/nodeName.test.d.ts +1 -0
- package/dist/internal/nodes/nodeName.test.js +50 -0
- package/dist/internal/nodes/nodeName.test.js.map +1 -0
- package/dist/internal/nodes/nodesManagement.d.ts +1 -0
- package/dist/internal/nodes/nodesManagement.js +30 -1
- package/dist/internal/nodes/nodesManagement.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.test.js +61 -0
- package/dist/internal/nodes/nodesManagement.test.js.map +1 -1
- package/dist/internal/photos/index.d.ts +2 -0
- package/dist/internal/photos/index.js +4 -0
- package/dist/internal/photos/index.js.map +1 -1
- package/dist/internal/shares/index.d.ts +1 -0
- package/dist/internal/shares/index.js +3 -0
- package/dist/internal/shares/index.js.map +1 -1
- package/dist/internal/shares/interface.d.ts +8 -0
- package/dist/internal/shares/interface.js +10 -1
- package/dist/internal/shares/interface.js.map +1 -1
- package/dist/internal/sharing/apiService.d.ts +3 -1
- package/dist/internal/sharing/apiService.js +16 -12
- package/dist/internal/sharing/apiService.js.map +1 -1
- package/dist/internal/sharing/index.d.ts +2 -1
- package/dist/internal/sharing/index.js +6 -2
- package/dist/internal/sharing/index.js.map +1 -1
- package/dist/internal/sharingPublic/index.js +2 -1
- package/dist/internal/sharingPublic/index.js.map +1 -1
- package/dist/internal/upload/apiService.d.ts +0 -9
- package/dist/internal/upload/apiService.js +0 -16
- package/dist/internal/upload/apiService.js.map +1 -1
- package/dist/internal/upload/cryptoService.d.ts +0 -4
- package/dist/internal/upload/cryptoService.js +0 -6
- package/dist/internal/upload/cryptoService.js.map +1 -1
- package/dist/internal/upload/fileUploader.d.ts +0 -1
- package/dist/internal/upload/fileUploader.js +0 -4
- package/dist/internal/upload/fileUploader.js.map +1 -1
- package/dist/internal/upload/manager.d.ts +0 -1
- package/dist/internal/upload/manager.js +0 -51
- package/dist/internal/upload/manager.js.map +1 -1
- package/dist/internal/upload/manager.test.js +0 -61
- package/dist/internal/upload/manager.test.js.map +1 -1
- package/dist/protonDriveClient.d.ts +17 -2
- package/dist/protonDriveClient.js +19 -1
- package/dist/protonDriveClient.js.map +1 -1
- package/dist/protonDrivePhotosClient.d.ts +100 -4
- package/dist/protonDrivePhotosClient.js +160 -9
- package/dist/protonDrivePhotosClient.js.map +1 -1
- package/package.json +1 -1
- package/src/interface/index.ts +1 -1
- package/src/interface/upload.ts +1 -13
- package/src/internal/nodes/apiService.test.ts +1 -1
- package/src/internal/nodes/apiService.ts +42 -0
- package/src/internal/nodes/cryptoService.ts +9 -0
- package/src/internal/nodes/index.test.ts +1 -0
- package/src/internal/nodes/index.ts +2 -1
- package/src/internal/nodes/nodeName.test.ts +57 -0
- package/src/internal/nodes/nodeName.ts +26 -0
- package/src/internal/nodes/nodesManagement.test.ts +65 -0
- package/src/internal/nodes/nodesManagement.ts +43 -1
- package/src/internal/photos/index.ts +4 -0
- package/src/internal/shares/index.ts +1 -0
- package/src/internal/shares/interface.ts +9 -0
- package/src/internal/sharing/apiService.ts +15 -12
- package/src/internal/sharing/index.ts +7 -1
- package/src/internal/sharingPublic/index.ts +2 -1
- package/src/internal/upload/apiService.ts +0 -39
- package/src/internal/upload/cryptoService.ts +0 -9
- package/src/internal/upload/fileUploader.ts +0 -5
- package/src/internal/upload/manager.test.ts +0 -65
- package/src/internal/upload/manager.ts +0 -64
- package/src/protonDriveClient.ts +21 -2
- 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 (!
|
|
174
|
-
this.logger.
|
|
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?${
|
|
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 (!
|
|
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
|
|
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
|
-
}
|
package/src/protonDriveClient.ts
CHANGED
|
@@ -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<
|
|
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 {
|
|
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
|
-
*
|
|
254
|
+
* Rename the node.
|
|
230
255
|
*
|
|
231
|
-
*
|
|
256
|
+
* See `ProtonDriveClient.renameNode` for more information.
|
|
232
257
|
*/
|
|
233
|
-
async
|
|
234
|
-
this.logger.info(
|
|
235
|
-
|
|
236
|
-
|
|
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
|
}
|