@protontech/drive-sdk 0.3.1 → 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/crypto/driveCrypto.d.ts +1 -1
- package/dist/crypto/driveCrypto.js.map +1 -1
- package/dist/crypto/interface.d.ts +1 -1
- package/dist/crypto/openPGPCrypto.d.ts +1 -1
- package/dist/crypto/openPGPCrypto.js +4 -1
- package/dist/crypto/openPGPCrypto.js.map +1 -1
- 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/download/cryptoService.js +2 -2
- package/dist/internal/download/cryptoService.js.map +1 -1
- package/dist/internal/download/fileDownloader.js +2 -2
- package/dist/internal/download/fileDownloader.js.map +1 -1
- package/dist/internal/download/fileDownloader.test.js +3 -1
- package/dist/internal/download/fileDownloader.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/cache.js +3 -1
- package/dist/internal/nodes/cache.js.map +1 -1
- package/dist/internal/nodes/cryptoReporter.d.ts +20 -0
- package/dist/internal/nodes/cryptoReporter.js +96 -0
- package/dist/internal/nodes/cryptoReporter.js.map +1 -0
- package/dist/internal/nodes/cryptoService.d.ts +18 -13
- package/dist/internal/nodes/cryptoService.js +18 -98
- package/dist/internal/nodes/cryptoService.js.map +1 -1
- package/dist/internal/nodes/cryptoService.test.js +7 -5
- 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.js +3 -1
- 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/interface.d.ts +5 -2
- package/dist/internal/nodes/nodesAccess.d.ts +4 -4
- package/dist/internal/nodes/nodesAccess.js +77 -69
- 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/cache.d.ts +3 -0
- package/dist/internal/sharing/cache.js +17 -2
- package/dist/internal/sharing/cache.js.map +1 -1
- package/dist/internal/sharing/interface.d.ts +2 -2
- package/dist/internal/sharing/interface.js +1 -1
- package/dist/internal/sharing/sharingAccess.js +7 -1
- package/dist/internal/sharing/sharingAccess.js.map +1 -1
- package/dist/internal/sharing/sharingAccess.test.js +243 -34
- package/dist/internal/sharing/sharingAccess.test.js.map +1 -1
- package/dist/internal/sharingPublic/apiService.d.ts +1 -1
- package/dist/internal/sharingPublic/apiService.js +9 -2
- package/dist/internal/sharingPublic/apiService.js.map +1 -1
- package/dist/internal/sharingPublic/cryptoService.d.ts +6 -20
- package/dist/internal/sharingPublic/cryptoService.js +40 -103
- package/dist/internal/sharingPublic/cryptoService.js.map +1 -1
- package/dist/internal/sharingPublic/index.d.ts +2 -2
- package/dist/internal/sharingPublic/index.js +2 -2
- package/dist/internal/sharingPublic/index.js.map +1 -1
- package/dist/internal/sharingPublic/interface.d.ts +1 -43
- package/dist/internal/sharingPublic/manager.d.ts +1 -1
- package/dist/internal/sharingPublic/manager.js +9 -7
- package/dist/internal/sharingPublic/manager.js.map +1 -1
- package/dist/internal/upload/streamUploader.js +1 -1
- package/dist/internal/upload/streamUploader.js.map +1 -1
- package/dist/internal/upload/streamUploader.test.js +3 -1
- package/dist/internal/upload/streamUploader.test.js.map +1 -1
- package/dist/protonDriveClient.d.ts +20 -3
- package/dist/protonDriveClient.js +24 -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/dist/protonDrivePublicLinkClient.d.ts +13 -4
- package/dist/protonDrivePublicLinkClient.js +13 -11
- package/dist/protonDrivePublicLinkClient.js.map +1 -1
- package/package.json +1 -1
- package/src/crypto/driveCrypto.ts +1 -1
- package/src/crypto/interface.ts +1 -1
- package/src/crypto/openPGPCrypto.ts +5 -2
- 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/download/cryptoService.ts +2 -2
- package/src/internal/download/fileDownloader.test.ts +3 -1
- package/src/internal/download/fileDownloader.ts +2 -2
- 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/cache.ts +3 -1
- package/src/internal/nodes/cryptoReporter.ts +145 -0
- package/src/internal/nodes/cryptoService.test.ts +11 -9
- package/src/internal/nodes/cryptoService.ts +45 -138
- package/src/internal/nodes/errors.ts +5 -0
- package/src/internal/nodes/index.test.ts +1 -1
- package/src/internal/nodes/index.ts +3 -1
- package/src/internal/nodes/interface.ts +6 -2
- package/src/internal/nodes/nodesAccess.test.ts +68 -8
- package/src/internal/nodes/nodesAccess.ts +101 -76
- 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/cache.ts +19 -2
- package/src/internal/sharing/interface.ts +2 -2
- package/src/internal/sharing/sharingAccess.test.ts +283 -35
- package/src/internal/sharing/sharingAccess.ts +7 -1
- package/src/internal/sharingPublic/apiService.ts +11 -2
- package/src/internal/sharingPublic/cryptoService.ts +71 -135
- package/src/internal/sharingPublic/index.ts +3 -2
- package/src/internal/sharingPublic/interface.ts +8 -53
- package/src/internal/sharingPublic/manager.ts +9 -8
- package/src/internal/upload/streamUploader.test.ts +3 -1
- package/src/internal/upload/streamUploader.ts +1 -1
- package/src/protonDriveClient.ts +34 -4
- package/src/protonDrivePhotosClient.ts +211 -32
- package/src/protonDrivePublicLinkClient.ts +26 -12
- 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
|
@@ -1,13 +1,168 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { c } from 'ttag';
|
|
2
2
|
|
|
3
|
+
import { DriveAPIService, drivePaths, NotFoundAPIError } from '../apiService';
|
|
4
|
+
import { EncryptedRootShare, EncryptedShareCrypto, ShareType } from '../shares/interface';
|
|
5
|
+
import { makeNodeUid } from '../uids';
|
|
6
|
+
|
|
7
|
+
type GetVolumesResponse = drivePaths['/drive/volumes']['get']['responses']['200']['content']['application/json'];
|
|
8
|
+
|
|
9
|
+
type GetShareResponse = drivePaths['/drive/shares/{shareID}']['get']['responses']['200']['content']['application/json'];
|
|
10
|
+
|
|
11
|
+
type PostCreateVolumeRequest = Extract<
|
|
12
|
+
drivePaths['/drive/photos/volumes']['post']['requestBody'],
|
|
13
|
+
{ content: object }
|
|
14
|
+
>['content']['application/json'];
|
|
15
|
+
type PostCreateVolumeResponse =
|
|
16
|
+
drivePaths['/drive/photos/volumes']['post']['responses']['200']['content']['application/json'];
|
|
17
|
+
|
|
18
|
+
type GetTimelineResponse =
|
|
19
|
+
drivePaths['/drive/volumes/{volumeID}/photos']['get']['responses']['200']['content']['application/json'];
|
|
20
|
+
|
|
21
|
+
type GetAlbumsResponse =
|
|
22
|
+
drivePaths['/drive/photos/volumes/{volumeID}/albums']['get']['responses']['200']['content']['application/json'];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Provides API communication for fetching and manipulating photos and albums
|
|
26
|
+
* metadata.
|
|
27
|
+
*
|
|
28
|
+
* The service is responsible for transforming local objects to API payloads
|
|
29
|
+
* and vice versa. It should not contain any business logic.
|
|
30
|
+
*/
|
|
3
31
|
export class PhotosAPIService {
|
|
4
32
|
constructor(private apiService: DriveAPIService) {
|
|
5
33
|
this.apiService = apiService;
|
|
6
34
|
}
|
|
7
35
|
|
|
8
|
-
async
|
|
36
|
+
async getPhotoShare(): Promise<EncryptedRootShare> {
|
|
37
|
+
// TODO: Switch to drive/v2/shares/photos once available.
|
|
38
|
+
|
|
39
|
+
const volumesResponse = await this.apiService.get<GetVolumesResponse>('drive/volumes');
|
|
40
|
+
|
|
41
|
+
const photoVolume = volumesResponse.Volumes.find((volume) => volume.Type === 2);
|
|
42
|
+
|
|
43
|
+
if (!photoVolume) {
|
|
44
|
+
throw new NotFoundAPIError(c('Error').t`Photo volume not found`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const response = await this.apiService.get<GetShareResponse>(`drive/shares/${photoVolume.Share.ShareID}`);
|
|
9
48
|
|
|
10
|
-
|
|
49
|
+
if (!response.AddressID) {
|
|
50
|
+
throw new Error('Photo root share has not address ID set');
|
|
51
|
+
}
|
|
11
52
|
|
|
12
|
-
|
|
53
|
+
return {
|
|
54
|
+
volumeId: response.VolumeID,
|
|
55
|
+
shareId: response.ShareID,
|
|
56
|
+
rootNodeId: response.LinkID,
|
|
57
|
+
creatorEmail: response.Creator,
|
|
58
|
+
encryptedCrypto: {
|
|
59
|
+
armoredKey: response.Key,
|
|
60
|
+
armoredPassphrase: response.Passphrase,
|
|
61
|
+
armoredPassphraseSignature: response.PassphraseSignature,
|
|
62
|
+
},
|
|
63
|
+
addressId: response.AddressID,
|
|
64
|
+
type: ShareType.Photo,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async createPhotoVolume(
|
|
69
|
+
share: {
|
|
70
|
+
addressId: string;
|
|
71
|
+
addressKeyId: string;
|
|
72
|
+
} & EncryptedShareCrypto,
|
|
73
|
+
node: {
|
|
74
|
+
encryptedName: string;
|
|
75
|
+
armoredKey: string;
|
|
76
|
+
armoredPassphrase: string;
|
|
77
|
+
armoredPassphraseSignature: string;
|
|
78
|
+
armoredHashKey: string;
|
|
79
|
+
},
|
|
80
|
+
): Promise<{ volumeId: string; shareId: string; rootNodeId: string }> {
|
|
81
|
+
const response = await this.apiService.post<PostCreateVolumeRequest, PostCreateVolumeResponse>(
|
|
82
|
+
'drive/photos/volumes',
|
|
83
|
+
{
|
|
84
|
+
Share: {
|
|
85
|
+
AddressID: share.addressId,
|
|
86
|
+
AddressKeyID: share.addressKeyId,
|
|
87
|
+
Key: share.armoredKey,
|
|
88
|
+
Passphrase: share.armoredPassphrase,
|
|
89
|
+
PassphraseSignature: share.armoredPassphraseSignature,
|
|
90
|
+
},
|
|
91
|
+
Link: {
|
|
92
|
+
Name: node.encryptedName,
|
|
93
|
+
NodeKey: node.armoredKey,
|
|
94
|
+
NodePassphrase: node.armoredPassphrase,
|
|
95
|
+
NodePassphraseSignature: node.armoredPassphraseSignature,
|
|
96
|
+
NodeHashKey: node.armoredHashKey,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
return {
|
|
101
|
+
volumeId: response.Volume.VolumeID,
|
|
102
|
+
shareId: response.Volume.Share.ShareID,
|
|
103
|
+
rootNodeId: response.Volume.Share.LinkID,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async *iterateTimeline(
|
|
108
|
+
volumeId: string,
|
|
109
|
+
signal?: AbortSignal,
|
|
110
|
+
): AsyncGenerator<{
|
|
111
|
+
nodeUid: string;
|
|
112
|
+
captureTime: Date;
|
|
113
|
+
tags: number[];
|
|
114
|
+
}> {
|
|
115
|
+
let anchor = '';
|
|
116
|
+
while (true) {
|
|
117
|
+
const response = await this.apiService.get<GetTimelineResponse>(
|
|
118
|
+
`drive/volumes/${volumeId}/photos?${anchor ? `PreviousPageLastLinkID=${anchor}` : ''}`,
|
|
119
|
+
signal,
|
|
120
|
+
);
|
|
121
|
+
for (const photo of response.Photos) {
|
|
122
|
+
const nodeUid = makeNodeUid(volumeId, photo.LinkID);
|
|
123
|
+
yield {
|
|
124
|
+
nodeUid,
|
|
125
|
+
captureTime: new Date(photo.CaptureTime * 1000),
|
|
126
|
+
tags: photo.Tags,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!response.Photos.length) {
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
anchor = response.Photos[response.Photos.length - 1].LinkID;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async *iterateAlbums(
|
|
138
|
+
volumeId: string,
|
|
139
|
+
signal?: AbortSignal,
|
|
140
|
+
): AsyncGenerator<{
|
|
141
|
+
albumUid: string;
|
|
142
|
+
coverNodeUid?: string;
|
|
143
|
+
photoCount: number;
|
|
144
|
+
lastActivityTime: Date;
|
|
145
|
+
}> {
|
|
146
|
+
let anchor = '';
|
|
147
|
+
while (true) {
|
|
148
|
+
const response = await this.apiService.get<GetAlbumsResponse>(
|
|
149
|
+
`drive/photos/volumes/${volumeId}/albums?${anchor ? `AnchorID=${anchor}` : ''}`,
|
|
150
|
+
signal,
|
|
151
|
+
);
|
|
152
|
+
for (const album of response.Albums) {
|
|
153
|
+
const albumUid = makeNodeUid(volumeId, album.LinkID);
|
|
154
|
+
yield {
|
|
155
|
+
albumUid,
|
|
156
|
+
coverNodeUid: album.CoverLinkID ? makeNodeUid(volumeId, album.CoverLinkID) : undefined,
|
|
157
|
+
photoCount: album.PhotoCount,
|
|
158
|
+
lastActivityTime: new Date(album.LastActivityTime * 1000),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!response.More || !response.AnchorID) {
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
anchor = response.AnchorID;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
13
168
|
}
|
|
@@ -1,23 +1,68 @@
|
|
|
1
1
|
import { DriveAPIService } from '../apiService';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import { DriveCrypto } from '../../crypto';
|
|
3
|
+
import {
|
|
4
|
+
ProtonDriveAccount,
|
|
5
|
+
ProtonDriveCryptoCache,
|
|
6
|
+
ProtonDriveEntitiesCache,
|
|
7
|
+
ProtonDriveTelemetry,
|
|
8
|
+
} from '../../interface';
|
|
9
|
+
import { SharesCache } from '../shares/cache';
|
|
10
|
+
import { SharesCryptoCache } from '../shares/cryptoCache';
|
|
11
|
+
import { SharesCryptoService } from '../shares/cryptoService';
|
|
6
12
|
import { Albums } from './albums';
|
|
7
|
-
import {
|
|
13
|
+
import { PhotosAPIService } from './apiService';
|
|
14
|
+
import { NodesService, SharesService } from './interface';
|
|
15
|
+
import { PhotoSharesManager } from './shares';
|
|
16
|
+
import { PhotosTimeline } from './timeline';
|
|
8
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Provides facade for the whole photos module.
|
|
20
|
+
*
|
|
21
|
+
* The photos module is responsible for handling photos and albums metadata,
|
|
22
|
+
* including API communication, crypto, caching, and event handling.
|
|
23
|
+
*/
|
|
9
24
|
export function initPhotosModule(
|
|
10
25
|
apiService: DriveAPIService,
|
|
11
|
-
|
|
26
|
+
photoShares: PhotoSharesManager,
|
|
12
27
|
nodesService: NodesService,
|
|
13
28
|
) {
|
|
14
29
|
const api = new PhotosAPIService(apiService);
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const albums = new Albums(api, cache, nodesService);
|
|
30
|
+
const timeline = new PhotosTimeline(api, photoShares);
|
|
31
|
+
const albums = new Albums(api, photoShares, nodesService);
|
|
18
32
|
|
|
19
33
|
return {
|
|
20
34
|
timeline,
|
|
21
35
|
albums,
|
|
22
36
|
};
|
|
23
37
|
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Provides facade for the photo share module.
|
|
41
|
+
*
|
|
42
|
+
* The photo share wraps the core share module, but uses photos volume instead
|
|
43
|
+
* of main volume. It provides the same interface so it can be used in the same
|
|
44
|
+
* way in various modules that use shares.
|
|
45
|
+
*/
|
|
46
|
+
export function initPhotoSharesModule(
|
|
47
|
+
telemetry: ProtonDriveTelemetry,
|
|
48
|
+
apiService: DriveAPIService,
|
|
49
|
+
driveEntitiesCache: ProtonDriveEntitiesCache,
|
|
50
|
+
driveCryptoCache: ProtonDriveCryptoCache,
|
|
51
|
+
account: ProtonDriveAccount,
|
|
52
|
+
crypto: DriveCrypto,
|
|
53
|
+
sharesService: SharesService,
|
|
54
|
+
) {
|
|
55
|
+
const api = new PhotosAPIService(apiService);
|
|
56
|
+
const cache = new SharesCache(telemetry.getLogger('shares-cache'), driveEntitiesCache);
|
|
57
|
+
const cryptoCache = new SharesCryptoCache(telemetry.getLogger('shares-cache'), driveCryptoCache);
|
|
58
|
+
const cryptoService = new SharesCryptoService(telemetry, crypto, account);
|
|
59
|
+
|
|
60
|
+
return new PhotoSharesManager(
|
|
61
|
+
telemetry.getLogger('photos-shares'),
|
|
62
|
+
api,
|
|
63
|
+
cache,
|
|
64
|
+
cryptoCache,
|
|
65
|
+
cryptoService,
|
|
66
|
+
sharesService,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
@@ -1,5 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PrivateKey } from '../../crypto';
|
|
2
|
+
import { MissingNode, MetricVolumeType } from '../../interface';
|
|
2
3
|
import { DecryptedNode } from '../nodes';
|
|
4
|
+
import { EncryptedShare } from '../shares';
|
|
5
|
+
|
|
6
|
+
export interface SharesService {
|
|
7
|
+
getOwnVolumeIDs(): Promise<{ volumeId: string; rootNodeId: string }>;
|
|
8
|
+
loadEncryptedShare(shareId: string): Promise<EncryptedShare>;
|
|
9
|
+
getSharePrivateKey(shareId: string): Promise<PrivateKey>;
|
|
10
|
+
getMyFilesShareMemberEmailKey(): Promise<{
|
|
11
|
+
email: string;
|
|
12
|
+
addressId: string;
|
|
13
|
+
addressKey: PrivateKey;
|
|
14
|
+
addressKeyId: string;
|
|
15
|
+
}>;
|
|
16
|
+
getContextShareMemberEmailKey(shareId: string): Promise<{
|
|
17
|
+
email: string;
|
|
18
|
+
addressId: string;
|
|
19
|
+
addressKey: PrivateKey;
|
|
20
|
+
addressKeyId: string;
|
|
21
|
+
}>;
|
|
22
|
+
isOwnVolume(volumeId: string): Promise<boolean>;
|
|
23
|
+
getVolumeMetricContext(volumeId: string): Promise<MetricVolumeType>;
|
|
24
|
+
}
|
|
3
25
|
|
|
4
26
|
export interface NodesService {
|
|
5
27
|
getNode(nodeUid: string): Promise<DecryptedNode>;
|
|
@@ -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
|
|
@@ -44,11 +44,28 @@ export class SharingCache {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
async getSharedWithMeNodeUids(): Promise<string[]> {
|
|
47
|
-
return this.getNodeUids(SharingType.
|
|
47
|
+
return this.getNodeUids(SharingType.SharedWithMe);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async hasSharedWithMeNodeUidsLoaded(): Promise<boolean> {
|
|
51
|
+
try {
|
|
52
|
+
await this.getNodeUids(SharingType.SharedWithMe);
|
|
53
|
+
return true;
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async addSharedWithMeNodeUid(nodeUid: string): Promise<void> {
|
|
60
|
+
return this.addNodeUid(SharingType.SharedWithMe, nodeUid);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async removeSharedWithMeNodeUid(nodeUid: string): Promise<void> {
|
|
64
|
+
return this.removeNodeUid(SharingType.SharedWithMe, nodeUid);
|
|
48
65
|
}
|
|
49
66
|
|
|
50
67
|
async setSharedWithMeNodeUids(nodeUids: string[] | undefined): Promise<void> {
|
|
51
|
-
return this.setNodeUids(SharingType.
|
|
68
|
+
return this.setNodeUids(SharingType.SharedWithMe, nodeUids);
|
|
52
69
|
}
|
|
53
70
|
|
|
54
71
|
/**
|
|
@@ -5,7 +5,7 @@ import { DecryptedNode } from '../nodes';
|
|
|
5
5
|
|
|
6
6
|
export enum SharingType {
|
|
7
7
|
SharedByMe = 'sharedByMe',
|
|
8
|
-
|
|
8
|
+
SharedWithMe = 'sharedWithMe',
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -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;
|