@protontech/drive-sdk 0.3.2 → 0.4.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/internal/apiService/errorCodes.d.ts +1 -0
- package/dist/internal/apiService/errors.d.ts +3 -0
- package/dist/internal/apiService/errors.js +7 -1
- package/dist/internal/apiService/errors.js.map +1 -1
- package/dist/internal/devices/interface.d.ts +1 -1
- package/dist/internal/devices/manager.js +1 -1
- package/dist/internal/devices/manager.js.map +1 -1
- package/dist/internal/devices/manager.test.js +3 -3
- package/dist/internal/devices/manager.test.js.map +1 -1
- package/dist/internal/events/apiService.js +1 -1
- package/dist/internal/events/apiService.js.map +1 -1
- package/dist/internal/events/coreEventManager.js +1 -1
- package/dist/internal/events/coreEventManager.js.map +1 -1
- package/dist/internal/events/coreEventManager.test.js +18 -24
- package/dist/internal/events/coreEventManager.test.js.map +1 -1
- package/dist/internal/events/index.d.ts +3 -4
- package/dist/internal/events/index.js +4 -4
- package/dist/internal/events/index.js.map +1 -1
- package/dist/internal/events/interface.d.ts +3 -0
- package/dist/internal/nodes/apiService.d.ts +12 -3
- package/dist/internal/nodes/apiService.js +53 -13
- package/dist/internal/nodes/apiService.js.map +1 -1
- package/dist/internal/nodes/apiService.test.js +19 -2
- package/dist/internal/nodes/apiService.test.js.map +1 -1
- package/dist/internal/nodes/cryptoService.d.ts +1 -1
- package/dist/internal/nodes/cryptoService.js +1 -1
- package/dist/internal/nodes/cryptoService.js.map +1 -1
- package/dist/internal/nodes/cryptoService.test.js +4 -4
- package/dist/internal/nodes/cryptoService.test.js.map +1 -1
- package/dist/internal/nodes/errors.d.ts +4 -0
- package/dist/internal/nodes/errors.js +9 -0
- package/dist/internal/nodes/errors.js.map +1 -0
- package/dist/internal/nodes/index.test.js +1 -1
- package/dist/internal/nodes/index.test.js.map +1 -1
- package/dist/internal/nodes/interface.d.ts +4 -1
- package/dist/internal/nodes/nodesAccess.d.ts +3 -3
- package/dist/internal/nodes/nodesAccess.js +25 -15
- package/dist/internal/nodes/nodesAccess.js.map +1 -1
- package/dist/internal/nodes/nodesAccess.test.js +48 -8
- package/dist/internal/nodes/nodesAccess.test.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.d.ts +2 -0
- package/dist/internal/nodes/nodesManagement.js +86 -9
- package/dist/internal/nodes/nodesManagement.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.test.js +81 -5
- package/dist/internal/nodes/nodesManagement.test.js.map +1 -1
- package/dist/internal/photos/albums.d.ts +9 -7
- package/dist/internal/photos/albums.js +26 -13
- package/dist/internal/photos/albums.js.map +1 -1
- package/dist/internal/photos/apiService.d.ts +34 -3
- package/dist/internal/photos/apiService.js +96 -3
- package/dist/internal/photos/apiService.js.map +1 -1
- package/dist/internal/photos/index.d.ts +20 -4
- package/dist/internal/photos/index.js +30 -7
- package/dist/internal/photos/index.js.map +1 -1
- package/dist/internal/photos/interface.d.ts +25 -1
- package/dist/internal/photos/shares.d.ts +43 -0
- package/dist/internal/photos/shares.js +112 -0
- package/dist/internal/photos/shares.js.map +1 -0
- package/dist/internal/photos/timeline.d.ts +15 -0
- package/dist/internal/photos/timeline.js +22 -0
- package/dist/internal/photos/timeline.js.map +1 -0
- package/dist/internal/shares/manager.d.ts +1 -1
- package/dist/internal/shares/manager.js +4 -4
- package/dist/internal/shares/manager.js.map +1 -1
- package/dist/internal/shares/manager.test.js +7 -7
- package/dist/internal/shares/manager.test.js.map +1 -1
- package/dist/internal/sharing/interface.d.ts +1 -1
- package/dist/internal/sharing/sharingAccess.js +1 -1
- package/dist/internal/sharing/sharingAccess.js.map +1 -1
- package/dist/internal/sharing/sharingAccess.test.js +1 -1
- package/dist/internal/sharing/sharingAccess.test.js.map +1 -1
- package/dist/protonDriveClient.d.ts +20 -3
- package/dist/protonDriveClient.js +23 -4
- package/dist/protonDriveClient.js.map +1 -1
- package/dist/protonDrivePhotosClient.d.ts +86 -12
- package/dist/protonDrivePhotosClient.js +132 -29
- package/dist/protonDrivePhotosClient.js.map +1 -1
- package/package.json +1 -1
- package/src/internal/apiService/errorCodes.ts +1 -0
- package/src/internal/apiService/errors.ts +6 -0
- package/src/internal/devices/interface.ts +1 -1
- package/src/internal/devices/manager.test.ts +3 -3
- package/src/internal/devices/manager.ts +1 -1
- package/src/internal/events/apiService.ts +1 -1
- package/src/internal/events/coreEventManager.test.ts +21 -27
- package/src/internal/events/coreEventManager.ts +1 -1
- package/src/internal/events/index.ts +3 -4
- package/src/internal/events/interface.ts +4 -0
- package/src/internal/nodes/apiService.test.ts +35 -1
- package/src/internal/nodes/apiService.ts +103 -17
- package/src/internal/nodes/cryptoService.test.ts +8 -8
- package/src/internal/nodes/cryptoService.ts +1 -1
- package/src/internal/nodes/errors.ts +5 -0
- package/src/internal/nodes/index.test.ts +1 -1
- package/src/internal/nodes/interface.ts +5 -1
- package/src/internal/nodes/nodesAccess.test.ts +68 -8
- package/src/internal/nodes/nodesAccess.ts +42 -15
- package/src/internal/nodes/nodesManagement.test.ts +100 -5
- package/src/internal/nodes/nodesManagement.ts +100 -13
- package/src/internal/photos/albums.ts +31 -12
- package/src/internal/photos/apiService.ts +159 -4
- package/src/internal/photos/index.ts +54 -9
- package/src/internal/photos/interface.ts +23 -1
- package/src/internal/photos/shares.ts +134 -0
- package/src/internal/photos/timeline.ts +24 -0
- package/src/internal/shares/manager.test.ts +7 -7
- package/src/internal/shares/manager.ts +4 -4
- package/src/internal/sharing/interface.ts +1 -1
- package/src/internal/sharing/sharingAccess.test.ts +1 -1
- package/src/internal/sharing/sharingAccess.ts +1 -1
- package/src/protonDriveClient.ts +33 -4
- package/src/protonDrivePhotosClient.ts +211 -32
- package/dist/internal/photos/cache.d.ts +0 -6
- package/dist/internal/photos/cache.js +0 -15
- package/dist/internal/photos/cache.js.map +0 -1
- package/dist/internal/photos/photosTimeline.d.ts +0 -10
- package/dist/internal/photos/photosTimeline.js +0 -19
- package/dist/internal/photos/photosTimeline.js.map +0 -1
- package/src/internal/photos/cache.ts +0 -11
- package/src/internal/photos/photosTimeline.ts +0 -17
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { PrivateKey } from '../../crypto';
|
|
2
|
+
import { Logger, MetricVolumeType } from '../../interface';
|
|
3
|
+
import { NotFoundAPIError } from '../apiService';
|
|
4
|
+
import { SharesCache } from '../shares/cache';
|
|
5
|
+
import { SharesCryptoCache } from '../shares/cryptoCache';
|
|
6
|
+
import { SharesCryptoService } from '../shares/cryptoService';
|
|
7
|
+
import { EncryptedShare, VolumeShareNodeIDs } from '../shares/interface';
|
|
8
|
+
import { PhotosAPIService } from './apiService';
|
|
9
|
+
import { SharesService } from './interface';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Provides high-level actions for managing photo share.
|
|
13
|
+
*
|
|
14
|
+
* The photo share manager wraps the core share service, but uses photos volume
|
|
15
|
+
* instead of main volume. It provides the same interface so it can be used in
|
|
16
|
+
* the same way in various modules that use shares.
|
|
17
|
+
*/
|
|
18
|
+
export class PhotoSharesManager {
|
|
19
|
+
private photoRootIds?: VolumeShareNodeIDs;
|
|
20
|
+
|
|
21
|
+
constructor(
|
|
22
|
+
private logger: Logger,
|
|
23
|
+
private apiService: PhotosAPIService,
|
|
24
|
+
private cache: SharesCache,
|
|
25
|
+
private cryptoCache: SharesCryptoCache,
|
|
26
|
+
private cryptoService: SharesCryptoService,
|
|
27
|
+
private sharesService: SharesService,
|
|
28
|
+
) {
|
|
29
|
+
this.logger = logger;
|
|
30
|
+
this.apiService = apiService;
|
|
31
|
+
this.cache = cache;
|
|
32
|
+
this.cryptoCache = cryptoCache;
|
|
33
|
+
this.cryptoService = cryptoService;
|
|
34
|
+
this.sharesService = sharesService;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async getOwnVolumeIDs(): Promise<VolumeShareNodeIDs> {
|
|
38
|
+
if (this.photoRootIds) {
|
|
39
|
+
return this.photoRootIds;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const encryptedShare = await this.apiService.getPhotoShare();
|
|
44
|
+
|
|
45
|
+
// Once any place needs IDs for My files, it will most likely
|
|
46
|
+
// need also the keys for decrypting the tree. It is better to
|
|
47
|
+
// decrypt the share here right away.
|
|
48
|
+
const { share: myFilesShare, key } = await this.cryptoService.decryptRootShare(encryptedShare);
|
|
49
|
+
await this.cryptoCache.setShareKey(myFilesShare.shareId, key);
|
|
50
|
+
await this.cache.setVolume({
|
|
51
|
+
volumeId: myFilesShare.volumeId,
|
|
52
|
+
shareId: myFilesShare.shareId,
|
|
53
|
+
rootNodeId: myFilesShare.rootNodeId,
|
|
54
|
+
creatorEmail: encryptedShare.creatorEmail,
|
|
55
|
+
addressId: encryptedShare.addressId,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
this.photoRootIds = {
|
|
59
|
+
volumeId: myFilesShare.volumeId,
|
|
60
|
+
shareId: myFilesShare.shareId,
|
|
61
|
+
rootNodeId: myFilesShare.rootNodeId,
|
|
62
|
+
};
|
|
63
|
+
return this.photoRootIds;
|
|
64
|
+
} catch (error: unknown) {
|
|
65
|
+
if (error instanceof NotFoundAPIError) {
|
|
66
|
+
this.logger.warn('Active photo volume not found, creating a new one');
|
|
67
|
+
return this.createVolume();
|
|
68
|
+
}
|
|
69
|
+
this.logger.error('Failed to get active photo volume', error);
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private async createVolume(): Promise<VolumeShareNodeIDs> {
|
|
75
|
+
const address = await this.sharesService.getMyFilesShareMemberEmailKey();
|
|
76
|
+
const bootstrap = await this.cryptoService.generateVolumeBootstrap(address.addressKey);
|
|
77
|
+
const photoRootIds = await this.apiService.createPhotoVolume(
|
|
78
|
+
{
|
|
79
|
+
addressId: address.addressId,
|
|
80
|
+
addressKeyId: address.addressKeyId,
|
|
81
|
+
...bootstrap.shareKey.encrypted,
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
...bootstrap.rootNode.key.encrypted,
|
|
85
|
+
encryptedName: bootstrap.rootNode.encryptedName,
|
|
86
|
+
armoredHashKey: bootstrap.rootNode.armoredHashKey,
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
await this.cryptoCache.setShareKey(photoRootIds.shareId, bootstrap.shareKey.decrypted);
|
|
90
|
+
return photoRootIds;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async getSharePrivateKey(shareId: string): Promise<PrivateKey> {
|
|
94
|
+
return this.sharesService.getSharePrivateKey(shareId);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async getMyFilesShareMemberEmailKey(): Promise<{
|
|
98
|
+
email: string;
|
|
99
|
+
addressId: string;
|
|
100
|
+
addressKey: PrivateKey;
|
|
101
|
+
addressKeyId: string;
|
|
102
|
+
}> {
|
|
103
|
+
return this.sharesService.getMyFilesShareMemberEmailKey();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async getContextShareMemberEmailKey(shareId: string): Promise<{
|
|
107
|
+
email: string;
|
|
108
|
+
addressId: string;
|
|
109
|
+
addressKey: PrivateKey;
|
|
110
|
+
addressKeyId: string;
|
|
111
|
+
}> {
|
|
112
|
+
return this.sharesService.getContextShareMemberEmailKey(shareId);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async isOwnVolume(volumeId: string): Promise<boolean> {
|
|
116
|
+
const { volumeId: myVolumeId } = await this.getOwnVolumeIDs();
|
|
117
|
+
if (volumeId === myVolumeId) {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
return this.sharesService.isOwnVolume(volumeId);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async getVolumeMetricContext(volumeId: string): Promise<MetricVolumeType> {
|
|
124
|
+
const { volumeId: myVolumeId } = await this.getOwnVolumeIDs();
|
|
125
|
+
if (volumeId === myVolumeId) {
|
|
126
|
+
return MetricVolumeType.OwnVolume;
|
|
127
|
+
}
|
|
128
|
+
return this.sharesService.getVolumeMetricContext(volumeId);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async loadEncryptedShare(shareId: string): Promise<EncryptedShare> {
|
|
132
|
+
return this.sharesService.loadEncryptedShare(shareId);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { PhotosAPIService } from './apiService';
|
|
2
|
+
import { PhotoSharesManager } from './shares';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Provides access to the photo timeline.
|
|
6
|
+
*/
|
|
7
|
+
export class PhotosTimeline {
|
|
8
|
+
constructor(
|
|
9
|
+
private apiService: PhotosAPIService,
|
|
10
|
+
private photoShares: PhotoSharesManager,
|
|
11
|
+
) {
|
|
12
|
+
this.apiService = apiService;
|
|
13
|
+
this.photoShares = photoShares;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async* iterateTimeline(signal?: AbortSignal): AsyncGenerator<{
|
|
17
|
+
nodeUid: string;
|
|
18
|
+
captureTime: Date;
|
|
19
|
+
tags: number[];
|
|
20
|
+
}> {
|
|
21
|
+
const { volumeId } = await this.photoShares.getOwnVolumeIDs();
|
|
22
|
+
yield* this.apiService.iterateTimeline(volumeId, signal);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -50,7 +50,7 @@ describe('SharesManager', () => {
|
|
|
50
50
|
manager = new SharesManager(getMockLogger(), apiService, cache, cryptoCache, cryptoService, account);
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
-
describe('
|
|
53
|
+
describe('getOwnVolumeIDs', () => {
|
|
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.
|
|
75
|
-
const result = await manager.
|
|
74
|
+
await manager.getOwnVolumeIDs();
|
|
75
|
+
const result = await manager.getOwnVolumeIDs();
|
|
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.
|
|
106
|
+
const result = await manager.getOwnVolumeIDs();
|
|
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.
|
|
116
|
+
await expect(manager.getOwnVolumeIDs()).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, '
|
|
145
|
+
jest.spyOn(manager, 'getOwnVolumeIDs').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, '
|
|
161
|
+
jest.spyOn(manager, 'getOwnVolumeIDs').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
|
|
49
|
+
async getOwnVolumeIDs(): 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.
|
|
143
|
+
const { volumeId } = await this.getOwnVolumeIDs();
|
|
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.
|
|
199
|
+
return (await this.getOwnVolumeIDs()).volumeId === volumeId;
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
async getVolumeMetricContext(volumeId: string): Promise<MetricVolumeType> {
|
|
203
|
-
const { volumeId: myVolumeId } = await this.
|
|
203
|
+
const { volumeId: myVolumeId } = await this.getOwnVolumeIDs();
|
|
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
|
-
|
|
145
|
+
getOwnVolumeIDs(): 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
|
-
|
|
96
|
+
getOwnVolumeIDs: 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.
|
|
43
|
+
const { volumeId } = await this.sharesService.getOwnVolumeIDs();
|
|
44
44
|
const nodeUidsIterator = this.apiService.iterateSharedNodeUids(volumeId, signal);
|
|
45
45
|
yield* this.iterateSharedNodesFromAPI(
|
|
46
46
|
nodeUidsIterator,
|
package/src/protonDriveClient.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
ThumbnailType,
|
|
28
28
|
ThumbnailResult,
|
|
29
29
|
SDKEvent,
|
|
30
|
+
NodeType,
|
|
30
31
|
} from './interface';
|
|
31
32
|
import {
|
|
32
33
|
getUid,
|
|
@@ -252,7 +253,7 @@ export class ProtonDriveClient {
|
|
|
252
253
|
}
|
|
253
254
|
|
|
254
255
|
/**
|
|
255
|
-
* Subscribes to
|
|
256
|
+
* Subscribes to the remote general data updates.
|
|
256
257
|
*
|
|
257
258
|
* Only one instance of the SDK should subscribe to updates.
|
|
258
259
|
*/
|
|
@@ -285,7 +286,7 @@ export class ProtonDriveClient {
|
|
|
285
286
|
*/
|
|
286
287
|
async getMyFilesRootFolder(): Promise<MaybeNode> {
|
|
287
288
|
this.logger.info('Getting my files root folder');
|
|
288
|
-
return convertInternalNodePromise(this.nodes.access.
|
|
289
|
+
return convertInternalNodePromise(this.nodes.access.getVolumeRootFolder());
|
|
289
290
|
}
|
|
290
291
|
|
|
291
292
|
/**
|
|
@@ -297,9 +298,14 @@ export class ProtonDriveClient {
|
|
|
297
298
|
* @param signal - Signal to abort the operation.
|
|
298
299
|
* @returns An async generator of the children of the given parent node.
|
|
299
300
|
*/
|
|
300
|
-
async *iterateFolderChildren(
|
|
301
|
+
async *iterateFolderChildren(
|
|
302
|
+
parentNodeUid: NodeOrUid,
|
|
303
|
+
filterOptions?: { type?: NodeType },
|
|
304
|
+
signal?: AbortSignal,
|
|
305
|
+
): AsyncGenerator<MaybeNode> {
|
|
301
306
|
this.logger.info(`Iterating children of ${getUid(parentNodeUid)}`);
|
|
302
|
-
|
|
307
|
+
const iterator = this.nodes.access.iterateFolderChildren(getUid(parentNodeUid), filterOptions, signal);
|
|
308
|
+
yield* convertInternalNodeIterator(iterator);
|
|
303
309
|
}
|
|
304
310
|
|
|
305
311
|
/**
|
|
@@ -384,6 +390,29 @@ export class ProtonDriveClient {
|
|
|
384
390
|
yield* this.nodes.management.moveNodes(getUids(nodeUids), getUid(newParentNodeUid), signal);
|
|
385
391
|
}
|
|
386
392
|
|
|
393
|
+
/**
|
|
394
|
+
* Copy the nodes to a new parent node.
|
|
395
|
+
*
|
|
396
|
+
* The operation is performed node by node and the results are yielded
|
|
397
|
+
* as they are available. Order of the results is not guaranteed.
|
|
398
|
+
*
|
|
399
|
+
* If one of the nodes fails to copy, the operation continues with the
|
|
400
|
+
* rest of the nodes. Use `NodeResult` to check the status of the action.
|
|
401
|
+
*
|
|
402
|
+
* @param nodeUids - List of node entities or their UIDs.
|
|
403
|
+
* @param newParentNodeUid - Node entity or its UID string.
|
|
404
|
+
* @param signal - Signal to abort the operation.
|
|
405
|
+
* @returns An async generator of the results of the copy operation
|
|
406
|
+
*/
|
|
407
|
+
async *copyNodes(
|
|
408
|
+
nodeUids: NodeOrUid[],
|
|
409
|
+
newParentNodeUid: NodeOrUid,
|
|
410
|
+
signal?: AbortSignal,
|
|
411
|
+
): AsyncGenerator<NodeResult> {
|
|
412
|
+
this.logger.info(`Copying ${nodeUids.length} nodes to ${getUid(newParentNodeUid)}`);
|
|
413
|
+
yield* this.nodes.management.copyNodes(getUids(nodeUids), getUid(newParentNodeUid), signal);
|
|
414
|
+
}
|
|
415
|
+
|
|
387
416
|
/**
|
|
388
417
|
* Trash the nodes.
|
|
389
418
|
*
|
|
@@ -1,19 +1,56 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
Logger,
|
|
3
|
+
ProtonDriveClientContructorParameters,
|
|
4
|
+
NodeOrUid,
|
|
5
|
+
MaybeMissingNode,
|
|
6
|
+
UploadMetadata,
|
|
7
|
+
FileDownloader,
|
|
8
|
+
FileUploader,
|
|
9
|
+
SDKEvent,
|
|
10
|
+
MaybeNode,
|
|
11
|
+
} from './interface';
|
|
12
|
+
import { getConfig } from './config';
|
|
3
13
|
import { DriveCrypto } from './crypto';
|
|
4
|
-
import {
|
|
14
|
+
import { Telemetry } from './telemetry';
|
|
15
|
+
import { convertInternalMissingNodeIterator, convertInternalNodeIterator, getUid, getUids } from './transformers';
|
|
16
|
+
import { DriveAPIService } from './internal/apiService';
|
|
17
|
+
import { initDownloadModule } from './internal/download';
|
|
18
|
+
import { DriveEventsService, DriveListener, EventSubscription } from './internal/events';
|
|
5
19
|
import { initNodesModule } from './internal/nodes';
|
|
6
|
-
import { initPhotosModule } from './internal/photos';
|
|
7
|
-
import { DriveEventsService } from './internal/events';
|
|
20
|
+
import { initPhotoSharesModule, initPhotosModule } from './internal/photos';
|
|
8
21
|
import { SDKEvents } from './internal/sdkEvents';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
22
|
+
import { initSharesModule } from './internal/shares';
|
|
23
|
+
import { initSharingModule } from './internal/sharing';
|
|
24
|
+
import { initUploadModule } from './internal/upload';
|
|
11
25
|
|
|
12
|
-
|
|
26
|
+
/**
|
|
27
|
+
* ProtonDrivePhotosClient is the interface to access Photos functionality.
|
|
28
|
+
*
|
|
29
|
+
* The client provides high-level operations for managing photos, albums, sharing,
|
|
30
|
+
* and downloading/uploading photos.
|
|
31
|
+
*
|
|
32
|
+
* @deprecated This is an experimental feature that might change without a warning.
|
|
33
|
+
*/
|
|
13
34
|
export class ProtonDrivePhotosClient {
|
|
35
|
+
private logger: Logger;
|
|
36
|
+
private sdkEvents: SDKEvents;
|
|
37
|
+
private events: DriveEventsService;
|
|
38
|
+
private photoShares: ReturnType<typeof initPhotoSharesModule>;
|
|
14
39
|
private nodes: ReturnType<typeof initNodesModule>;
|
|
40
|
+
private sharing: ReturnType<typeof initSharingModule>;
|
|
41
|
+
private download: ReturnType<typeof initDownloadModule>;
|
|
42
|
+
private upload: ReturnType<typeof initUploadModule>;
|
|
15
43
|
private photos: ReturnType<typeof initPhotosModule>;
|
|
16
44
|
|
|
45
|
+
public experimental: {
|
|
46
|
+
/**
|
|
47
|
+
* Experimental feature to return the URL of the node.
|
|
48
|
+
*
|
|
49
|
+
* See `ProtonDriveClient.experimental.getNodeUrl` for more information.
|
|
50
|
+
*/
|
|
51
|
+
getNodeUrl: (nodeUid: NodeOrUid) => Promise<string>;
|
|
52
|
+
};
|
|
53
|
+
|
|
17
54
|
constructor({
|
|
18
55
|
httpClient,
|
|
19
56
|
entitiesCache,
|
|
@@ -23,41 +60,183 @@ export class ProtonDrivePhotosClient {
|
|
|
23
60
|
srpModule,
|
|
24
61
|
config,
|
|
25
62
|
telemetry,
|
|
63
|
+
latestEventIdProvider,
|
|
26
64
|
}: ProtonDriveClientContructorParameters) {
|
|
27
65
|
if (!telemetry) {
|
|
28
66
|
telemetry = new Telemetry();
|
|
29
67
|
}
|
|
68
|
+
this.logger = telemetry.getLogger('interface');
|
|
30
69
|
|
|
31
70
|
const fullConfig = getConfig(config);
|
|
32
|
-
|
|
71
|
+
this.sdkEvents = new SDKEvents(telemetry);
|
|
33
72
|
const cryptoModule = new DriveCrypto(openPGPCryptoModule, srpModule);
|
|
34
73
|
const apiService = new DriveAPIService(
|
|
35
74
|
telemetry,
|
|
36
|
-
sdkEvents,
|
|
75
|
+
this.sdkEvents,
|
|
37
76
|
httpClient,
|
|
38
77
|
fullConfig.baseUrl,
|
|
39
78
|
fullConfig.language,
|
|
40
79
|
);
|
|
41
|
-
const
|
|
42
|
-
this.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
80
|
+
const coreShares = initSharesModule(telemetry, apiService, entitiesCache, cryptoCache, account, cryptoModule);
|
|
81
|
+
this.photoShares = initPhotoSharesModule(
|
|
82
|
+
telemetry,
|
|
83
|
+
apiService,
|
|
84
|
+
entitiesCache,
|
|
85
|
+
cryptoCache,
|
|
86
|
+
account,
|
|
87
|
+
cryptoModule,
|
|
88
|
+
coreShares,
|
|
89
|
+
);
|
|
90
|
+
this.nodes = initNodesModule(
|
|
91
|
+
telemetry,
|
|
92
|
+
apiService,
|
|
93
|
+
entitiesCache,
|
|
94
|
+
cryptoCache,
|
|
95
|
+
account,
|
|
96
|
+
cryptoModule,
|
|
97
|
+
this.photoShares,
|
|
98
|
+
);
|
|
99
|
+
this.photos = initPhotosModule(apiService, this.photoShares, this.nodes.access);
|
|
100
|
+
this.sharing = initSharingModule(
|
|
101
|
+
telemetry,
|
|
102
|
+
apiService,
|
|
103
|
+
entitiesCache,
|
|
104
|
+
account,
|
|
105
|
+
cryptoModule,
|
|
106
|
+
this.photoShares,
|
|
107
|
+
this.nodes.access,
|
|
108
|
+
);
|
|
109
|
+
this.download = initDownloadModule(
|
|
110
|
+
telemetry,
|
|
111
|
+
apiService,
|
|
112
|
+
cryptoModule,
|
|
113
|
+
account,
|
|
114
|
+
this.photoShares,
|
|
115
|
+
this.nodes.access,
|
|
116
|
+
this.nodes.revisions,
|
|
117
|
+
);
|
|
118
|
+
this.upload = initUploadModule(
|
|
119
|
+
telemetry,
|
|
120
|
+
apiService,
|
|
121
|
+
cryptoModule,
|
|
122
|
+
this.photoShares,
|
|
123
|
+
this.nodes.access,
|
|
124
|
+
fullConfig.clientUid,
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// These are used to keep the internal cache up to date
|
|
128
|
+
const cacheEventListeners: DriveListener[] = [
|
|
129
|
+
this.nodes.eventHandler.updateNodesCacheOnEvent.bind(this.nodes.eventHandler),
|
|
130
|
+
this.sharing.eventHandler.handleDriveEvent.bind(this.sharing.eventHandler),
|
|
131
|
+
];
|
|
132
|
+
this.events = new DriveEventsService(
|
|
133
|
+
telemetry,
|
|
134
|
+
apiService,
|
|
135
|
+
this.photoShares,
|
|
136
|
+
cacheEventListeners,
|
|
137
|
+
latestEventIdProvider,
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
this.experimental = {
|
|
141
|
+
getNodeUrl: async (nodeUid: NodeOrUid) => {
|
|
142
|
+
this.logger.debug(`Getting node URL for ${getUid(nodeUid)}`);
|
|
143
|
+
return this.nodes.access.getNodeUrl(getUid(nodeUid));
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Subscribes to the general SDK events.
|
|
150
|
+
*
|
|
151
|
+
* See `ProtonDriveClient.onMessage` for more information.
|
|
152
|
+
*/
|
|
153
|
+
onMessage(eventName: SDKEvent, callback: () => void): () => void {
|
|
154
|
+
this.logger.debug(`Subscribing to event ${eventName}`);
|
|
155
|
+
return this.sdkEvents.addListener(eventName, callback);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Subscribes to the remote data updates for all files in a tree.
|
|
160
|
+
*
|
|
161
|
+
* See `ProtonDriveClient.subscribeToTreeEvents` for more information.
|
|
162
|
+
*/
|
|
163
|
+
async subscribeToTreeEvents(treeEventScopeId: string, callback: DriveListener): Promise<EventSubscription> {
|
|
164
|
+
this.logger.debug('Subscribing to node updates');
|
|
165
|
+
return this.events.subscribeToTreeEvents(treeEventScopeId, callback);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Subscribes to the remote general data updates.
|
|
170
|
+
*
|
|
171
|
+
* See `ProtonDriveClient.subscribeToDriveEvents` for more information.
|
|
172
|
+
*/
|
|
173
|
+
async subscribeToDriveEvents(callback: DriveListener): Promise<EventSubscription> {
|
|
174
|
+
this.logger.debug('Subscribing to core updates');
|
|
175
|
+
return this.events.subscribeToCoreEvents(callback);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Iterates all the photos for the timeline view.
|
|
180
|
+
*
|
|
181
|
+
* The output includes only necessary information to quickly prepare
|
|
182
|
+
* the whole timeline view with the break-down per month/year and fast
|
|
183
|
+
* scrollbar.
|
|
184
|
+
*
|
|
185
|
+
* Individual photos details must be loaded separately based on what
|
|
186
|
+
* is visible in the UI.
|
|
187
|
+
*
|
|
188
|
+
* The output is sorted by the capture time, starting from the
|
|
189
|
+
* the most recent photos.
|
|
190
|
+
*/
|
|
191
|
+
async *iterateTimeline(signal?: AbortSignal): AsyncGenerator<{
|
|
192
|
+
nodeUid: string;
|
|
193
|
+
captureTime: Date;
|
|
194
|
+
tags: number[];
|
|
195
|
+
}> {
|
|
196
|
+
// TODO: expose better type
|
|
197
|
+
yield* this.photos.timeline.iterateTimeline(signal);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Iterates the nodes by their UIDs.
|
|
202
|
+
*
|
|
203
|
+
* See `ProtonDriveClient.iterateNodes` for more information.
|
|
204
|
+
*/
|
|
205
|
+
async *iterateNodes(nodeUids: NodeOrUid[], signal?: AbortSignal): AsyncGenerator<MaybeMissingNode> {
|
|
206
|
+
this.logger.info(`Iterating ${nodeUids.length} nodes`);
|
|
207
|
+
// TODO: expose photo type
|
|
208
|
+
yield* convertInternalMissingNodeIterator(this.nodes.access.iterateNodes(getUids(nodeUids), signal));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Iterates the albums.
|
|
213
|
+
*
|
|
214
|
+
* The output is not sorted and the order of the nodes is not guaranteed.
|
|
215
|
+
*/
|
|
216
|
+
async *iterateAlbums(signal?: AbortSignal): AsyncGenerator<MaybeNode> {
|
|
217
|
+
this.logger.info('Iterating albums');
|
|
218
|
+
// TODO: expose album type
|
|
219
|
+
yield* convertInternalNodeIterator(this.photos.albums.iterateAlbums(signal));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Get the file downloader to download the node content.
|
|
224
|
+
*
|
|
225
|
+
* See `ProtonDriveClient.getFileDownloader` for more information.
|
|
226
|
+
*/
|
|
227
|
+
async getFileDownloader(nodeUid: NodeOrUid, signal?: AbortSignal): Promise<FileDownloader> {
|
|
228
|
+
this.logger.info(`Getting file downloader for ${getUid(nodeUid)}`);
|
|
229
|
+
return this.download.getFileDownloader(getUid(nodeUid), signal);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Get the file uploader to upload a new file.
|
|
234
|
+
*
|
|
235
|
+
* See `ProtonDriveClient.getFileUploader` for more information.
|
|
236
|
+
*/
|
|
237
|
+
async getFileUploader(name: string, metadata: UploadMetadata, signal?: AbortSignal): Promise<FileUploader> {
|
|
238
|
+
this.logger.info(`Getting file uploader`);
|
|
239
|
+
const parentFolderUid = await this.nodes.access.getVolumeRootFolder();
|
|
240
|
+
return this.upload.getFileUploader(getUid(parentFolderUid), name, metadata, signal);
|
|
241
|
+
}
|
|
63
242
|
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PhotosCache = void 0;
|
|
4
|
-
class PhotosCache {
|
|
5
|
-
driveCache;
|
|
6
|
-
constructor(driveCache) {
|
|
7
|
-
this.driveCache = driveCache;
|
|
8
|
-
this.driveCache = driveCache;
|
|
9
|
-
}
|
|
10
|
-
async setAlbum(album) {
|
|
11
|
-
await this.driveCache.setEntity(album.uid, album);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
exports.PhotosCache = PhotosCache;
|
|
15
|
-
//# sourceMappingURL=cache.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../../src/internal/photos/cache.ts"],"names":[],"mappings":";;;AAEA,MAAa,WAAW;IACA;IAApB,YAAoB,UAAoC;QAApC,eAAU,GAAV,UAAU,CAA0B;QACpD,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IACjC,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,KAAU;QACrB,MAAM,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACtD,CAAC;CACJ;AARD,kCAQC"}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { PhotosAPIService } from './apiService';
|
|
2
|
-
import { PhotosCache } from './cache';
|
|
3
|
-
import { NodesService } from './interface';
|
|
4
|
-
export declare class PhotosTimeline {
|
|
5
|
-
private apiService;
|
|
6
|
-
private cache;
|
|
7
|
-
private nodesService;
|
|
8
|
-
constructor(apiService: PhotosAPIService, cache: PhotosCache, nodesService: NodesService);
|
|
9
|
-
getTimelineStructure(): Promise<void>;
|
|
10
|
-
}
|