@protontech/drive-sdk 0.6.2 → 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/apiService.d.ts +2 -2
- package/dist/internal/apiService/apiService.js.map +1 -1
- package/dist/internal/apiService/driveTypes.d.ts +1294 -517
- package/dist/internal/apiService/errors.js +4 -3
- package/dist/internal/apiService/errors.js.map +1 -1
- package/dist/internal/download/cryptoService.js +8 -6
- package/dist/internal/download/cryptoService.js.map +1 -1
- package/dist/internal/download/fileDownloader.d.ts +2 -1
- package/dist/internal/download/fileDownloader.js +6 -3
- package/dist/internal/download/fileDownloader.js.map +1 -1
- package/dist/internal/download/index.d.ts +1 -1
- package/dist/internal/download/index.js +3 -3
- package/dist/internal/download/index.js.map +1 -1
- 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 +68 -16
- package/dist/internal/nodes/apiService.js +138 -85
- package/dist/internal/nodes/apiService.js.map +1 -1
- package/dist/internal/nodes/apiService.test.js +7 -5
- 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/cryptoReporter.d.ts +3 -3
- package/dist/internal/nodes/cryptoReporter.js.map +1 -1
- package/dist/internal/nodes/cryptoService.d.ts +13 -22
- package/dist/internal/nodes/cryptoService.js +47 -16
- package/dist/internal/nodes/cryptoService.js.map +1 -1
- package/dist/internal/nodes/cryptoService.test.js +262 -17
- 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 +14 -3
- package/dist/internal/nodes/nodesAccess.d.ts +36 -20
- package/dist/internal/nodes/nodesAccess.js +54 -29
- package/dist/internal/nodes/nodesAccess.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.d.ts +34 -14
- package/dist/internal/nodes/nodesManagement.js +44 -31
- package/dist/internal/nodes/nodesManagement.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.test.js +60 -14
- 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/internal/photos/upload.d.ts +2 -2
- package/dist/internal/photos/upload.js.map +1 -1
- package/dist/internal/sharingPublic/index.d.ts +6 -6
- package/dist/internal/sharingPublic/index.js +8 -7
- package/dist/internal/sharingPublic/index.js.map +1 -1
- package/dist/internal/sharingPublic/nodes.d.ts +16 -3
- package/dist/internal/sharingPublic/nodes.js +34 -2
- package/dist/internal/sharingPublic/nodes.js.map +1 -1
- package/dist/internal/sharingPublic/unauthApiService.d.ts +17 -0
- package/dist/internal/sharingPublic/unauthApiService.js +31 -0
- package/dist/internal/sharingPublic/unauthApiService.js.map +1 -0
- package/dist/internal/sharingPublic/unauthApiService.test.d.ts +1 -0
- package/dist/internal/sharingPublic/unauthApiService.test.js +27 -0
- package/dist/internal/sharingPublic/unauthApiService.test.js.map +1 -0
- package/dist/internal/upload/apiService.d.ts +4 -3
- package/dist/internal/upload/apiService.js.map +1 -1
- package/dist/internal/upload/cryptoService.d.ts +8 -3
- package/dist/internal/upload/cryptoService.js +45 -9
- package/dist/internal/upload/cryptoService.js.map +1 -1
- package/dist/internal/upload/fileUploader.test.js +1 -1
- package/dist/internal/upload/fileUploader.test.js.map +1 -1
- package/dist/internal/upload/interface.d.ts +25 -13
- package/dist/internal/upload/manager.js +7 -4
- package/dist/internal/upload/manager.js.map +1 -1
- package/dist/internal/upload/manager.test.js +5 -4
- package/dist/internal/upload/manager.test.js.map +1 -1
- package/dist/internal/upload/streamUploader.js +9 -4
- package/dist/internal/upload/streamUploader.js.map +1 -1
- package/dist/internal/upload/streamUploader.test.js +8 -5
- package/dist/internal/upload/streamUploader.test.js.map +1 -1
- package/dist/protonDriveClient.d.ts +11 -2
- package/dist/protonDriveClient.js +20 -4
- 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 +9 -2
- package/dist/protonDrivePublicLinkClient.js +16 -5
- 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/apiService.ts +2 -2
- package/src/internal/apiService/driveTypes.ts +1294 -517
- package/src/internal/apiService/errors.ts +5 -4
- package/src/internal/download/cryptoService.ts +13 -6
- package/src/internal/download/fileDownloader.ts +4 -2
- package/src/internal/download/index.ts +3 -0
- package/src/internal/errors.ts +4 -0
- package/src/internal/nodes/apiService.test.ts +7 -5
- package/src/internal/nodes/apiService.ts +210 -124
- package/src/internal/nodes/cache.test.ts +1 -0
- package/src/internal/nodes/cache.ts +32 -13
- package/src/internal/nodes/cryptoReporter.ts +3 -3
- package/src/internal/nodes/cryptoService.test.ts +380 -18
- package/src/internal/nodes/cryptoService.ts +77 -36
- package/src/internal/nodes/events.ts +2 -2
- package/src/internal/nodes/index.test.ts +1 -0
- package/src/internal/nodes/interface.ts +17 -2
- package/src/internal/nodes/nodesAccess.ts +99 -54
- package/src/internal/nodes/nodesManagement.test.ts +69 -14
- package/src/internal/nodes/nodesManagement.ts +94 -48
- 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/internal/photos/upload.ts +3 -3
- package/src/internal/sharingPublic/index.ts +7 -3
- package/src/internal/sharingPublic/nodes.ts +43 -2
- package/src/internal/sharingPublic/unauthApiService.test.ts +29 -0
- package/src/internal/sharingPublic/unauthApiService.ts +32 -0
- package/src/internal/upload/apiService.ts +4 -3
- package/src/internal/upload/cryptoService.ts +73 -12
- package/src/internal/upload/fileUploader.test.ts +1 -1
- package/src/internal/upload/interface.ts +24 -13
- package/src/internal/upload/manager.test.ts +5 -4
- package/src/internal/upload/manager.ts +7 -4
- package/src/internal/upload/streamUploader.test.ts +8 -5
- package/src/internal/upload/streamUploader.ts +10 -4
- package/src/protonDriveClient.ts +27 -5
- package/src/protonDrivePhotosClient.ts +23 -23
- package/src/protonDrivePublicLinkClient.ts +19 -3
- package/src/transformers.ts +49 -2
|
@@ -6,6 +6,7 @@ import { DecryptedNode } from './interface';
|
|
|
6
6
|
import { NodesManagement } from './nodesManagement';
|
|
7
7
|
import { NodeResult } from '../../interface';
|
|
8
8
|
import { NodeOutOfSyncError } from './errors';
|
|
9
|
+
import { ValidationError } from '../../errors';
|
|
9
10
|
|
|
10
11
|
describe('NodesManagement', () => {
|
|
11
12
|
let apiService: NodeAPIService;
|
|
@@ -57,7 +58,7 @@ describe('NodesManagement', () => {
|
|
|
57
58
|
restoreNodes: jest.fn(async function* (uids) {
|
|
58
59
|
yield* uids.map((uid) => ({ ok: true, uid }) as NodeResult);
|
|
59
60
|
}),
|
|
60
|
-
|
|
61
|
+
deleteTrashedNodes: jest.fn(async function* (uids) {
|
|
61
62
|
yield* uids.map((uid) => ({ ok: true, uid }) as NodeResult);
|
|
62
63
|
}),
|
|
63
64
|
createFolder: jest.fn(),
|
|
@@ -117,7 +118,12 @@ describe('NodesManagement', () => {
|
|
|
117
118
|
nameSessionKey: `${uid}-nameSessionKey`,
|
|
118
119
|
}),
|
|
119
120
|
),
|
|
120
|
-
|
|
121
|
+
getNodeSigningKeys: jest.fn().mockResolvedValue({
|
|
122
|
+
type: 'userAddress',
|
|
123
|
+
email: 'root-email',
|
|
124
|
+
addressId: 'root-addressId',
|
|
125
|
+
key: 'root-key',
|
|
126
|
+
}),
|
|
121
127
|
notifyNodeChanged: jest.fn(),
|
|
122
128
|
notifyNodeDeleted: jest.fn(),
|
|
123
129
|
notifyChildCreated: jest.fn(),
|
|
@@ -136,11 +142,11 @@ describe('NodesManagement', () => {
|
|
|
136
142
|
nameAuthor: { ok: true, value: 'newSignatureEmail' },
|
|
137
143
|
hash: 'newHash',
|
|
138
144
|
});
|
|
139
|
-
expect(nodesAccess.
|
|
145
|
+
expect(nodesAccess.getNodeSigningKeys).toHaveBeenCalledWith({ nodeUid: 'nodeUid', parentNodeUid: 'parentUid' });
|
|
140
146
|
expect(cryptoService.encryptNewName).toHaveBeenCalledWith(
|
|
141
147
|
{ key: 'parentUid-key', hashKey: 'parentUid-hashKey' },
|
|
142
148
|
'nodeUid-nameSessionKey',
|
|
143
|
-
{ email: 'root-email',
|
|
149
|
+
{ type: 'userAddress', email: 'root-email', addressId: 'root-addressId', key: 'root-key' },
|
|
144
150
|
'new name',
|
|
145
151
|
);
|
|
146
152
|
expect(apiService.renameNode).toHaveBeenCalledWith(
|
|
@@ -181,9 +187,12 @@ describe('NodesManagement', () => {
|
|
|
181
187
|
keyAuthor: { ok: true, value: 'movedSignatureEmail' },
|
|
182
188
|
nameAuthor: { ok: true, value: 'movedNameSignatureEmail' },
|
|
183
189
|
});
|
|
184
|
-
expect(nodesAccess.
|
|
190
|
+
expect(nodesAccess.getNodeSigningKeys).toHaveBeenCalledWith({
|
|
191
|
+
nodeUid: 'nodeUid',
|
|
192
|
+
parentNodeUid: 'newParentNodeUid',
|
|
193
|
+
});
|
|
185
194
|
expect(cryptoService.encryptNodeWithNewParent).toHaveBeenCalledWith(
|
|
186
|
-
nodes.nodeUid,
|
|
195
|
+
nodes.nodeUid.name,
|
|
187
196
|
expect.objectContaining({
|
|
188
197
|
key: 'nodeUid-key',
|
|
189
198
|
passphrase: 'nodeUid-passphrase',
|
|
@@ -192,7 +201,7 @@ describe('NodesManagement', () => {
|
|
|
192
201
|
nameSessionKey: 'nodeUid-nameSessionKey',
|
|
193
202
|
}),
|
|
194
203
|
expect.objectContaining({ key: 'newParentNodeUid-key', hashKey: 'newParentNodeUid-hashKey' }),
|
|
195
|
-
{ email: 'root-email',
|
|
204
|
+
{ type: 'userAddress', email: 'root-email', addressId: 'root-addressId', key: 'root-key' },
|
|
196
205
|
);
|
|
197
206
|
expect(apiService.moveNode).toHaveBeenCalledWith(
|
|
198
207
|
'nodeUid',
|
|
@@ -223,7 +232,7 @@ describe('NodesManagement', () => {
|
|
|
223
232
|
const newNode = await management.moveNode('anonymousNodeUid', 'newParentNodeUid');
|
|
224
233
|
|
|
225
234
|
expect(cryptoService.encryptNodeWithNewParent).toHaveBeenCalledWith(
|
|
226
|
-
nodes.anonymousNodeUid,
|
|
235
|
+
nodes.anonymousNodeUid.name,
|
|
227
236
|
expect.objectContaining({
|
|
228
237
|
key: 'anonymousNodeUid-key',
|
|
229
238
|
passphrase: 'anonymousNodeUid-passphrase',
|
|
@@ -232,7 +241,7 @@ describe('NodesManagement', () => {
|
|
|
232
241
|
nameSessionKey: 'anonymousNodeUid-nameSessionKey',
|
|
233
242
|
}),
|
|
234
243
|
expect.objectContaining({ key: 'newParentNodeUid-key', hashKey: 'newParentNodeUid-hashKey' }),
|
|
235
|
-
{ email: 'root-email',
|
|
244
|
+
{ type: 'userAddress', email: 'root-email', addressId: 'root-addressId', key: 'root-key' },
|
|
236
245
|
);
|
|
237
246
|
expect(newNode).toEqual({
|
|
238
247
|
...nodes.anonymousNodeUid,
|
|
@@ -276,9 +285,12 @@ describe('NodesManagement', () => {
|
|
|
276
285
|
keyAuthor: { ok: true, value: 'copiedSignatureEmail' },
|
|
277
286
|
nameAuthor: { ok: true, value: 'copiedNameSignatureEmail' },
|
|
278
287
|
});
|
|
279
|
-
expect(nodesAccess.
|
|
288
|
+
expect(nodesAccess.getNodeSigningKeys).toHaveBeenCalledWith({
|
|
289
|
+
nodeUid: 'nodeUid',
|
|
290
|
+
parentNodeUid: 'newParentNodeUid',
|
|
291
|
+
});
|
|
280
292
|
expect(cryptoService.encryptNodeWithNewParent).toHaveBeenCalledWith(
|
|
281
|
-
nodes.nodeUid,
|
|
293
|
+
nodes.nodeUid.name,
|
|
282
294
|
expect.objectContaining({
|
|
283
295
|
key: 'nodeUid-key',
|
|
284
296
|
passphrase: 'nodeUid-passphrase',
|
|
@@ -287,7 +299,7 @@ describe('NodesManagement', () => {
|
|
|
287
299
|
nameSessionKey: 'nodeUid-nameSessionKey',
|
|
288
300
|
}),
|
|
289
301
|
expect.objectContaining({ key: 'newParentNodeUid-key', hashKey: 'newParentNodeUid-hashKey' }),
|
|
290
|
-
{ email: 'root-email',
|
|
302
|
+
{ type: 'userAddress', email: 'root-email', addressId: 'root-addressId', key: 'root-key' },
|
|
291
303
|
);
|
|
292
304
|
expect(apiService.copyNode).toHaveBeenCalledWith('nodeUid', {
|
|
293
305
|
parentUid: 'newParentNodeUid',
|
|
@@ -313,7 +325,7 @@ describe('NodesManagement', () => {
|
|
|
313
325
|
const newNode = await management.copyNode('anonymousNodeUid', 'newParentNodeUid');
|
|
314
326
|
|
|
315
327
|
expect(cryptoService.encryptNodeWithNewParent).toHaveBeenCalledWith(
|
|
316
|
-
nodes.anonymousNodeUid,
|
|
328
|
+
nodes.anonymousNodeUid.name,
|
|
317
329
|
expect.objectContaining({
|
|
318
330
|
key: 'anonymousNodeUid-key',
|
|
319
331
|
passphrase: 'anonymousNodeUid-passphrase',
|
|
@@ -322,7 +334,7 @@ describe('NodesManagement', () => {
|
|
|
322
334
|
nameSessionKey: 'anonymousNodeUid-nameSessionKey',
|
|
323
335
|
}),
|
|
324
336
|
expect.objectContaining({ key: 'newParentNodeUid-key', hashKey: 'newParentNodeUid-hashKey' }),
|
|
325
|
-
{ email: 'root-email',
|
|
337
|
+
{ type: 'userAddress', email: 'root-email', addressId: 'root-addressId', key: 'root-key' },
|
|
326
338
|
);
|
|
327
339
|
expect(newNode).toEqual({
|
|
328
340
|
...nodes.anonymousNodeUid,
|
|
@@ -339,6 +351,49 @@ describe('NodesManagement', () => {
|
|
|
339
351
|
});
|
|
340
352
|
});
|
|
341
353
|
|
|
354
|
+
it('copyNode manages copy of node with new name', async () => {
|
|
355
|
+
const encryptedCrypto = {
|
|
356
|
+
encryptedName: 'copiedArmoredNodeName',
|
|
357
|
+
hash: 'copiedHash',
|
|
358
|
+
armoredNodePassphrase: 'copiedArmoredNodePassphrase',
|
|
359
|
+
armoredNodePassphraseSignature: 'copiedArmoredNodePassphraseSignature',
|
|
360
|
+
signatureEmail: 'copiedSignatureEmail',
|
|
361
|
+
nameSignatureEmail: 'copiedNameSignatureEmail',
|
|
362
|
+
};
|
|
363
|
+
cryptoService.encryptNodeWithNewParent = jest.fn().mockResolvedValue(encryptedCrypto);
|
|
364
|
+
|
|
365
|
+
const newName = 'new name';
|
|
366
|
+
const newNode = await management.copyNode('nodeUid', 'newParentNodeUid', newName);
|
|
367
|
+
|
|
368
|
+
expect(newNode).toEqual({
|
|
369
|
+
...nodes.nodeUid,
|
|
370
|
+
name: { ok: true, value: newName },
|
|
371
|
+
uid: 'newCopiedNodeUid',
|
|
372
|
+
parentUid: 'newParentNodeUid',
|
|
373
|
+
encryptedName: 'copiedArmoredNodeName',
|
|
374
|
+
hash: 'copiedHash',
|
|
375
|
+
keyAuthor: { ok: true, value: 'copiedSignatureEmail' },
|
|
376
|
+
nameAuthor: { ok: true, value: 'copiedNameSignatureEmail' },
|
|
377
|
+
});
|
|
378
|
+
expect(cryptoService.encryptNodeWithNewParent).toHaveBeenCalledWith(
|
|
379
|
+
{ ok: true, value: newName },
|
|
380
|
+
expect.objectContaining({
|
|
381
|
+
key: 'nodeUid-key',
|
|
382
|
+
passphrase: 'nodeUid-passphrase',
|
|
383
|
+
passphraseSessionKey: 'nodeUid-passphraseSessionKey',
|
|
384
|
+
contentKeyPacketSessionKey: 'nodeUid-contentKeyPacketSessionKey',
|
|
385
|
+
nameSessionKey: 'nodeUid-nameSessionKey',
|
|
386
|
+
}),
|
|
387
|
+
expect.objectContaining({ key: 'newParentNodeUid-key', hashKey: 'newParentNodeUid-hashKey' }),
|
|
388
|
+
{ type: 'userAddress', email: 'root-email', addressId: 'root-addressId', key: 'root-key' },
|
|
389
|
+
);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it('copyNode throws error if name is invalid', async () => {
|
|
393
|
+
const promise = management.copyNode('nodeUid', 'newParentNodeUid', 'invalid/name');
|
|
394
|
+
await expect(promise).rejects.toThrow(ValidationError);
|
|
395
|
+
});
|
|
396
|
+
|
|
342
397
|
it('trashes node and updates cache', async () => {
|
|
343
398
|
const uids = ['v1~n1', 'v1~n2'];
|
|
344
399
|
const trashed = new Set();
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { c } from 'ttag';
|
|
2
2
|
|
|
3
|
-
import { MemberRole, NodeType, NodeResult, NodeResultWithNewUid, resultOk } from '../../interface';
|
|
3
|
+
import { MemberRole, NodeType, NodeResult, NodeResultWithNewUid, resultOk, InvalidNameError } from '../../interface';
|
|
4
4
|
import { AbortError, ValidationError } from '../../errors';
|
|
5
|
-
import { getErrorMessage } from '../errors';
|
|
5
|
+
import { createErrorFromUnknown, getErrorMessage } from '../errors';
|
|
6
6
|
import { splitNodeUid } from '../uids';
|
|
7
|
-
import {
|
|
7
|
+
import { NodeAPIServiceBase } from './apiService';
|
|
8
8
|
import { NodesCryptoCache } from './cryptoCache';
|
|
9
9
|
import { NodesCryptoService } from './cryptoService';
|
|
10
10
|
import { NodeOutOfSyncError } from './errors';
|
|
11
11
|
import { generateFolderExtendedAttributes } from './extendedAttributes';
|
|
12
|
-
import { DecryptedNode } from './interface';
|
|
12
|
+
import { DecryptedNode, EncryptedNode } from './interface';
|
|
13
13
|
import { splitExtension, joinNameAndExtension } from './nodeName';
|
|
14
|
-
import {
|
|
14
|
+
import { NodesAccessBase } from './nodesAccess';
|
|
15
15
|
import { validateNodeName } from './validations';
|
|
16
16
|
|
|
17
17
|
const AVAILABLE_NAME_BATCH_SIZE = 10;
|
|
@@ -26,12 +26,16 @@ const AVAILABLE_NAME_LIMIT = 1000;
|
|
|
26
26
|
* This module uses other modules providing low-level operations, such
|
|
27
27
|
* as API service, cache, crypto service, etc.
|
|
28
28
|
*/
|
|
29
|
-
export class
|
|
29
|
+
export abstract class NodesManagementBase<
|
|
30
|
+
TEncryptedNode extends EncryptedNode = EncryptedNode,
|
|
31
|
+
TDecryptedNode extends DecryptedNode = DecryptedNode,
|
|
32
|
+
TNodesCryptoService extends NodesCryptoService = NodesCryptoService,
|
|
33
|
+
> {
|
|
30
34
|
constructor(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
protected apiService: NodeAPIServiceBase<TEncryptedNode>,
|
|
36
|
+
protected cryptoCache: NodesCryptoCache,
|
|
37
|
+
protected cryptoService: NodesCryptoService,
|
|
38
|
+
protected nodesAccess: NodesAccessBase<TEncryptedNode, TDecryptedNode, TNodesCryptoService>,
|
|
35
39
|
) {
|
|
36
40
|
this.apiService = apiService;
|
|
37
41
|
this.cryptoCache = cryptoCache;
|
|
@@ -43,13 +47,13 @@ export class NodesManagement {
|
|
|
43
47
|
nodeUid: string,
|
|
44
48
|
newName: string,
|
|
45
49
|
options = { allowRenameRootNode: false },
|
|
46
|
-
): Promise<
|
|
50
|
+
): Promise<TDecryptedNode> {
|
|
47
51
|
validateNodeName(newName);
|
|
48
52
|
|
|
49
53
|
const node = await this.nodesAccess.getNode(nodeUid);
|
|
50
54
|
const { nameSessionKey: nodeNameSessionKey } = await this.nodesAccess.getNodePrivateAndSessionKeys(nodeUid);
|
|
51
55
|
const parentKeys = await this.nodesAccess.getParentKeys(node);
|
|
52
|
-
const
|
|
56
|
+
const signingKeys = await this.nodesAccess.getNodeSigningKeys({ nodeUid, parentNodeUid: node.parentUid });
|
|
53
57
|
|
|
54
58
|
if (!options.allowRenameRootNode && (!node.hash || !parentKeys.hashKey)) {
|
|
55
59
|
throw new ValidationError(c('Error').t`Renaming root item is not allowed`);
|
|
@@ -58,7 +62,7 @@ export class NodesManagement {
|
|
|
58
62
|
const { signatureEmail, armoredNodeName, hash } = await this.cryptoService.encryptNewName(
|
|
59
63
|
parentKeys,
|
|
60
64
|
nodeNameSessionKey,
|
|
61
|
-
|
|
65
|
+
signingKeys,
|
|
62
66
|
newName,
|
|
63
67
|
);
|
|
64
68
|
|
|
@@ -91,11 +95,11 @@ export class NodesManagement {
|
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
await this.nodesAccess.notifyNodeChanged(nodeUid);
|
|
94
|
-
const newNode:
|
|
98
|
+
const newNode: TDecryptedNode = {
|
|
95
99
|
...node,
|
|
96
100
|
name: resultOk(newName),
|
|
97
101
|
encryptedName: armoredNodeName,
|
|
98
|
-
nameAuthor: resultOk(signatureEmail),
|
|
102
|
+
nameAuthor: resultOk(signatureEmail || null),
|
|
99
103
|
hash,
|
|
100
104
|
};
|
|
101
105
|
return newNode;
|
|
@@ -123,15 +127,13 @@ export class NodesManagement {
|
|
|
123
127
|
}
|
|
124
128
|
}
|
|
125
129
|
|
|
126
|
-
async moveNode(nodeUid: string, newParentUid: string): Promise<
|
|
127
|
-
const
|
|
128
|
-
this.nodesAccess.getNode(nodeUid),
|
|
129
|
-
this.nodesAccess.getRootNodeEmailKey(newParentUid),
|
|
130
|
-
]);
|
|
130
|
+
async moveNode(nodeUid: string, newParentUid: string): Promise<TDecryptedNode> {
|
|
131
|
+
const node = await this.nodesAccess.getNode(nodeUid);
|
|
131
132
|
|
|
132
|
-
const [keys, newParentKeys] = await Promise.all([
|
|
133
|
+
const [keys, newParentKeys, signingKeys] = await Promise.all([
|
|
133
134
|
this.nodesAccess.getNodePrivateAndSessionKeys(nodeUid),
|
|
134
135
|
this.nodesAccess.getNodeKeys(newParentUid),
|
|
136
|
+
this.nodesAccess.getNodeSigningKeys({ nodeUid, parentNodeUid: newParentUid }),
|
|
135
137
|
]);
|
|
136
138
|
|
|
137
139
|
if (!node.hash) {
|
|
@@ -142,10 +144,10 @@ export class NodesManagement {
|
|
|
142
144
|
}
|
|
143
145
|
|
|
144
146
|
const encryptedCrypto = await this.cryptoService.encryptNodeWithNewParent(
|
|
145
|
-
node,
|
|
147
|
+
node.name,
|
|
146
148
|
keys,
|
|
147
149
|
{ key: newParentKeys.key, hashKey: newParentKeys.hashKey },
|
|
148
|
-
|
|
150
|
+
signingKeys,
|
|
149
151
|
);
|
|
150
152
|
|
|
151
153
|
// Node could be uploaded or renamed by anonymous user and thus have
|
|
@@ -174,7 +176,7 @@ export class NodesManagement {
|
|
|
174
176
|
// TODO: When moving photos, we need to pass content hash.
|
|
175
177
|
},
|
|
176
178
|
);
|
|
177
|
-
const newNode:
|
|
179
|
+
const newNode: TDecryptedNode = {
|
|
178
180
|
...node,
|
|
179
181
|
encryptedName: encryptedCrypto.encryptedName,
|
|
180
182
|
parentUid: newParentUid,
|
|
@@ -188,16 +190,18 @@ export class NodesManagement {
|
|
|
188
190
|
|
|
189
191
|
// Improvement requested: copy nodes in parallel using copy_multiple endpoint
|
|
190
192
|
async *copyNodes(
|
|
191
|
-
|
|
193
|
+
nodeUidsOrWithNames: (string | { uid: string; name: string })[],
|
|
192
194
|
newParentNodeUid: string,
|
|
193
195
|
signal?: AbortSignal,
|
|
194
196
|
): AsyncGenerator<NodeResultWithNewUid> {
|
|
195
|
-
for (const
|
|
197
|
+
for (const nodeUidOrWithName of nodeUidsOrWithNames) {
|
|
196
198
|
if (signal?.aborted) {
|
|
197
199
|
throw new AbortError(c('Error').t`Copy operation aborted`);
|
|
198
200
|
}
|
|
201
|
+
const nodeUid = typeof nodeUidOrWithName === 'string' ? nodeUidOrWithName : nodeUidOrWithName.uid;
|
|
202
|
+
const name = typeof nodeUidOrWithName === 'string' ? undefined : nodeUidOrWithName.name;
|
|
199
203
|
try {
|
|
200
|
-
const { uid: newNodeUid } = await this.copyNode(nodeUid, newParentNodeUid);
|
|
204
|
+
const { uid: newNodeUid } = await this.copyNode(nodeUid, newParentNodeUid, name);
|
|
201
205
|
yield {
|
|
202
206
|
uid: nodeUid,
|
|
203
207
|
newUid: newNodeUid,
|
|
@@ -207,21 +211,24 @@ export class NodesManagement {
|
|
|
207
211
|
yield {
|
|
208
212
|
uid: nodeUid,
|
|
209
213
|
ok: false,
|
|
210
|
-
error:
|
|
214
|
+
error: createErrorFromUnknown(error),
|
|
211
215
|
};
|
|
212
216
|
}
|
|
213
217
|
}
|
|
214
218
|
}
|
|
215
219
|
|
|
216
|
-
async copyNode(nodeUid: string, newParentUid: string): Promise<
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
]);
|
|
220
|
+
async copyNode(nodeUid: string, newParentUid: string, name?: string): Promise<TDecryptedNode> {
|
|
221
|
+
if (name) {
|
|
222
|
+
validateNodeName(name);
|
|
223
|
+
}
|
|
221
224
|
|
|
222
|
-
const
|
|
225
|
+
const node = await this.nodesAccess.getNode(nodeUid);
|
|
226
|
+
const nodeName = name ? resultOk<string, Error | InvalidNameError>(name) : node.name;
|
|
227
|
+
|
|
228
|
+
const [keys, newParentKeys, signingKeys] = await Promise.all([
|
|
223
229
|
this.nodesAccess.getNodePrivateAndSessionKeys(nodeUid),
|
|
224
230
|
this.nodesAccess.getNodeKeys(newParentUid),
|
|
231
|
+
this.nodesAccess.getNodeSigningKeys({ nodeUid, parentNodeUid: newParentUid }),
|
|
225
232
|
]);
|
|
226
233
|
|
|
227
234
|
if (!newParentKeys.hashKey) {
|
|
@@ -229,10 +236,10 @@ export class NodesManagement {
|
|
|
229
236
|
}
|
|
230
237
|
|
|
231
238
|
const encryptedCrypto = await this.cryptoService.encryptNodeWithNewParent(
|
|
232
|
-
|
|
239
|
+
nodeName,
|
|
233
240
|
keys,
|
|
234
241
|
{ key: newParentKeys.key, hashKey: newParentKeys.hashKey },
|
|
235
|
-
|
|
242
|
+
signingKeys,
|
|
236
243
|
);
|
|
237
244
|
|
|
238
245
|
// Node could be uploaded or renamed by anonymous user and thus have
|
|
@@ -254,8 +261,9 @@ export class NodesManagement {
|
|
|
254
261
|
nameSignatureEmail: encryptedCrypto.nameSignatureEmail,
|
|
255
262
|
hash: encryptedCrypto.hash,
|
|
256
263
|
});
|
|
257
|
-
const newNode:
|
|
264
|
+
const newNode: TDecryptedNode = {
|
|
258
265
|
...node,
|
|
266
|
+
name: nodeName,
|
|
259
267
|
uid: newNodeUid,
|
|
260
268
|
encryptedName: encryptedCrypto.encryptedName,
|
|
261
269
|
parentUid: newParentUid,
|
|
@@ -286,7 +294,7 @@ export class NodesManagement {
|
|
|
286
294
|
}
|
|
287
295
|
|
|
288
296
|
async *deleteNodes(nodeUids: string[], signal?: AbortSignal): AsyncGenerator<NodeResult> {
|
|
289
|
-
for await (const result of this.apiService.
|
|
297
|
+
for await (const result of this.apiService.deleteTrashedNodes(nodeUids, signal)) {
|
|
290
298
|
if (result.ok) {
|
|
291
299
|
await this.nodesAccess.notifyNodeDeleted(result.uid);
|
|
292
300
|
}
|
|
@@ -295,7 +303,7 @@ export class NodesManagement {
|
|
|
295
303
|
}
|
|
296
304
|
|
|
297
305
|
// FIXME create test for create folder
|
|
298
|
-
async createFolder(parentNodeUid: string, folderName: string, modificationTime?: Date): Promise<
|
|
306
|
+
async createFolder(parentNodeUid: string, folderName: string, modificationTime?: Date): Promise<TDecryptedNode> {
|
|
299
307
|
validateNodeName(folderName);
|
|
300
308
|
|
|
301
309
|
const parentKeys = await this.nodesAccess.getNodeKeys(parentNodeUid);
|
|
@@ -303,12 +311,12 @@ export class NodesManagement {
|
|
|
303
311
|
throw new ValidationError(c('Error').t`Creating folders in non-folders is not allowed`);
|
|
304
312
|
}
|
|
305
313
|
|
|
306
|
-
const
|
|
314
|
+
const signingKeys = await this.nodesAccess.getNodeSigningKeys({ parentNodeUid });
|
|
307
315
|
const extendedAttributes = generateFolderExtendedAttributes(modificationTime);
|
|
308
316
|
|
|
309
317
|
const { encryptedCrypto, keys } = await this.cryptoService.createFolder(
|
|
310
318
|
{ key: parentKeys.key, hashKey: parentKeys.hashKey },
|
|
311
|
-
|
|
319
|
+
signingKeys,
|
|
312
320
|
folderName,
|
|
313
321
|
extendedAttributes,
|
|
314
322
|
);
|
|
@@ -324,8 +332,33 @@ export class NodesManagement {
|
|
|
324
332
|
});
|
|
325
333
|
|
|
326
334
|
await this.nodesAccess.notifyChildCreated(parentNodeUid);
|
|
335
|
+
const node = this.generateNodeFolder(nodeUid, parentNodeUid, folderName, encryptedCrypto);
|
|
336
|
+
await this.cryptoCache.setNodeKeys(nodeUid, keys);
|
|
337
|
+
return node;
|
|
338
|
+
}
|
|
327
339
|
|
|
328
|
-
|
|
340
|
+
protected abstract generateNodeFolder(
|
|
341
|
+
nodeUid: string,
|
|
342
|
+
parentUid: string,
|
|
343
|
+
name: string,
|
|
344
|
+
encryptedCrypto: {
|
|
345
|
+
hash: string;
|
|
346
|
+
encryptedName: string;
|
|
347
|
+
signatureEmail: string | null;
|
|
348
|
+
},
|
|
349
|
+
): TDecryptedNode;
|
|
350
|
+
|
|
351
|
+
protected generateNodeFolderBase(
|
|
352
|
+
nodeUid: string,
|
|
353
|
+
parentNodeUid: string,
|
|
354
|
+
name: string,
|
|
355
|
+
encryptedCrypto: {
|
|
356
|
+
hash: string;
|
|
357
|
+
encryptedName: string;
|
|
358
|
+
signatureEmail: string | null;
|
|
359
|
+
},
|
|
360
|
+
): DecryptedNode {
|
|
361
|
+
return {
|
|
329
362
|
// Internal metadata
|
|
330
363
|
hash: encryptedCrypto.hash,
|
|
331
364
|
encryptedName: encryptedCrypto.encryptedName,
|
|
@@ -336,6 +369,7 @@ export class NodesManagement {
|
|
|
336
369
|
type: NodeType.Folder,
|
|
337
370
|
mediaType: 'Folder',
|
|
338
371
|
creationTime: new Date(),
|
|
372
|
+
modificationTime: new Date(),
|
|
339
373
|
|
|
340
374
|
// Share node metadata
|
|
341
375
|
isShared: false,
|
|
@@ -344,14 +378,11 @@ export class NodesManagement {
|
|
|
344
378
|
|
|
345
379
|
// Decrypted metadata
|
|
346
380
|
isStale: false,
|
|
347
|
-
keyAuthor: resultOk(encryptedCrypto.signatureEmail),
|
|
348
|
-
nameAuthor: resultOk(encryptedCrypto.signatureEmail),
|
|
349
|
-
name: resultOk(
|
|
381
|
+
keyAuthor: resultOk(encryptedCrypto.signatureEmail || null),
|
|
382
|
+
nameAuthor: resultOk(encryptedCrypto.signatureEmail || null),
|
|
383
|
+
name: resultOk(name),
|
|
350
384
|
treeEventScopeId: splitNodeUid(nodeUid).volumeId,
|
|
351
385
|
};
|
|
352
|
-
|
|
353
|
-
await this.cryptoCache.setNodeKeys(nodeUid, keys);
|
|
354
|
-
return node;
|
|
355
386
|
}
|
|
356
387
|
|
|
357
388
|
async findAvailableName(parentFolderUid: string, name: string): Promise<string> {
|
|
@@ -392,3 +423,18 @@ export class NodesManagement {
|
|
|
392
423
|
throw new ValidationError(c('Error').t`No available name found`);
|
|
393
424
|
}
|
|
394
425
|
}
|
|
426
|
+
|
|
427
|
+
export class NodesManagement extends NodesManagementBase {
|
|
428
|
+
protected generateNodeFolder(
|
|
429
|
+
nodeUid: string,
|
|
430
|
+
parentNodeUid: string,
|
|
431
|
+
name: string,
|
|
432
|
+
encryptedCrypto: {
|
|
433
|
+
hash: string;
|
|
434
|
+
encryptedName: string;
|
|
435
|
+
signatureEmail: string | null;
|
|
436
|
+
},
|
|
437
|
+
): DecryptedNode {
|
|
438
|
+
return this.generateNodeFolderBase(nodeUid, parentNodeUid, name, encryptedCrypto);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Logger } from '../../interface';
|
|
2
2
|
import { makeNodeUidFromRevisionUid } from '../uids';
|
|
3
|
-
import {
|
|
3
|
+
import { NodeAPIServiceBase } from './apiService';
|
|
4
4
|
import { NodesCryptoService } from './cryptoService';
|
|
5
5
|
import { NodesAccess } from './nodesAccess';
|
|
6
6
|
import { parseFileExtendedAttributes } from './extendedAttributes';
|
|
@@ -12,9 +12,9 @@ import { DecryptedRevision } from './interface';
|
|
|
12
12
|
export class NodesRevisons {
|
|
13
13
|
constructor(
|
|
14
14
|
private logger: Logger,
|
|
15
|
-
private apiService:
|
|
15
|
+
private apiService: NodeAPIServiceBase,
|
|
16
16
|
private cryptoService: NodesCryptoService,
|
|
17
|
-
private nodesAccess: NodesAccess,
|
|
17
|
+
private nodesAccess: Pick<NodesAccess, 'getNodeKeys'>,
|
|
18
18
|
) {
|
|
19
19
|
this.logger = logger;
|
|
20
20
|
this.apiService = apiService;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BatchLoading } from '../batchLoading';
|
|
2
2
|
import { DecryptedNode } from '../nodes';
|
|
3
3
|
import { PhotosAPIService } from './apiService';
|
|
4
|
-
import {
|
|
4
|
+
import { PhotosNodesAccess } from './nodes';
|
|
5
5
|
import { PhotoSharesManager } from './shares';
|
|
6
6
|
|
|
7
7
|
const BATCH_LOADING_SIZE = 10;
|
|
@@ -13,7 +13,7 @@ export class Albums {
|
|
|
13
13
|
constructor(
|
|
14
14
|
private apiService: PhotosAPIService,
|
|
15
15
|
private photoShares: PhotoSharesManager,
|
|
16
|
-
private nodesService:
|
|
16
|
+
private nodesService: PhotosNodesAccess,
|
|
17
17
|
) {
|
|
18
18
|
this.apiService = apiService;
|
|
19
19
|
this.photoShares = photoShares;
|
|
@@ -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
|
+
};
|