@protontech/drive-sdk 0.7.0 → 0.7.1
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 -0
- package/dist/interface/index.js.map +1 -1
- package/dist/interface/nodes.d.ts +14 -10
- package/dist/interface/nodes.js +5 -8
- package/dist/interface/nodes.js.map +1 -1
- package/dist/interface/photos.d.ts +62 -0
- package/dist/interface/photos.js +3 -0
- package/dist/interface/photos.js.map +1 -0
- package/dist/internal/apiService/driveTypes.d.ts +1294 -517
- package/dist/internal/errors.d.ts +1 -0
- package/dist/internal/errors.js +4 -0
- package/dist/internal/errors.js.map +1 -1
- package/dist/internal/nodes/apiService.d.ts +60 -9
- package/dist/internal/nodes/apiService.js +125 -81
- package/dist/internal/nodes/apiService.js.map +1 -1
- package/dist/internal/nodes/apiService.test.js +2 -0
- package/dist/internal/nodes/apiService.test.js.map +1 -1
- package/dist/internal/nodes/cache.d.ts +16 -8
- package/dist/internal/nodes/cache.js +19 -5
- package/dist/internal/nodes/cache.js.map +1 -1
- package/dist/internal/nodes/cache.test.js +1 -0
- package/dist/internal/nodes/cache.test.js.map +1 -1
- package/dist/internal/nodes/cryptoService.d.ts +1 -1
- package/dist/internal/nodes/cryptoService.js +4 -4
- package/dist/internal/nodes/cryptoService.js.map +1 -1
- package/dist/internal/nodes/cryptoService.test.js +3 -3
- package/dist/internal/nodes/cryptoService.test.js.map +1 -1
- package/dist/internal/nodes/events.d.ts +2 -2
- package/dist/internal/nodes/events.js.map +1 -1
- package/dist/internal/nodes/index.test.js +1 -0
- package/dist/internal/nodes/index.test.js.map +1 -1
- package/dist/internal/nodes/interface.d.ts +1 -0
- package/dist/internal/nodes/nodesAccess.d.ts +29 -20
- package/dist/internal/nodes/nodesAccess.js +41 -29
- package/dist/internal/nodes/nodesAccess.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.d.ts +32 -12
- package/dist/internal/nodes/nodesManagement.js +30 -13
- package/dist/internal/nodes/nodesManagement.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.test.js +39 -4
- package/dist/internal/nodes/nodesManagement.test.js.map +1 -1
- package/dist/internal/nodes/nodesRevisions.d.ts +2 -2
- package/dist/internal/nodes/nodesRevisions.js.map +1 -1
- package/dist/internal/photos/albums.d.ts +2 -2
- package/dist/internal/photos/albums.js.map +1 -1
- package/dist/internal/photos/index.d.ts +19 -3
- package/dist/internal/photos/index.js +38 -8
- package/dist/internal/photos/index.js.map +1 -1
- package/dist/internal/photos/interface.d.ts +18 -9
- package/dist/internal/photos/nodes.d.ts +57 -0
- package/dist/internal/photos/nodes.js +165 -0
- package/dist/internal/photos/nodes.js.map +1 -0
- package/dist/internal/photos/timeline.d.ts +2 -2
- package/dist/internal/photos/timeline.js.map +1 -1
- package/dist/internal/photos/timeline.test.js.map +1 -1
- package/dist/protonDriveClient.d.ts +10 -1
- package/dist/protonDriveClient.js +18 -3
- package/dist/protonDriveClient.js.map +1 -1
- package/dist/protonDrivePhotosClient.d.ts +8 -8
- package/dist/protonDrivePhotosClient.js +8 -9
- package/dist/protonDrivePhotosClient.js.map +1 -1
- package/dist/protonDrivePublicLinkClient.d.ts +7 -1
- package/dist/protonDrivePublicLinkClient.js +9 -0
- package/dist/protonDrivePublicLinkClient.js.map +1 -1
- package/dist/transformers.d.ts +7 -2
- package/dist/transformers.js +37 -0
- package/dist/transformers.js.map +1 -1
- package/package.json +1 -1
- package/src/interface/index.ts +1 -0
- package/src/interface/nodes.ts +14 -11
- package/src/interface/photos.ts +67 -0
- package/src/internal/apiService/driveTypes.ts +1294 -517
- package/src/internal/errors.ts +4 -0
- package/src/internal/nodes/apiService.test.ts +2 -0
- package/src/internal/nodes/apiService.ts +187 -114
- package/src/internal/nodes/cache.test.ts +1 -0
- package/src/internal/nodes/cache.ts +32 -13
- package/src/internal/nodes/cryptoService.test.ts +13 -3
- package/src/internal/nodes/cryptoService.ts +4 -4
- package/src/internal/nodes/events.ts +2 -2
- package/src/internal/nodes/index.test.ts +1 -0
- package/src/internal/nodes/interface.ts +1 -0
- package/src/internal/nodes/nodesAccess.ts +82 -54
- package/src/internal/nodes/nodesManagement.test.ts +48 -4
- package/src/internal/nodes/nodesManagement.ts +76 -26
- package/src/internal/nodes/nodesRevisions.ts +3 -3
- package/src/internal/photos/albums.ts +2 -2
- package/src/internal/photos/index.ts +45 -3
- package/src/internal/photos/interface.ts +21 -9
- package/src/internal/photos/nodes.ts +233 -0
- package/src/internal/photos/timeline.test.ts +2 -2
- package/src/internal/photos/timeline.ts +2 -2
- package/src/protonDriveClient.ts +20 -3
- package/src/protonDrivePhotosClient.ts +23 -23
- package/src/protonDrivePublicLinkClient.ts +11 -0
- package/src/transformers.ts +49 -2
|
@@ -6,6 +6,10 @@ import {
|
|
|
6
6
|
ProtonDriveEntitiesCache,
|
|
7
7
|
ProtonDriveTelemetry,
|
|
8
8
|
} from '../../interface';
|
|
9
|
+
import { NodesCryptoService } from '../nodes/cryptoService';
|
|
10
|
+
import { NodesCryptoReporter } from '../nodes/cryptoReporter';
|
|
11
|
+
import { NodesCryptoCache } from '../nodes/cryptoCache';
|
|
12
|
+
import { ShareTargetType } from '../shares';
|
|
9
13
|
import { SharesCache } from '../shares/cache';
|
|
10
14
|
import { SharesCryptoCache } from '../shares/cryptoCache';
|
|
11
15
|
import { SharesCryptoService } from '../shares/cryptoService';
|
|
@@ -14,7 +18,8 @@ import { UploadTelemetry } from '../upload/telemetry';
|
|
|
14
18
|
import { UploadQueue } from '../upload/queue';
|
|
15
19
|
import { Albums } from './albums';
|
|
16
20
|
import { PhotosAPIService } from './apiService';
|
|
17
|
-
import {
|
|
21
|
+
import { SharesService } from './interface';
|
|
22
|
+
import { PhotosNodesAPIService, PhotosNodesAccess, PhotosNodesCache, PhotosNodesManagement } from './nodes';
|
|
18
23
|
import { PhotoSharesManager } from './shares';
|
|
19
24
|
import { PhotosTimeline } from './timeline';
|
|
20
25
|
import {
|
|
@@ -24,7 +29,10 @@ import {
|
|
|
24
29
|
PhotoUploadManager,
|
|
25
30
|
PhotoUploadMetadata,
|
|
26
31
|
} from './upload';
|
|
27
|
-
import {
|
|
32
|
+
import { NodesRevisons } from '../nodes/nodesRevisions';
|
|
33
|
+
import { NodesEventsHandler } from '../nodes/events';
|
|
34
|
+
|
|
35
|
+
export type { DecryptedPhotoNode } from './interface';
|
|
28
36
|
|
|
29
37
|
// Only photos and albums can be shared in photos volume.
|
|
30
38
|
export const PHOTOS_SHARE_TARGET_TYPES = [ShareTargetType.Photo, ShareTargetType.Album];
|
|
@@ -40,7 +48,7 @@ export function initPhotosModule(
|
|
|
40
48
|
apiService: DriveAPIService,
|
|
41
49
|
driveCrypto: DriveCrypto,
|
|
42
50
|
photoShares: PhotoSharesManager,
|
|
43
|
-
nodesService:
|
|
51
|
+
nodesService: PhotosNodesAccess,
|
|
44
52
|
) {
|
|
45
53
|
const api = new PhotosAPIService(apiService);
|
|
46
54
|
const timeline = new PhotosTimeline(
|
|
@@ -89,6 +97,40 @@ export function initPhotoSharesModule(
|
|
|
89
97
|
);
|
|
90
98
|
}
|
|
91
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Provides facade for the photo nodes module.
|
|
102
|
+
*
|
|
103
|
+
* The photo nodes module wraps the core nodes module and adds photo specific
|
|
104
|
+
* metadata. It provides the same interface so it can be used in the same way.
|
|
105
|
+
*/
|
|
106
|
+
export function initPhotosNodesModule(
|
|
107
|
+
telemetry: ProtonDriveTelemetry,
|
|
108
|
+
apiService: DriveAPIService,
|
|
109
|
+
driveEntitiesCache: ProtonDriveEntitiesCache,
|
|
110
|
+
driveCryptoCache: ProtonDriveCryptoCache,
|
|
111
|
+
account: ProtonDriveAccount,
|
|
112
|
+
driveCrypto: DriveCrypto,
|
|
113
|
+
sharesService: PhotoSharesManager,
|
|
114
|
+
clientUid: string | undefined,
|
|
115
|
+
) {
|
|
116
|
+
const api = new PhotosNodesAPIService(telemetry.getLogger('nodes-api'), apiService, clientUid);
|
|
117
|
+
const cache = new PhotosNodesCache(telemetry.getLogger('nodes-cache'), driveEntitiesCache);
|
|
118
|
+
const cryptoCache = new NodesCryptoCache(telemetry.getLogger('nodes-cache'), driveCryptoCache);
|
|
119
|
+
const cryptoReporter = new NodesCryptoReporter(telemetry, sharesService);
|
|
120
|
+
const cryptoService = new NodesCryptoService(telemetry, driveCrypto, account, cryptoReporter);
|
|
121
|
+
const nodesAccess = new PhotosNodesAccess(telemetry, api, cache, cryptoCache, cryptoService, sharesService);
|
|
122
|
+
const nodesEventHandler = new NodesEventsHandler(telemetry.getLogger('nodes-events'), cache);
|
|
123
|
+
const nodesManagement = new PhotosNodesManagement(api, cryptoCache, cryptoService, nodesAccess);
|
|
124
|
+
const nodesRevisions = new NodesRevisons(telemetry.getLogger('nodes'), api, cryptoService, nodesAccess);
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
access: nodesAccess,
|
|
128
|
+
management: nodesManagement,
|
|
129
|
+
revisions: nodesRevisions,
|
|
130
|
+
eventHandler: nodesEventHandler,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
92
134
|
/**
|
|
93
135
|
* Provides facade for the photo upload module.
|
|
94
136
|
*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PrivateKey } from '../../crypto';
|
|
2
|
-
import {
|
|
3
|
-
import { DecryptedNode } from '../nodes';
|
|
2
|
+
import { MetricVolumeType, PhotoAttributes } from '../../interface';
|
|
3
|
+
import { DecryptedNode, EncryptedNode, DecryptedUnparsedNode } from '../nodes/interface';
|
|
4
4
|
import { EncryptedShare } from '../shares';
|
|
5
5
|
|
|
6
6
|
export interface SharesService {
|
|
@@ -23,10 +23,22 @@ export interface SharesService {
|
|
|
23
23
|
getVolumeMetricContext(volumeId: string): Promise<MetricVolumeType>;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
26
|
+
export type EncryptedPhotoNode = EncryptedNode & {
|
|
27
|
+
photo?: EcnryptedPhotoAttributes;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type DecryptedUnparsedPhotoNode = DecryptedUnparsedNode & {
|
|
31
|
+
photo?: PhotoAttributes;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type DecryptedPhotoNode = DecryptedNode & {
|
|
35
|
+
photo?: PhotoAttributes;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type EcnryptedPhotoAttributes = Omit<PhotoAttributes, 'albums'> & {
|
|
39
|
+
contentHash?: string;
|
|
40
|
+
albums: (PhotoAttributes['albums'][0] & {
|
|
41
|
+
nameHash?: string;
|
|
42
|
+
contentHash?: string;
|
|
43
|
+
})[];
|
|
44
|
+
};
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { PrivateKey } from '../../crypto';
|
|
2
|
+
import { DecryptionError } from '../../errors';
|
|
3
|
+
import { NodeType } from '../../interface';
|
|
4
|
+
import { drivePaths } from '../apiService';
|
|
5
|
+
import { NodeAPIServiceBase, linkToEncryptedNode, linkToEncryptedNodeBaseMetadata } from '../nodes/apiService';
|
|
6
|
+
import { NodesCacheBase, serialiseNode, deserialiseNode } from '../nodes/cache';
|
|
7
|
+
import { NodesCryptoService } from '../nodes/cryptoService';
|
|
8
|
+
import { DecryptedNodeKeys } from '../nodes/interface';
|
|
9
|
+
import { NodesAccessBase, parseNode as parseNodeBase } from '../nodes/nodesAccess';
|
|
10
|
+
import { NodesManagementBase } from '../nodes/nodesManagement';
|
|
11
|
+
import { makeNodeUid } from '../uids';
|
|
12
|
+
import { EncryptedPhotoNode, DecryptedPhotoNode, DecryptedUnparsedPhotoNode } from './interface';
|
|
13
|
+
|
|
14
|
+
type PostLoadLinksMetadataRequest = Extract<
|
|
15
|
+
drivePaths['/drive/photos/volumes/{volumeID}/links']['post']['requestBody'],
|
|
16
|
+
{ content: object }
|
|
17
|
+
>['content']['application/json'];
|
|
18
|
+
type PostLoadLinksMetadataResponse =
|
|
19
|
+
drivePaths['/drive/photos/volumes/{volumeID}/links']['post']['responses']['200']['content']['application/json'];
|
|
20
|
+
|
|
21
|
+
export class PhotosNodesAPIService extends NodeAPIServiceBase<
|
|
22
|
+
EncryptedPhotoNode,
|
|
23
|
+
PostLoadLinksMetadataResponse['Links'][0]
|
|
24
|
+
> {
|
|
25
|
+
protected async fetchNodeMetadata(volumeId: string, linkIds: string[], signal?: AbortSignal) {
|
|
26
|
+
const response = await this.apiService.post<PostLoadLinksMetadataRequest, PostLoadLinksMetadataResponse>(
|
|
27
|
+
`drive/photos/volumes/${volumeId}/links`,
|
|
28
|
+
{
|
|
29
|
+
LinkIDs: linkIds,
|
|
30
|
+
},
|
|
31
|
+
signal,
|
|
32
|
+
);
|
|
33
|
+
return response.Links;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
protected linkToEncryptedNode(
|
|
37
|
+
volumeId: string,
|
|
38
|
+
link: PostLoadLinksMetadataResponse['Links'][0],
|
|
39
|
+
isOwnVolumeId: boolean,
|
|
40
|
+
): EncryptedPhotoNode {
|
|
41
|
+
const { baseNodeMetadata, baseCryptoNodeMetadata } = linkToEncryptedNodeBaseMetadata(
|
|
42
|
+
this.logger,
|
|
43
|
+
volumeId,
|
|
44
|
+
link,
|
|
45
|
+
isOwnVolumeId,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
if (link.Link.Type === 2 && link.Photo && link.Photo.ActiveRevision) {
|
|
49
|
+
const node = linkToEncryptedNode(
|
|
50
|
+
this.logger,
|
|
51
|
+
volumeId,
|
|
52
|
+
{ ...link, File: link.Photo, Folder: null },
|
|
53
|
+
isOwnVolumeId,
|
|
54
|
+
);
|
|
55
|
+
return {
|
|
56
|
+
...node,
|
|
57
|
+
type: NodeType.Photo,
|
|
58
|
+
photo: {
|
|
59
|
+
captureTime: new Date(link.Photo.CaptureTime * 1000),
|
|
60
|
+
mainPhotoNodeUid: link.Photo.MainPhotoLinkID
|
|
61
|
+
? makeNodeUid(volumeId, link.Photo.MainPhotoLinkID)
|
|
62
|
+
: undefined,
|
|
63
|
+
relatedPhotoNodeUids: link.Photo.RelatedPhotosLinkIDs.map((relatedLinkId) =>
|
|
64
|
+
makeNodeUid(volumeId, relatedLinkId),
|
|
65
|
+
),
|
|
66
|
+
contentHash: link.Photo.ContentHash || undefined,
|
|
67
|
+
tags: link.Photo.Tags,
|
|
68
|
+
albums: link.Photo.Albums.map((album) => ({
|
|
69
|
+
nodeUid: makeNodeUid(volumeId, album.AlbumLinkID),
|
|
70
|
+
additionTime: new Date(album.AddedTime * 1000),
|
|
71
|
+
nameHash: album.Hash,
|
|
72
|
+
contentHash: album.ContentHash,
|
|
73
|
+
})),
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (link.Link.Type === 3) {
|
|
79
|
+
return {
|
|
80
|
+
...baseNodeMetadata,
|
|
81
|
+
encryptedCrypto: {
|
|
82
|
+
...baseCryptoNodeMetadata,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const baseLink = {
|
|
88
|
+
Link: link.Link,
|
|
89
|
+
Membership: link.Membership,
|
|
90
|
+
Sharing: link.Sharing,
|
|
91
|
+
// @ts-expect-error The photo link can have a folder type, but not always. If not set, it will use other paths.
|
|
92
|
+
Folder: link.Folder,
|
|
93
|
+
File: null, // The photo link metadata never returns a file type.
|
|
94
|
+
};
|
|
95
|
+
return linkToEncryptedNode(this.logger, volumeId, baseLink, isOwnVolumeId);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export class PhotosNodesCache extends NodesCacheBase<DecryptedPhotoNode> {
|
|
100
|
+
serialiseNode(node: DecryptedPhotoNode): string {
|
|
101
|
+
return serialiseNode(node);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// TODO: use better deserialisation with validation
|
|
105
|
+
deserialiseNode(nodeData: string): DecryptedPhotoNode {
|
|
106
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
107
|
+
const node = deserialiseNode(nodeData) as any;
|
|
108
|
+
|
|
109
|
+
if (
|
|
110
|
+
!node ||
|
|
111
|
+
typeof node !== 'object' ||
|
|
112
|
+
(typeof node.photo !== 'object' && node.photo !== undefined) ||
|
|
113
|
+
(typeof node.photo?.captureTime !== 'string' && node.folder?.captureTime !== undefined) ||
|
|
114
|
+
(typeof node.photo?.albums !== 'object' && node.photo?.albums !== undefined)
|
|
115
|
+
) {
|
|
116
|
+
throw new Error(`Invalid node data: ${nodeData}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
...node,
|
|
121
|
+
photo: !node.photo
|
|
122
|
+
? undefined
|
|
123
|
+
: {
|
|
124
|
+
captureTime: new Date(node.photo.captureTime),
|
|
125
|
+
mainPhotoNodeUid: node.photo.mainPhotoNodeUid,
|
|
126
|
+
relatedPhotoNodeUids: node.photo.relatedPhotoNodeUids,
|
|
127
|
+
contentHash: node.photo.contentHash,
|
|
128
|
+
tags: node.photo.tags,
|
|
129
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
130
|
+
albums: node.photo.albums?.map((album: any) => ({
|
|
131
|
+
nodeUid: album.nodeUid,
|
|
132
|
+
additionTime: new Date(album.additionTime),
|
|
133
|
+
})),
|
|
134
|
+
},
|
|
135
|
+
} as DecryptedPhotoNode;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export class PhotosNodesAccess extends NodesAccessBase<EncryptedPhotoNode, DecryptedPhotoNode, PhotosNodesCryptoService> {
|
|
140
|
+
async getParentKeys(
|
|
141
|
+
node: Pick<EncryptedPhotoNode, 'uid' | 'parentUid' | 'shareId' | 'photo'>,
|
|
142
|
+
): Promise<Pick<DecryptedNodeKeys, 'key' | 'hashKey'>> {
|
|
143
|
+
if (node.parentUid || node.shareId) {
|
|
144
|
+
return super.getParentKeys(node);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (node.photo?.albums.length) {
|
|
148
|
+
// If photo is in multiple albums, we just need to get keys for one of them.
|
|
149
|
+
// Prefer to find a cached key first.
|
|
150
|
+
for (const album of node.photo.albums) {
|
|
151
|
+
try {
|
|
152
|
+
const keys = await this.cryptoCache.getNodeKeys(album.nodeUid);
|
|
153
|
+
return {
|
|
154
|
+
key: keys.key,
|
|
155
|
+
hashKey: keys.hashKey,
|
|
156
|
+
};
|
|
157
|
+
} catch {
|
|
158
|
+
// We ignore missing or invalid keys here, its just optimization.
|
|
159
|
+
// If it cannot be fixed, it will bubble up later when requesting
|
|
160
|
+
// the node keys for one of the albums.
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const albumNodeUid = node.photo.albums[0].nodeUid;
|
|
165
|
+
return this.getNodeKeys(albumNodeUid);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// This is bug that should not happen.
|
|
169
|
+
// API cannot provide node without parent or share or album.
|
|
170
|
+
throw new Error('Node has neither parent node nor share nor album');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
protected getDegradedUndecryptableNode(
|
|
174
|
+
encryptedNode: EncryptedPhotoNode,
|
|
175
|
+
error: DecryptionError,
|
|
176
|
+
): DecryptedPhotoNode {
|
|
177
|
+
return this.getDegradedUndecryptableNodeBase(encryptedNode, error);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
protected parseNode(unparsedNode: DecryptedUnparsedPhotoNode): DecryptedPhotoNode {
|
|
181
|
+
if (unparsedNode.type === NodeType.Photo) {
|
|
182
|
+
const node = parseNodeBase(this.logger, {
|
|
183
|
+
...unparsedNode,
|
|
184
|
+
type: NodeType.File,
|
|
185
|
+
});
|
|
186
|
+
return {
|
|
187
|
+
...node,
|
|
188
|
+
photo: unparsedNode.photo,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return parseNodeBase(this.logger, unparsedNode);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export class PhotosNodesCryptoService extends NodesCryptoService {
|
|
197
|
+
async decryptNode(
|
|
198
|
+
encryptedNode: EncryptedPhotoNode,
|
|
199
|
+
parentKey: PrivateKey,
|
|
200
|
+
): Promise<{ node: DecryptedUnparsedPhotoNode; keys?: DecryptedNodeKeys }> {
|
|
201
|
+
const decryptedNode = await super.decryptNode(encryptedNode, parentKey);
|
|
202
|
+
|
|
203
|
+
if (decryptedNode.node.type === NodeType.Photo) {
|
|
204
|
+
return {
|
|
205
|
+
node: {
|
|
206
|
+
...decryptedNode.node,
|
|
207
|
+
photo: encryptedNode.photo,
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return decryptedNode;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export class PhotosNodesManagement extends NodesManagementBase<
|
|
217
|
+
EncryptedPhotoNode,
|
|
218
|
+
DecryptedPhotoNode,
|
|
219
|
+
PhotosNodesCryptoService
|
|
220
|
+
> {
|
|
221
|
+
protected generateNodeFolder(
|
|
222
|
+
nodeUid: string,
|
|
223
|
+
parentNodeUid: string,
|
|
224
|
+
name: string,
|
|
225
|
+
encryptedCrypto: {
|
|
226
|
+
hash: string;
|
|
227
|
+
encryptedName: string;
|
|
228
|
+
signatureEmail: string | null;
|
|
229
|
+
},
|
|
230
|
+
): DecryptedPhotoNode {
|
|
231
|
+
return this.generateNodeFolderBase(nodeUid, parentNodeUid, name, encryptedCrypto);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
@@ -2,7 +2,7 @@ import { getMockLogger } from '../../tests/logger';
|
|
|
2
2
|
import { DriveCrypto } from '../../crypto';
|
|
3
3
|
import { makeNodeUid } from '../uids';
|
|
4
4
|
import { PhotosAPIService } from './apiService';
|
|
5
|
-
import {
|
|
5
|
+
import { PhotosNodesAccess } from './nodes';
|
|
6
6
|
import { PhotoSharesManager } from './shares';
|
|
7
7
|
import { PhotosTimeline } from './timeline';
|
|
8
8
|
|
|
@@ -11,7 +11,7 @@ describe('PhotosTimeline', () => {
|
|
|
11
11
|
let apiService: PhotosAPIService;
|
|
12
12
|
let driveCrypto: DriveCrypto;
|
|
13
13
|
let photoShares: PhotoSharesManager;
|
|
14
|
-
let nodesService:
|
|
14
|
+
let nodesService: PhotosNodesAccess;
|
|
15
15
|
let timeline: PhotosTimeline;
|
|
16
16
|
|
|
17
17
|
const volumeId = 'volumeId';
|
|
@@ -2,7 +2,7 @@ import { DriveCrypto } from '../../crypto';
|
|
|
2
2
|
import { Logger } from '../../interface';
|
|
3
3
|
import { makeNodeUid } from '../uids';
|
|
4
4
|
import { PhotosAPIService } from './apiService';
|
|
5
|
-
import {
|
|
5
|
+
import { PhotosNodesAccess } from './nodes';
|
|
6
6
|
import { PhotoSharesManager } from './shares';
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -14,7 +14,7 @@ export class PhotosTimeline {
|
|
|
14
14
|
private apiService: PhotosAPIService,
|
|
15
15
|
private driveCrypto: DriveCrypto,
|
|
16
16
|
private photoShares: PhotoSharesManager,
|
|
17
|
-
private nodesService:
|
|
17
|
+
private nodesService: PhotosNodesAccess,
|
|
18
18
|
) {
|
|
19
19
|
this.logger = logger;
|
|
20
20
|
this.apiService = apiService;
|
package/src/protonDriveClient.ts
CHANGED
|
@@ -424,6 +424,12 @@ export class ProtonDriveClient {
|
|
|
424
424
|
* The operation is performed node by node and the results are yielded
|
|
425
425
|
* as they are available. Order of the results is not guaranteed.
|
|
426
426
|
*
|
|
427
|
+
* The `nodeUids` can be a list of node entities or their UIDs, or a list
|
|
428
|
+
* of objects with `uid` and `name` properties where the name is the new
|
|
429
|
+
* name of the copied node. By default, the name is the same as the
|
|
430
|
+
* original node. Use `getAvailableName` to get the available name for the
|
|
431
|
+
* new node in the target parent node in case of a name conflict.
|
|
432
|
+
*
|
|
427
433
|
* If one of the nodes fails to copy, the operation continues with the
|
|
428
434
|
* rest of the nodes. Use `NodeResult` to check the status of the action.
|
|
429
435
|
*
|
|
@@ -433,12 +439,23 @@ export class ProtonDriveClient {
|
|
|
433
439
|
* @returns An async generator of the results of the copy operation
|
|
434
440
|
*/
|
|
435
441
|
async *copyNodes(
|
|
436
|
-
|
|
442
|
+
nodesOrNodeUidsOrWithNames: (NodeOrUid | { uid: string; name: string })[],
|
|
437
443
|
newParentNodeUid: NodeOrUid,
|
|
438
444
|
signal?: AbortSignal,
|
|
439
445
|
): AsyncGenerator<NodeResultWithNewUid> {
|
|
440
|
-
this.logger.info(`Copying ${
|
|
441
|
-
|
|
446
|
+
this.logger.info(`Copying ${nodesOrNodeUidsOrWithNames.length} nodes to ${getUid(newParentNodeUid)}`);
|
|
447
|
+
|
|
448
|
+
const nodeUidsOrWithNames = nodesOrNodeUidsOrWithNames.map((param) => {
|
|
449
|
+
if (typeof param === 'string') {
|
|
450
|
+
return param;
|
|
451
|
+
}
|
|
452
|
+
if ('uid' in param && 'name' in param && typeof param.uid === 'string' && typeof param.name === 'string') {
|
|
453
|
+
return { uid: param.uid, name: param.name };
|
|
454
|
+
}
|
|
455
|
+
return getUid(param);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
yield* this.nodes.management.copyNodes(nodeUidsOrWithNames, getUid(newParentNodeUid), signal);
|
|
442
459
|
}
|
|
443
460
|
|
|
444
461
|
/**
|
|
@@ -2,12 +2,12 @@ import {
|
|
|
2
2
|
Logger,
|
|
3
3
|
ProtonDriveClientContructorParameters,
|
|
4
4
|
NodeOrUid,
|
|
5
|
-
|
|
5
|
+
MaybeMissingPhotoNode,
|
|
6
6
|
UploadMetadata,
|
|
7
7
|
FileDownloader,
|
|
8
8
|
FileUploader,
|
|
9
9
|
SDKEvent,
|
|
10
|
-
|
|
10
|
+
MaybePhotoNode,
|
|
11
11
|
ThumbnailType,
|
|
12
12
|
ThumbnailResult,
|
|
13
13
|
ShareNodeSettings,
|
|
@@ -22,22 +22,22 @@ import { getConfig } from './config';
|
|
|
22
22
|
import { DriveCrypto } from './crypto';
|
|
23
23
|
import { Telemetry } from './telemetry';
|
|
24
24
|
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
convertInternalMissingPhotoNodeIterator,
|
|
26
|
+
convertInternalPhotoNode,
|
|
27
|
+
convertInternalPhotoNodeIterator,
|
|
28
|
+
convertInternalPhotoNodePromise,
|
|
29
29
|
getUid,
|
|
30
30
|
getUids,
|
|
31
31
|
} from './transformers';
|
|
32
32
|
import { DriveAPIService } from './internal/apiService';
|
|
33
33
|
import { initDownloadModule } from './internal/download';
|
|
34
34
|
import { DriveEventsService, DriveListener, EventSubscription } from './internal/events';
|
|
35
|
-
import { initNodesModule } from './internal/nodes';
|
|
36
35
|
import {
|
|
37
36
|
PHOTOS_SHARE_TARGET_TYPES,
|
|
38
37
|
initPhotosModule,
|
|
39
38
|
initPhotoSharesModule,
|
|
40
39
|
initPhotoUploadModule,
|
|
40
|
+
initPhotosNodesModule,
|
|
41
41
|
} from './internal/photos';
|
|
42
42
|
import { SDKEvents } from './internal/sdkEvents';
|
|
43
43
|
import { initSharesModule } from './internal/shares';
|
|
@@ -56,7 +56,7 @@ export class ProtonDrivePhotosClient {
|
|
|
56
56
|
private sdkEvents: SDKEvents;
|
|
57
57
|
private events: DriveEventsService;
|
|
58
58
|
private photoShares: ReturnType<typeof initPhotoSharesModule>;
|
|
59
|
-
private nodes: ReturnType<typeof
|
|
59
|
+
private nodes: ReturnType<typeof initPhotosNodesModule>;
|
|
60
60
|
private sharing: ReturnType<typeof initSharingModule>;
|
|
61
61
|
private download: ReturnType<typeof initDownloadModule>;
|
|
62
62
|
private upload: ReturnType<typeof initPhotoUploadModule>;
|
|
@@ -107,7 +107,7 @@ export class ProtonDrivePhotosClient {
|
|
|
107
107
|
cryptoModule,
|
|
108
108
|
coreShares,
|
|
109
109
|
);
|
|
110
|
-
this.nodes =
|
|
110
|
+
this.nodes = initPhotosNodesModule(
|
|
111
111
|
telemetry,
|
|
112
112
|
apiService,
|
|
113
113
|
entitiesCache,
|
|
@@ -224,9 +224,9 @@ export class ProtonDrivePhotosClient {
|
|
|
224
224
|
*
|
|
225
225
|
* See `ProtonDriveClient.iterateTrashedNodes` for more information.
|
|
226
226
|
*/
|
|
227
|
-
async *iterateTrashedNodes(signal?: AbortSignal): AsyncGenerator<
|
|
227
|
+
async *iterateTrashedNodes(signal?: AbortSignal): AsyncGenerator<MaybePhotoNode> {
|
|
228
228
|
this.logger.info('Iterating trashed nodes');
|
|
229
|
-
yield*
|
|
229
|
+
yield * convertInternalPhotoNodeIterator(this.nodes.access.iterateTrashedNodes(signal));
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
/**
|
|
@@ -234,10 +234,10 @@ export class ProtonDrivePhotosClient {
|
|
|
234
234
|
*
|
|
235
235
|
* See `ProtonDriveClient.iterateNodes` for more information.
|
|
236
236
|
*/
|
|
237
|
-
async *iterateNodes(nodeUids: NodeOrUid[], signal?: AbortSignal): AsyncGenerator<
|
|
237
|
+
async *iterateNodes(nodeUids: NodeOrUid[], signal?: AbortSignal): AsyncGenerator<MaybeMissingPhotoNode> {
|
|
238
238
|
this.logger.info(`Iterating ${nodeUids.length} nodes`);
|
|
239
239
|
// TODO: expose photo type
|
|
240
|
-
yield*
|
|
240
|
+
yield * convertInternalMissingPhotoNodeIterator(this.nodes.access.iterateNodes(getUids(nodeUids), signal));
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
/**
|
|
@@ -245,9 +245,9 @@ export class ProtonDrivePhotosClient {
|
|
|
245
245
|
*
|
|
246
246
|
* See `ProtonDriveClient.getNode` for more information.
|
|
247
247
|
*/
|
|
248
|
-
async getNode(nodeUid: NodeOrUid): Promise<
|
|
248
|
+
async getNode(nodeUid: NodeOrUid): Promise<MaybePhotoNode> {
|
|
249
249
|
this.logger.info(`Getting node ${getUid(nodeUid)}`);
|
|
250
|
-
return
|
|
250
|
+
return convertInternalPhotoNodePromise(this.nodes.access.getNode(getUid(nodeUid)));
|
|
251
251
|
}
|
|
252
252
|
|
|
253
253
|
/**
|
|
@@ -255,9 +255,9 @@ export class ProtonDrivePhotosClient {
|
|
|
255
255
|
*
|
|
256
256
|
* See `ProtonDriveClient.renameNode` for more information.
|
|
257
257
|
*/
|
|
258
|
-
async renameNode(nodeUid: NodeOrUid, newName: string): Promise<
|
|
258
|
+
async renameNode(nodeUid: NodeOrUid, newName: string): Promise<MaybePhotoNode> {
|
|
259
259
|
this.logger.info(`Renaming node ${getUid(nodeUid)}`);
|
|
260
|
-
return
|
|
260
|
+
return convertInternalPhotoNodePromise(this.nodes.management.renameNode(getUid(nodeUid), newName));
|
|
261
261
|
}
|
|
262
262
|
|
|
263
263
|
/**
|
|
@@ -305,9 +305,9 @@ export class ProtonDrivePhotosClient {
|
|
|
305
305
|
*
|
|
306
306
|
* See `ProtonDriveClient.iterateSharedNodes` for more information.
|
|
307
307
|
*/
|
|
308
|
-
async *iterateSharedNodes(signal?: AbortSignal): AsyncGenerator<
|
|
308
|
+
async *iterateSharedNodes(signal?: AbortSignal): AsyncGenerator<MaybePhotoNode> {
|
|
309
309
|
this.logger.info('Iterating shared nodes by me');
|
|
310
|
-
yield*
|
|
310
|
+
yield * convertInternalPhotoNodeIterator(this.sharing.access.iterateSharedNodes(signal));
|
|
311
311
|
}
|
|
312
312
|
|
|
313
313
|
/**
|
|
@@ -315,11 +315,11 @@ export class ProtonDrivePhotosClient {
|
|
|
315
315
|
*
|
|
316
316
|
* See `ProtonDriveClient.iterateSharedNodesWithMe` for more information.
|
|
317
317
|
*/
|
|
318
|
-
async *iterateSharedNodesWithMe(signal?: AbortSignal): AsyncGenerator<
|
|
318
|
+
async *iterateSharedNodesWithMe(signal?: AbortSignal): AsyncGenerator<MaybePhotoNode> {
|
|
319
319
|
this.logger.info('Iterating shared nodes with me');
|
|
320
320
|
|
|
321
321
|
for await (const node of this.sharing.access.iterateSharedNodesWithMe(signal)) {
|
|
322
|
-
yield
|
|
322
|
+
yield convertInternalPhotoNode(node);
|
|
323
323
|
}
|
|
324
324
|
}
|
|
325
325
|
|
|
@@ -482,9 +482,9 @@ export class ProtonDrivePhotosClient {
|
|
|
482
482
|
*
|
|
483
483
|
* The output is not sorted and the order of the nodes is not guaranteed.
|
|
484
484
|
*/
|
|
485
|
-
async *iterateAlbums(signal?: AbortSignal): AsyncGenerator<
|
|
485
|
+
async *iterateAlbums(signal?: AbortSignal): AsyncGenerator<MaybePhotoNode> {
|
|
486
486
|
this.logger.info('Iterating albums');
|
|
487
487
|
// TODO: expose album type
|
|
488
|
-
yield*
|
|
488
|
+
yield * convertInternalPhotoNodeIterator(this.photos.albums.iterateAlbums(signal));
|
|
489
489
|
}
|
|
490
490
|
}
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
UploadMetadata,
|
|
19
19
|
FileUploader,
|
|
20
20
|
NodeResult,
|
|
21
|
+
SDKEvent,
|
|
21
22
|
} from './interface';
|
|
22
23
|
import { Telemetry } from './telemetry';
|
|
23
24
|
import {
|
|
@@ -164,6 +165,16 @@ export class ProtonDrivePublicLinkClient {
|
|
|
164
165
|
};
|
|
165
166
|
}
|
|
166
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Subscribes to the general SDK events.
|
|
170
|
+
*
|
|
171
|
+
* See `ProtonDriveClient.onMessage` for more information.
|
|
172
|
+
*/
|
|
173
|
+
onMessage(eventName: SDKEvent, callback: () => void): () => void {
|
|
174
|
+
this.logger.debug(`Subscribing to event ${eventName}`);
|
|
175
|
+
return this.sdkEvents.addListener(eventName, callback);
|
|
176
|
+
}
|
|
177
|
+
|
|
167
178
|
/**
|
|
168
179
|
* @returns The root folder to the public link.
|
|
169
180
|
*/
|