@protontech/drive-sdk 0.7.2 → 0.8.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/crypto/driveCrypto.js +1 -1
- package/dist/crypto/driveCrypto.js.map +1 -1
- package/dist/crypto/interface.d.ts +3 -1
- package/dist/crypto/openPGPCrypto.d.ts +4 -1
- package/dist/crypto/openPGPCrypto.js +2 -1
- package/dist/crypto/openPGPCrypto.js.map +1 -1
- package/dist/interface/account.d.ts +6 -0
- package/dist/internal/apiService/driveTypes.d.ts +197 -22
- package/dist/internal/nodes/apiService.d.ts +1 -1
- package/dist/internal/nodes/apiService.js +2 -2
- package/dist/internal/nodes/apiService.js.map +1 -1
- package/dist/internal/nodes/cryptoService.d.ts +1 -0
- package/dist/internal/nodes/cryptoService.js +28 -4
- package/dist/internal/nodes/cryptoService.js.map +1 -1
- package/dist/internal/nodes/cryptoService.test.js +70 -2
- package/dist/internal/nodes/cryptoService.test.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.d.ts +1 -1
- package/dist/internal/nodes/nodesManagement.js +1 -1
- package/dist/internal/nodes/nodesManagement.js.map +1 -1
- package/dist/internal/photos/timeline.d.ts +1 -1
- package/dist/internal/photos/timeline.js +4 -4
- package/dist/internal/photos/timeline.js.map +1 -1
- package/dist/internal/photos/timeline.test.js +34 -7
- package/dist/internal/photos/timeline.test.js.map +1 -1
- package/dist/internal/shares/apiService.js +2 -0
- package/dist/internal/shares/apiService.js.map +1 -1
- package/dist/internal/sharingPublic/nodes.d.ts +1 -1
- package/dist/internal/sharingPublic/nodes.js +2 -2
- package/dist/internal/sharingPublic/nodes.js.map +1 -1
- package/dist/protonDriveClient.d.ts +1 -1
- package/dist/protonDriveClient.js +2 -2
- package/dist/protonDriveClient.js.map +1 -1
- package/dist/protonDrivePhotosClient.d.ts +20 -0
- package/dist/protonDrivePhotosClient.js +25 -2
- package/dist/protonDrivePhotosClient.js.map +1 -1
- package/dist/protonDrivePublicLinkClient.d.ts +3 -1
- package/dist/protonDrivePublicLinkClient.js +4 -2
- package/dist/protonDrivePublicLinkClient.js.map +1 -1
- package/package.json +1 -1
- package/src/crypto/driveCrypto.ts +1 -0
- package/src/crypto/interface.ts +1 -0
- package/src/crypto/openPGPCrypto.ts +3 -0
- package/src/interface/account.ts +6 -0
- package/src/internal/apiService/driveTypes.ts +197 -22
- package/src/internal/nodes/apiService.ts +13 -6
- package/src/internal/nodes/cryptoService.test.ts +113 -2
- package/src/internal/nodes/cryptoService.ts +53 -8
- package/src/internal/nodes/nodesManagement.ts +1 -1
- package/src/internal/photos/timeline.test.ts +39 -7
- package/src/internal/photos/timeline.ts +4 -4
- package/src/internal/shares/apiService.ts +3 -1
- package/src/internal/sharingPublic/nodes.ts +2 -2
- package/src/protonDriveClient.ts +2 -2
- package/src/protonDrivePhotosClient.ts +26 -2
- package/src/protonDrivePublicLinkClient.ts +4 -2
|
@@ -45,15 +45,15 @@ describe('PhotosTimeline', () => {
|
|
|
45
45
|
timeline = new PhotosTimeline(logger, apiService, driveCrypto, photoShares, nodesService);
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
-
describe('
|
|
48
|
+
describe('findPhotoDuplicates', () => {
|
|
49
49
|
it('should not call sha1 callback when there is no name hash match', async () => {
|
|
50
50
|
const generateSha1 = jest.fn();
|
|
51
51
|
apiService.checkPhotoDuplicates = jest.fn().mockResolvedValue([]);
|
|
52
52
|
driveCrypto.generateLookupHash = jest.fn().mockResolvedValue(nameHash);
|
|
53
53
|
|
|
54
|
-
const result = await timeline.
|
|
54
|
+
const result = await timeline.findPhotoDuplicates(name, generateSha1);
|
|
55
55
|
|
|
56
|
-
expect(result).
|
|
56
|
+
expect(result).toEqual([]);
|
|
57
57
|
expect(generateSha1).not.toHaveBeenCalled();
|
|
58
58
|
expect(photoShares.getRootIDs).toHaveBeenCalled();
|
|
59
59
|
expect(nodesService.getNodeKeys).toHaveBeenCalledWith(rootNodeUid);
|
|
@@ -76,9 +76,9 @@ describe('PhotosTimeline', () => {
|
|
|
76
76
|
.mockResolvedValueOnce(nameHash)
|
|
77
77
|
.mockResolvedValueOnce(contentHash);
|
|
78
78
|
|
|
79
|
-
const result = await timeline.
|
|
79
|
+
const result = await timeline.findPhotoDuplicates(name, generateSha1);
|
|
80
80
|
|
|
81
|
-
expect(result).
|
|
81
|
+
expect(result).toEqual([]);
|
|
82
82
|
expect(generateSha1).toHaveBeenCalledTimes(1);
|
|
83
83
|
expect(driveCrypto.generateLookupHash).toHaveBeenCalledTimes(2);
|
|
84
84
|
expect(driveCrypto.generateLookupHash).toHaveBeenNthCalledWith(1, name, hashKey);
|
|
@@ -102,15 +102,47 @@ describe('PhotosTimeline', () => {
|
|
|
102
102
|
.mockResolvedValueOnce(nameHash)
|
|
103
103
|
.mockResolvedValueOnce(contentHash);
|
|
104
104
|
|
|
105
|
-
const result = await timeline.
|
|
105
|
+
const result = await timeline.findPhotoDuplicates(name, generateSha1);
|
|
106
106
|
|
|
107
|
-
expect(result).
|
|
107
|
+
expect(result).toEqual([nodeUid1]);
|
|
108
108
|
expect(generateSha1).toHaveBeenCalledTimes(1);
|
|
109
109
|
expect(logger.debug).toHaveBeenCalledTimes(1);
|
|
110
110
|
expect(logger.debug).toHaveBeenCalledWith(
|
|
111
111
|
`Duplicate photo found: name hash: ${nameHash}, content hash: ${contentHash}, node uids: ${nodeUid1}`,
|
|
112
112
|
);
|
|
113
113
|
});
|
|
114
|
+
|
|
115
|
+
it('should return multiple node UIDs when multiple duplicates match', async () => {
|
|
116
|
+
const generateSha1 = jest.fn().mockResolvedValue(sha1);
|
|
117
|
+
const nodeUid1 = 'volumeId~node1';
|
|
118
|
+
const nodeUid2 = 'volumeId~node2';
|
|
119
|
+
const duplicates = [
|
|
120
|
+
{
|
|
121
|
+
nameHash: nameHash,
|
|
122
|
+
contentHash: contentHash,
|
|
123
|
+
nodeUid: nodeUid1,
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
nameHash: nameHash,
|
|
127
|
+
contentHash: contentHash,
|
|
128
|
+
nodeUid: nodeUid2,
|
|
129
|
+
},
|
|
130
|
+
];
|
|
131
|
+
apiService.checkPhotoDuplicates = jest.fn().mockResolvedValue(duplicates);
|
|
132
|
+
driveCrypto.generateLookupHash = jest
|
|
133
|
+
.fn()
|
|
134
|
+
.mockResolvedValueOnce(nameHash)
|
|
135
|
+
.mockResolvedValueOnce(contentHash);
|
|
136
|
+
|
|
137
|
+
const result = await timeline.findPhotoDuplicates(name, generateSha1);
|
|
138
|
+
|
|
139
|
+
expect(result).toEqual([nodeUid1, nodeUid2]);
|
|
140
|
+
expect(generateSha1).toHaveBeenCalledTimes(1);
|
|
141
|
+
expect(logger.debug).toHaveBeenCalledTimes(1);
|
|
142
|
+
expect(logger.debug).toHaveBeenCalledWith(
|
|
143
|
+
`Duplicate photo found: name hash: ${nameHash}, content hash: ${contentHash}, node uids: ${nodeUid1},${nodeUid2}`,
|
|
144
|
+
);
|
|
145
|
+
});
|
|
114
146
|
});
|
|
115
147
|
});
|
|
116
148
|
|
|
@@ -32,7 +32,7 @@ export class PhotosTimeline {
|
|
|
32
32
|
yield* this.apiService.iterateTimeline(volumeId, signal);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
async
|
|
35
|
+
async findPhotoDuplicates(name: string, generateSha1: () => Promise<string>, signal?: AbortSignal): Promise<string[]> {
|
|
36
36
|
const { volumeId, rootNodeId } = await this.photoShares.getRootIDs();
|
|
37
37
|
const rootNodeUid = makeNodeUid(volumeId, rootNodeId);
|
|
38
38
|
const { hashKey } = await this.nodesService.getNodeKeys(rootNodeUid);
|
|
@@ -44,7 +44,7 @@ export class PhotosTimeline {
|
|
|
44
44
|
const duplicates = await this.apiService.checkPhotoDuplicates(volumeId, [nameHash], signal);
|
|
45
45
|
|
|
46
46
|
if (duplicates.length === 0) {
|
|
47
|
-
return
|
|
47
|
+
return [];
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
// Generate the SHA1 only when there is any matching node hash to avoid
|
|
@@ -57,13 +57,13 @@ export class PhotosTimeline {
|
|
|
57
57
|
);
|
|
58
58
|
|
|
59
59
|
if (matchingDuplicates.length === 0) {
|
|
60
|
-
return
|
|
60
|
+
return [];
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
const nodeUids = matchingDuplicates.map((duplicate) => duplicate.nodeUid);
|
|
64
64
|
this.logger.debug(
|
|
65
65
|
`Duplicate photo found: name hash: ${nameHash}, content hash: ${contentHash}, node uids: ${nodeUids}`,
|
|
66
66
|
);
|
|
67
|
-
return
|
|
67
|
+
return nodeUids;
|
|
68
68
|
}
|
|
69
69
|
}
|
|
@@ -173,7 +173,7 @@ function convertSharePayload(response: GetShareResponse): EncryptedShare {
|
|
|
173
173
|
};
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
-
function convertShareTypeNumberToEnum(type: 1 | 2 | 3 | 4): ShareType {
|
|
176
|
+
function convertShareTypeNumberToEnum(type: 1 | 2 | 3 | 4 | 5): ShareType {
|
|
177
177
|
switch (type) {
|
|
178
178
|
case 1:
|
|
179
179
|
return ShareType.Main;
|
|
@@ -183,5 +183,7 @@ function convertShareTypeNumberToEnum(type: 1 | 2 | 3 | 4): ShareType {
|
|
|
183
183
|
return ShareType.Device;
|
|
184
184
|
case 4:
|
|
185
185
|
return ShareType.Photo;
|
|
186
|
+
case 5:
|
|
187
|
+
throw new Error('Organization shares are not supported yet');
|
|
186
188
|
}
|
|
187
189
|
}
|
|
@@ -87,10 +87,10 @@ export class SharingPublicNodesManagement extends NodesManagement {
|
|
|
87
87
|
super(apiService, cryptoCache, cryptoService, nodesAccess);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
async *
|
|
90
|
+
async *deleteMyNodes(nodeUids: string[], signal?: AbortSignal): AsyncGenerator<NodeResult> {
|
|
91
91
|
// Public link does not support trashing and deleting trashed nodes.
|
|
92
92
|
// Instead, if user is owner, API allows directly deleting existing nodes.
|
|
93
|
-
for await (const result of this.apiService.
|
|
93
|
+
for await (const result of this.apiService.deleteMyNodes(nodeUids, signal)) {
|
|
94
94
|
if (result.ok) {
|
|
95
95
|
await this.nodesAccess.notifyNodeDeleted(result.uid);
|
|
96
96
|
}
|
package/src/protonDriveClient.ts
CHANGED
|
@@ -495,7 +495,7 @@ export class ProtonDriveClient {
|
|
|
495
495
|
}
|
|
496
496
|
|
|
497
497
|
/**
|
|
498
|
-
* Delete the nodes permanently.
|
|
498
|
+
* Delete the trashed nodes permanently. Only the owner can do that.
|
|
499
499
|
*
|
|
500
500
|
* The operation is performed in batches and the results are yielded
|
|
501
501
|
* as they are available. Order of the results is not guaranteed.
|
|
@@ -509,7 +509,7 @@ export class ProtonDriveClient {
|
|
|
509
509
|
*/
|
|
510
510
|
async *deleteNodes(nodeUids: NodeOrUid[], signal?: AbortSignal): AsyncGenerator<NodeResult> {
|
|
511
511
|
this.logger.info(`Deleting ${nodeUids.length} nodes`);
|
|
512
|
-
yield* this.nodes.management.
|
|
512
|
+
yield* this.nodes.management.deleteTrashedNodes(getUids(nodeUids), signal);
|
|
513
513
|
}
|
|
514
514
|
|
|
515
515
|
async emptyTrash(): Promise<void> {
|
|
@@ -295,7 +295,7 @@ export class ProtonDrivePhotosClient {
|
|
|
295
295
|
*/
|
|
296
296
|
async *deleteNodes(nodeUids: NodeOrUid[], signal?: AbortSignal): AsyncGenerator<NodeResult> {
|
|
297
297
|
this.logger.info(`Deleting ${nodeUids.length} nodes`);
|
|
298
|
-
yield* this.nodes.management.
|
|
298
|
+
yield * this.nodes.management.deleteTrashedNodes(getUids(nodeUids), signal);
|
|
299
299
|
}
|
|
300
300
|
|
|
301
301
|
/**
|
|
@@ -479,10 +479,34 @@ export class ProtonDrivePhotosClient {
|
|
|
479
479
|
* @param generateSha1 - A callback to generate the hex string representation of the SHA1 of the photo content.
|
|
480
480
|
* @param signal - An optional abort signal to cancel the operation.
|
|
481
481
|
* @returns True if the photo already exists in the timeline, false otherwise.
|
|
482
|
+
* @deprecated Use `findPhotoDuplicates` instead to get the node UIDs of duplicate photos.
|
|
482
483
|
*/
|
|
483
484
|
async isDuplicatePhoto(name: string, generateSha1: () => Promise<string>, signal?: AbortSignal): Promise<boolean> {
|
|
484
485
|
this.logger.info(`Checking if photo is a duplicate`);
|
|
485
|
-
return this.photos.timeline.
|
|
486
|
+
return this.photos.timeline.findPhotoDuplicates(name, generateSha1, signal).then(nodeUids => nodeUids.length !== 0);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Find duplicate photos by name and content.
|
|
491
|
+
*
|
|
492
|
+
* For given photo name, find existing photos with the same name
|
|
493
|
+
* in the timeline and check if the photo content is also the same.
|
|
494
|
+
* Only the same name is not considered as duplicate photo because
|
|
495
|
+
* it is expected that there are photos with the same name (e.g.,
|
|
496
|
+
* date as a name from multiple cameras, or rolling number).
|
|
497
|
+
*
|
|
498
|
+
* The function accepts a callback to generate the SHA1 and it is
|
|
499
|
+
* called only when there is any matching node name hash to avoid
|
|
500
|
+
* computation for every node if its not necessary.
|
|
501
|
+
*
|
|
502
|
+
* @param name - The name of the photo to check for duplicates.
|
|
503
|
+
* @param generateSha1 - A callback to generate the hex string representation of the SHA1 of the photo content.
|
|
504
|
+
* @param signal - An optional abort signal to cancel the operation.
|
|
505
|
+
* @returns An array of node UIDs of duplicate photos. Empty array if no duplicates found.
|
|
506
|
+
*/
|
|
507
|
+
async findPhotoDuplicates(name: string, generateSha1: () => Promise<string>, signal?: AbortSignal): Promise<string[]> {
|
|
508
|
+
this.logger.info(`Checking if photo have duplicates`);
|
|
509
|
+
return this.photos.timeline.findPhotoDuplicates(name, generateSha1, signal);
|
|
486
510
|
}
|
|
487
511
|
|
|
488
512
|
/**
|
|
@@ -233,13 +233,15 @@ export class ProtonDrivePublicLinkClient {
|
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
/**
|
|
236
|
-
* Delete
|
|
236
|
+
* Delete own nodes permanently. It skips the trash and allows to delete
|
|
237
|
+
* only nodes that are owned by the user. For anonymous files, this method
|
|
238
|
+
* allows to delete them only in the same session.
|
|
237
239
|
*
|
|
238
240
|
* See `ProtonDriveClient.deleteNodes` for more information.
|
|
239
241
|
*/
|
|
240
242
|
async *deleteNodes(nodeUids: NodeOrUid[], signal?: AbortSignal): AsyncGenerator<NodeResult> {
|
|
241
243
|
this.logger.info(`Deleting ${nodeUids.length} nodes`);
|
|
242
|
-
yield* this.sharingPublic.nodes.management.
|
|
244
|
+
yield* this.sharingPublic.nodes.management.deleteMyNodes(getUids(nodeUids), signal);
|
|
243
245
|
}
|
|
244
246
|
|
|
245
247
|
/**
|