@protontech/drive-sdk 0.2.1 → 0.3.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/crypto/interface.d.ts +5 -0
- package/dist/diagnostic/httpClient.d.ts +3 -3
- package/dist/diagnostic/interface.d.ts +26 -29
- package/dist/diagnostic/sdkDiagnostic.js +50 -24
- package/dist/diagnostic/sdkDiagnostic.js.map +1 -1
- package/dist/errors.d.ts +3 -3
- package/dist/errors.js +7 -7
- package/dist/errors.js.map +1 -1
- package/dist/interface/author.d.ts +1 -1
- package/dist/interface/events.d.ts +1 -1
- package/dist/interface/events.js.map +1 -1
- package/dist/interface/httpClient.d.ts +5 -5
- package/dist/interface/index.d.ts +15 -5
- package/dist/internal/apiService/apiService.js +12 -4
- package/dist/internal/apiService/apiService.js.map +1 -1
- package/dist/internal/apiService/errorCodes.d.ts +1 -0
- package/dist/internal/apiService/errorCodes.js.map +1 -1
- package/dist/internal/apiService/errors.d.ts +4 -3
- package/dist/internal/apiService/errors.js +7 -4
- package/dist/internal/apiService/errors.js.map +1 -1
- package/dist/internal/apiService/errors.test.js +2 -1
- package/dist/internal/apiService/errors.test.js.map +1 -1
- package/dist/internal/events/index.d.ts +1 -1
- package/dist/internal/nodes/apiService.js +3 -0
- package/dist/internal/nodes/apiService.js.map +1 -1
- package/dist/internal/nodes/apiService.test.js +18 -0
- package/dist/internal/nodes/apiService.test.js.map +1 -1
- package/dist/internal/nodes/cryptoCache.js +6 -7
- package/dist/internal/nodes/cryptoCache.js.map +1 -1
- package/dist/internal/nodes/cryptoCache.test.js +4 -7
- package/dist/internal/nodes/cryptoCache.test.js.map +1 -1
- package/dist/internal/nodes/cryptoService.js +44 -20
- package/dist/internal/nodes/cryptoService.js.map +1 -1
- package/dist/internal/nodes/nodesAccess.js +2 -2
- package/dist/internal/nodes/nodesAccess.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.js +0 -2
- package/dist/internal/nodes/nodesManagement.js.map +1 -1
- package/dist/internal/shares/cryptoCache.d.ts +4 -3
- package/dist/internal/shares/cryptoCache.js +23 -6
- package/dist/internal/shares/cryptoCache.js.map +1 -1
- package/dist/internal/shares/cryptoCache.test.js +3 -2
- package/dist/internal/shares/cryptoCache.test.js.map +1 -1
- package/dist/internal/shares/index.js +1 -1
- package/dist/internal/shares/index.js.map +1 -1
- package/dist/internal/sharing/cryptoService.js +8 -6
- package/dist/internal/sharing/cryptoService.js.map +1 -1
- package/dist/internal/sharing/cryptoService.test.js +13 -0
- package/dist/internal/sharing/cryptoService.test.js.map +1 -1
- package/dist/internal/sharing/index.js +1 -1
- package/dist/internal/sharing/index.js.map +1 -1
- package/dist/internal/sharing/interface.d.ts +0 -4
- package/dist/internal/sharing/sharingAccess.d.ts +1 -0
- package/dist/internal/sharing/sharingAccess.js +6 -1
- package/dist/internal/sharing/sharingAccess.js.map +1 -1
- package/dist/internal/sharing/sharingAccess.test.js +3 -3
- package/dist/internal/sharing/sharingAccess.test.js.map +1 -1
- package/dist/internal/sharing/sharingManagement.d.ts +3 -1
- package/dist/internal/sharing/sharingManagement.js +37 -17
- package/dist/internal/sharing/sharingManagement.js.map +1 -1
- package/dist/internal/sharing/sharingManagement.test.js +61 -14
- package/dist/internal/sharing/sharingManagement.test.js.map +1 -1
- package/dist/internal/sharingPublic/apiService.d.ts +19 -0
- package/dist/internal/sharingPublic/apiService.js +134 -0
- package/dist/internal/sharingPublic/apiService.js.map +1 -0
- package/dist/internal/sharingPublic/cryptoCache.d.ts +19 -0
- package/dist/internal/sharingPublic/cryptoCache.js +72 -0
- package/dist/internal/sharingPublic/cryptoCache.js.map +1 -0
- package/dist/internal/sharingPublic/cryptoService.d.ts +23 -0
- package/dist/internal/sharingPublic/cryptoService.js +120 -0
- package/dist/internal/sharingPublic/cryptoService.js.map +1 -0
- package/dist/internal/sharingPublic/index.d.ts +15 -0
- package/dist/internal/sharingPublic/index.js +27 -0
- package/dist/internal/sharingPublic/index.js.map +1 -0
- package/dist/internal/sharingPublic/interface.d.ts +48 -0
- package/dist/internal/sharingPublic/interface.js +3 -0
- package/dist/internal/sharingPublic/interface.js.map +1 -0
- package/dist/internal/sharingPublic/manager.d.ts +19 -0
- package/dist/internal/sharingPublic/manager.js +79 -0
- package/dist/internal/sharingPublic/manager.js.map +1 -0
- package/dist/internal/sharingPublic/session/apiService.d.ts +28 -0
- package/dist/internal/sharingPublic/session/apiService.js +55 -0
- package/dist/internal/sharingPublic/session/apiService.js.map +1 -0
- package/dist/internal/sharingPublic/session/httpClient.d.ts +16 -0
- package/dist/internal/sharingPublic/session/httpClient.js +41 -0
- package/dist/internal/sharingPublic/session/httpClient.js.map +1 -0
- package/dist/internal/sharingPublic/session/index.d.ts +1 -0
- package/dist/internal/sharingPublic/session/index.js +6 -0
- package/dist/internal/sharingPublic/session/index.js.map +1 -0
- package/dist/internal/sharingPublic/session/interface.d.ts +18 -0
- package/dist/internal/sharingPublic/session/interface.js +3 -0
- package/dist/internal/sharingPublic/session/interface.js.map +1 -0
- package/dist/internal/sharingPublic/session/manager.d.ts +49 -0
- package/dist/internal/sharingPublic/session/manager.js +75 -0
- package/dist/internal/sharingPublic/session/manager.js.map +1 -0
- package/dist/internal/sharingPublic/session/session.d.ts +34 -0
- package/dist/internal/sharingPublic/session/session.js +67 -0
- package/dist/internal/sharingPublic/session/session.js.map +1 -0
- package/dist/internal/sharingPublic/session/url.d.ts +12 -0
- package/dist/internal/sharingPublic/session/url.js +23 -0
- package/dist/internal/sharingPublic/session/url.js.map +1 -0
- package/dist/internal/sharingPublic/session/url.test.d.ts +1 -0
- package/dist/internal/sharingPublic/session/url.test.js +59 -0
- package/dist/internal/sharingPublic/session/url.test.js.map +1 -0
- package/dist/internal/upload/manager.js +1 -3
- package/dist/internal/upload/manager.js.map +1 -1
- package/dist/internal/upload/manager.test.js +2 -2
- package/dist/internal/upload/manager.test.js.map +1 -1
- package/dist/protonDriveClient.d.ts +25 -10
- package/dist/protonDriveClient.js +44 -22
- package/dist/protonDriveClient.js.map +1 -1
- package/dist/protonDrivePublicLinkClient.d.ts +48 -0
- package/dist/protonDrivePublicLinkClient.js +71 -0
- package/dist/protonDrivePublicLinkClient.js.map +1 -0
- package/package.json +1 -1
- package/src/crypto/interface.ts +11 -0
- package/src/diagnostic/httpClient.ts +4 -4
- package/src/diagnostic/interface.ts +27 -29
- package/src/diagnostic/sdkDiagnostic.ts +58 -30
- package/src/errors.ts +5 -5
- package/src/interface/author.ts +1 -1
- package/src/interface/events.ts +1 -7
- package/src/interface/httpClient.ts +5 -5
- package/src/interface/index.ts +18 -6
- package/src/internal/apiService/apiService.ts +13 -4
- package/src/internal/apiService/errorCodes.ts +1 -0
- package/src/internal/apiService/errors.test.ts +2 -1
- package/src/internal/apiService/errors.ts +15 -4
- package/src/internal/events/index.ts +1 -1
- package/src/internal/nodes/apiService.test.ts +28 -0
- package/src/internal/nodes/apiService.ts +3 -0
- package/src/internal/nodes/cryptoCache.test.ts +4 -7
- package/src/internal/nodes/cryptoCache.ts +6 -7
- package/src/internal/nodes/cryptoService.ts +68 -34
- package/src/internal/nodes/interface.ts +2 -0
- package/src/internal/nodes/nodesAccess.ts +2 -2
- package/src/internal/nodes/nodesManagement.ts +0 -3
- package/src/internal/shares/cryptoCache.test.ts +3 -2
- package/src/internal/shares/cryptoCache.ts +26 -7
- package/src/internal/shares/index.ts +1 -1
- package/src/internal/sharing/cryptoService.test.ts +22 -1
- package/src/internal/sharing/cryptoService.ts +8 -6
- package/src/internal/sharing/index.ts +1 -0
- package/src/internal/sharing/interface.ts +0 -4
- package/src/internal/sharing/sharingAccess.test.ts +4 -4
- package/src/internal/sharing/sharingAccess.ts +6 -0
- package/src/internal/sharing/sharingManagement.test.ts +87 -24
- package/src/internal/sharing/sharingManagement.ts +56 -16
- package/src/internal/sharingPublic/apiService.ts +164 -0
- package/src/internal/sharingPublic/cryptoCache.ts +79 -0
- package/src/internal/sharingPublic/cryptoService.ts +162 -0
- package/src/internal/sharingPublic/index.ts +40 -0
- package/src/internal/sharingPublic/interface.ts +59 -0
- package/src/internal/sharingPublic/manager.ts +85 -0
- package/src/internal/sharingPublic/session/apiService.ts +74 -0
- package/src/internal/sharingPublic/session/httpClient.ts +48 -0
- package/src/internal/sharingPublic/session/index.ts +1 -0
- package/src/internal/sharingPublic/session/interface.ts +20 -0
- package/src/internal/sharingPublic/session/manager.ts +97 -0
- package/src/internal/sharingPublic/session/session.ts +78 -0
- package/src/internal/sharingPublic/session/url.test.ts +72 -0
- package/src/internal/sharingPublic/session/url.ts +23 -0
- package/src/internal/upload/manager.test.ts +2 -2
- package/src/internal/upload/manager.ts +2 -4
- package/src/protonDriveClient.ts +64 -27
- package/src/protonDrivePublicLinkClient.ts +121 -0
|
@@ -10,12 +10,14 @@ import {
|
|
|
10
10
|
resultOk,
|
|
11
11
|
} from '../../interface';
|
|
12
12
|
import { SharingAPIService } from './apiService';
|
|
13
|
+
import { SharingCache } from './cache';
|
|
13
14
|
import { SharingCryptoService } from './cryptoService';
|
|
14
15
|
import { SharesService, NodesService } from './interface';
|
|
15
16
|
import { SharingManagement } from './sharingManagement';
|
|
16
17
|
|
|
17
18
|
describe('SharingManagement', () => {
|
|
18
19
|
let apiService: SharingAPIService;
|
|
20
|
+
let cache: SharingCache;
|
|
19
21
|
let cryptoService: SharingCryptoService;
|
|
20
22
|
let accountService: ProtonDriveAccount;
|
|
21
23
|
let sharesService: SharesService;
|
|
@@ -57,12 +59,16 @@ describe('SharingManagement', () => {
|
|
|
57
59
|
updatePublicLink: jest.fn(),
|
|
58
60
|
};
|
|
59
61
|
// @ts-expect-error No need to implement all methods for mocking
|
|
62
|
+
cache = {
|
|
63
|
+
hasSharedByMeNodeUidsLoaded: jest.fn().mockResolvedValue(true),
|
|
64
|
+
addSharedByMeNodeUid: jest.fn(),
|
|
65
|
+
removeSharedByMeNodeUid: jest.fn(),
|
|
66
|
+
};
|
|
67
|
+
// @ts-expect-error No need to implement all methods for mocking
|
|
60
68
|
cryptoService = {
|
|
61
|
-
generateShareKeys: jest
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
shareKey: { encrypted: 'encrypted-key', decrypted: { passphraseSessionKey: 'pass-session-key' } },
|
|
65
|
-
}),
|
|
69
|
+
generateShareKeys: jest.fn().mockResolvedValue({
|
|
70
|
+
shareKey: { encrypted: 'encrypted-key', decrypted: { passphraseSessionKey: 'pass-session-key' } },
|
|
71
|
+
}),
|
|
66
72
|
decryptShare: jest.fn().mockImplementation((share) => share),
|
|
67
73
|
decryptInvitation: jest.fn().mockImplementation((invitation) => invitation),
|
|
68
74
|
decryptExternalInvitation: jest.fn().mockImplementation((invitation) => invitation),
|
|
@@ -70,7 +76,7 @@ describe('SharingManagement', () => {
|
|
|
70
76
|
encryptInvitation: jest.fn().mockImplementation(() => {}),
|
|
71
77
|
encryptExternalInvitation: jest.fn().mockImplementation((invitation) => ({
|
|
72
78
|
...invitation,
|
|
73
|
-
base64ExternalInvitationSignature: '
|
|
79
|
+
base64ExternalInvitationSignature: 'external-signature',
|
|
74
80
|
})),
|
|
75
81
|
decryptPublicLink: jest.fn().mockImplementation((publicLink) => publicLink),
|
|
76
82
|
generatePublicLinkPassword: jest.fn().mockResolvedValue('generatedPassword'),
|
|
@@ -85,17 +91,12 @@ describe('SharingManagement', () => {
|
|
|
85
91
|
};
|
|
86
92
|
// @ts-expect-error No need to implement all methods for mocking
|
|
87
93
|
sharesService = {
|
|
88
|
-
loadEncryptedShare: jest
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
passphraseSessionKey: 'sharePassphraseSessionKey',
|
|
95
|
-
}),
|
|
96
|
-
getContextShareMemberEmailKey: jest
|
|
97
|
-
.fn()
|
|
98
|
-
.mockResolvedValue({ email: 'volume-email', addressId: 'addressId', addressKey: 'volume-key' }),
|
|
94
|
+
loadEncryptedShare: jest.fn().mockResolvedValue({
|
|
95
|
+
id: 'shareId',
|
|
96
|
+
addressId: 'addressId',
|
|
97
|
+
creatorEmail: 'address@example.com',
|
|
98
|
+
passphraseSessionKey: 'sharePassphraseSessionKey',
|
|
99
|
+
}),
|
|
99
100
|
};
|
|
100
101
|
// @ts-expect-error No need to implement all methods for mocking
|
|
101
102
|
nodesService = {
|
|
@@ -111,6 +112,7 @@ describe('SharingManagement', () => {
|
|
|
111
112
|
sharingManagement = new SharingManagement(
|
|
112
113
|
getMockLogger(),
|
|
113
114
|
apiService,
|
|
115
|
+
cache,
|
|
114
116
|
cryptoService,
|
|
115
117
|
accountService,
|
|
116
118
|
sharesService,
|
|
@@ -196,13 +198,11 @@ describe('SharingManagement', () => {
|
|
|
196
198
|
const nodeUid = 'volumeId~nodeUid';
|
|
197
199
|
|
|
198
200
|
it('should create share if no exists', async () => {
|
|
199
|
-
nodesService.getNode = jest
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
name: { ok: true, value: 'name' },
|
|
205
|
-
}));
|
|
201
|
+
nodesService.getNode = jest.fn().mockImplementation((nodeUid) => ({
|
|
202
|
+
nodeUid,
|
|
203
|
+
parentUid: 'parentUid',
|
|
204
|
+
name: { ok: true, value: 'name' },
|
|
205
|
+
}));
|
|
206
206
|
nodesService.notifyNodeChanged = jest.fn();
|
|
207
207
|
|
|
208
208
|
const sharingInfo = await sharingManagement.shareNode(nodeUid, { users: ['email'] });
|
|
@@ -223,6 +223,7 @@ describe('SharingManagement', () => {
|
|
|
223
223
|
expect(apiService.updateInvitation).not.toHaveBeenCalled();
|
|
224
224
|
expect(apiService.inviteProtonUser).toHaveBeenCalled();
|
|
225
225
|
expect(nodesService.notifyNodeChanged).toHaveBeenCalledWith(nodeUid);
|
|
226
|
+
expect(cache.addSharedByMeNodeUid).toHaveBeenCalledWith(nodeUid);
|
|
226
227
|
});
|
|
227
228
|
});
|
|
228
229
|
|
|
@@ -288,6 +289,7 @@ describe('SharingManagement', () => {
|
|
|
288
289
|
});
|
|
289
290
|
expect(apiService.updateInvitation).not.toHaveBeenCalled();
|
|
290
291
|
expect(apiService.inviteProtonUser).toHaveBeenCalled();
|
|
292
|
+
expect(cache.addSharedByMeNodeUid).not.toHaveBeenCalled();
|
|
291
293
|
});
|
|
292
294
|
|
|
293
295
|
it('should share node with proton email with specific role', async () => {
|
|
@@ -311,6 +313,7 @@ describe('SharingManagement', () => {
|
|
|
311
313
|
});
|
|
312
314
|
expect(apiService.updateInvitation).not.toHaveBeenCalled();
|
|
313
315
|
expect(apiService.inviteProtonUser).toHaveBeenCalled();
|
|
316
|
+
expect(cache.addSharedByMeNodeUid).not.toHaveBeenCalled();
|
|
314
317
|
});
|
|
315
318
|
|
|
316
319
|
it('should update existing role', async () => {
|
|
@@ -331,6 +334,7 @@ describe('SharingManagement', () => {
|
|
|
331
334
|
});
|
|
332
335
|
expect(apiService.updateInvitation).toHaveBeenCalled();
|
|
333
336
|
expect(apiService.inviteProtonUser).not.toHaveBeenCalled();
|
|
337
|
+
expect(cache.addSharedByMeNodeUid).not.toHaveBeenCalled();
|
|
334
338
|
});
|
|
335
339
|
|
|
336
340
|
it('should be no-op if no change', async () => {
|
|
@@ -346,6 +350,25 @@ describe('SharingManagement', () => {
|
|
|
346
350
|
});
|
|
347
351
|
expect(apiService.updateInvitation).not.toHaveBeenCalled();
|
|
348
352
|
expect(apiService.inviteProtonUser).not.toHaveBeenCalled();
|
|
353
|
+
expect(cache.addSharedByMeNodeUid).not.toHaveBeenCalled();
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it('should use address from the root node context share', async () => {
|
|
357
|
+
nodesService.getRootNodeEmailKey = jest
|
|
358
|
+
.fn()
|
|
359
|
+
.mockResolvedValue({ email: 'my-volume-email', addressKey: 'my-volume-key' });
|
|
360
|
+
|
|
361
|
+
await sharingManagement.shareNode(nodeUid, { users: ['email'] });
|
|
362
|
+
|
|
363
|
+
expect(apiService.inviteProtonUser).toHaveBeenCalledWith(
|
|
364
|
+
'shareId',
|
|
365
|
+
{
|
|
366
|
+
addedByEmail: 'my-volume-email',
|
|
367
|
+
inviteeEmail: 'email',
|
|
368
|
+
role: 'viewer',
|
|
369
|
+
},
|
|
370
|
+
expect.anything(),
|
|
371
|
+
);
|
|
349
372
|
});
|
|
350
373
|
});
|
|
351
374
|
|
|
@@ -374,6 +397,7 @@ describe('SharingManagement', () => {
|
|
|
374
397
|
});
|
|
375
398
|
expect(apiService.updateExternalInvitation).not.toHaveBeenCalled();
|
|
376
399
|
expect(apiService.inviteExternalUser).toHaveBeenCalled();
|
|
400
|
+
expect(cache.addSharedByMeNodeUid).not.toHaveBeenCalled();
|
|
377
401
|
});
|
|
378
402
|
|
|
379
403
|
it('should share node with external email with specific role', async () => {
|
|
@@ -398,6 +422,7 @@ describe('SharingManagement', () => {
|
|
|
398
422
|
});
|
|
399
423
|
expect(apiService.updateExternalInvitation).not.toHaveBeenCalled();
|
|
400
424
|
expect(apiService.inviteExternalUser).toHaveBeenCalled();
|
|
425
|
+
expect(cache.addSharedByMeNodeUid).not.toHaveBeenCalled();
|
|
401
426
|
});
|
|
402
427
|
|
|
403
428
|
it('should update existing role', async () => {
|
|
@@ -418,6 +443,7 @@ describe('SharingManagement', () => {
|
|
|
418
443
|
});
|
|
419
444
|
expect(apiService.updateExternalInvitation).toHaveBeenCalled();
|
|
420
445
|
expect(apiService.inviteExternalUser).not.toHaveBeenCalled();
|
|
446
|
+
expect(cache.addSharedByMeNodeUid).not.toHaveBeenCalled();
|
|
421
447
|
});
|
|
422
448
|
|
|
423
449
|
it('should be no-op if no change', async () => {
|
|
@@ -433,6 +459,28 @@ describe('SharingManagement', () => {
|
|
|
433
459
|
});
|
|
434
460
|
expect(apiService.updateExternalInvitation).not.toHaveBeenCalled();
|
|
435
461
|
expect(apiService.inviteExternalUser).not.toHaveBeenCalled();
|
|
462
|
+
expect(cache.addSharedByMeNodeUid).not.toHaveBeenCalled();
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it('should use address from the root node context share', async () => {
|
|
466
|
+
nodesService.getRootNodeEmailKey = jest.fn().mockResolvedValue({
|
|
467
|
+
email: 'my-volume-email',
|
|
468
|
+
addressId: 'my-volume-addressId',
|
|
469
|
+
addressKey: 'my-volume-key',
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
await sharingManagement.shareNode(nodeUid, { users: ['email'] });
|
|
473
|
+
|
|
474
|
+
expect(apiService.inviteExternalUser).toHaveBeenCalledWith(
|
|
475
|
+
'shareId',
|
|
476
|
+
{
|
|
477
|
+
inviterAddressId: 'my-volume-addressId',
|
|
478
|
+
inviteeEmail: 'email',
|
|
479
|
+
role: 'viewer',
|
|
480
|
+
base64Signature: 'external-signature',
|
|
481
|
+
},
|
|
482
|
+
expect.anything(),
|
|
483
|
+
);
|
|
436
484
|
});
|
|
437
485
|
});
|
|
438
486
|
|
|
@@ -482,6 +530,7 @@ describe('SharingManagement', () => {
|
|
|
482
530
|
}),
|
|
483
531
|
expect.anything(),
|
|
484
532
|
);
|
|
533
|
+
expect(cache.addSharedByMeNodeUid).not.toHaveBeenCalled();
|
|
485
534
|
});
|
|
486
535
|
});
|
|
487
536
|
|
|
@@ -505,6 +554,7 @@ describe('SharingManagement', () => {
|
|
|
505
554
|
expect(apiService.updateMember).toHaveBeenCalled();
|
|
506
555
|
expect(apiService.updateInvitation).not.toHaveBeenCalled();
|
|
507
556
|
expect(apiService.inviteProtonUser).not.toHaveBeenCalled();
|
|
557
|
+
expect(cache.addSharedByMeNodeUid).not.toHaveBeenCalled();
|
|
508
558
|
});
|
|
509
559
|
|
|
510
560
|
it('should be no-op if no change via proton user', async () => {
|
|
@@ -521,6 +571,7 @@ describe('SharingManagement', () => {
|
|
|
521
571
|
expect(apiService.updateMember).not.toHaveBeenCalled();
|
|
522
572
|
expect(apiService.updateInvitation).not.toHaveBeenCalled();
|
|
523
573
|
expect(apiService.inviteProtonUser).not.toHaveBeenCalled();
|
|
574
|
+
expect(cache.addSharedByMeNodeUid).not.toHaveBeenCalled();
|
|
524
575
|
});
|
|
525
576
|
|
|
526
577
|
it('should update member via non-proton user', async () => {
|
|
@@ -542,6 +593,7 @@ describe('SharingManagement', () => {
|
|
|
542
593
|
expect(apiService.updateMember).toHaveBeenCalled();
|
|
543
594
|
expect(apiService.updateInvitation).not.toHaveBeenCalled();
|
|
544
595
|
expect(apiService.inviteProtonUser).not.toHaveBeenCalled();
|
|
596
|
+
expect(cache.addSharedByMeNodeUid).not.toHaveBeenCalled();
|
|
545
597
|
});
|
|
546
598
|
|
|
547
599
|
it('should be no-op if no change via non-proton user', async () => {
|
|
@@ -558,6 +610,7 @@ describe('SharingManagement', () => {
|
|
|
558
610
|
expect(apiService.updateMember).not.toHaveBeenCalled();
|
|
559
611
|
expect(apiService.updateInvitation).not.toHaveBeenCalled();
|
|
560
612
|
expect(apiService.inviteProtonUser).not.toHaveBeenCalled();
|
|
613
|
+
expect(cache.addSharedByMeNodeUid).not.toHaveBeenCalled();
|
|
561
614
|
});
|
|
562
615
|
});
|
|
563
616
|
|
|
@@ -605,6 +658,7 @@ describe('SharingManagement', () => {
|
|
|
605
658
|
srp: 'publicLinkSrp',
|
|
606
659
|
}),
|
|
607
660
|
);
|
|
661
|
+
expect(cache.addSharedByMeNodeUid).not.toHaveBeenCalled();
|
|
608
662
|
});
|
|
609
663
|
|
|
610
664
|
it('should share node with custom password and expiration', async () => {
|
|
@@ -650,6 +704,7 @@ describe('SharingManagement', () => {
|
|
|
650
704
|
srp: 'publicLinkSrp',
|
|
651
705
|
}),
|
|
652
706
|
);
|
|
707
|
+
expect(cache.addSharedByMeNodeUid).not.toHaveBeenCalled();
|
|
653
708
|
});
|
|
654
709
|
|
|
655
710
|
it('should update public link with custom password and expiration', async () => {
|
|
@@ -704,6 +759,7 @@ describe('SharingManagement', () => {
|
|
|
704
759
|
srp: 'publicLinkSrp',
|
|
705
760
|
}),
|
|
706
761
|
);
|
|
762
|
+
expect(cache.addSharedByMeNodeUid).not.toHaveBeenCalled();
|
|
707
763
|
});
|
|
708
764
|
|
|
709
765
|
it('should not allow updating legacy public link', async () => {
|
|
@@ -809,6 +865,7 @@ describe('SharingManagement', () => {
|
|
|
809
865
|
expect(apiService.deleteExternalInvitation).not.toHaveBeenCalled();
|
|
810
866
|
expect(apiService.removeMember).not.toHaveBeenCalled();
|
|
811
867
|
expect(apiService.removePublicLink).not.toHaveBeenCalled();
|
|
868
|
+
expect(cache.removeSharedByMeNodeUid).not.toHaveBeenCalled();
|
|
812
869
|
});
|
|
813
870
|
|
|
814
871
|
it('should delete external invitation', async () => {
|
|
@@ -825,6 +882,7 @@ describe('SharingManagement', () => {
|
|
|
825
882
|
expect(apiService.deleteExternalInvitation).toHaveBeenCalled();
|
|
826
883
|
expect(apiService.removeMember).not.toHaveBeenCalled();
|
|
827
884
|
expect(apiService.removePublicLink).not.toHaveBeenCalled();
|
|
885
|
+
expect(cache.removeSharedByMeNodeUid).not.toHaveBeenCalled();
|
|
828
886
|
});
|
|
829
887
|
|
|
830
888
|
it('should remove member', async () => {
|
|
@@ -841,6 +899,7 @@ describe('SharingManagement', () => {
|
|
|
841
899
|
expect(apiService.deleteExternalInvitation).not.toHaveBeenCalled();
|
|
842
900
|
expect(apiService.removeMember).toHaveBeenCalled();
|
|
843
901
|
expect(apiService.removePublicLink).not.toHaveBeenCalled();
|
|
902
|
+
expect(cache.removeSharedByMeNodeUid).not.toHaveBeenCalled();
|
|
844
903
|
});
|
|
845
904
|
|
|
846
905
|
it('should be no-op if not shared with email', async () => {
|
|
@@ -857,6 +916,7 @@ describe('SharingManagement', () => {
|
|
|
857
916
|
expect(apiService.deleteExternalInvitation).not.toHaveBeenCalled();
|
|
858
917
|
expect(apiService.removeMember).not.toHaveBeenCalled();
|
|
859
918
|
expect(apiService.removePublicLink).not.toHaveBeenCalled();
|
|
919
|
+
expect(cache.removeSharedByMeNodeUid).not.toHaveBeenCalled();
|
|
860
920
|
});
|
|
861
921
|
|
|
862
922
|
it('should remove public link', async () => {
|
|
@@ -873,6 +933,7 @@ describe('SharingManagement', () => {
|
|
|
873
933
|
expect(apiService.deleteExternalInvitation).not.toHaveBeenCalled();
|
|
874
934
|
expect(apiService.removeMember).not.toHaveBeenCalled();
|
|
875
935
|
expect(apiService.removePublicLink).toHaveBeenCalled();
|
|
936
|
+
expect(cache.removeSharedByMeNodeUid).not.toHaveBeenCalled();
|
|
876
937
|
});
|
|
877
938
|
|
|
878
939
|
it('should remove share if all is removed', async () => {
|
|
@@ -885,6 +946,7 @@ describe('SharingManagement', () => {
|
|
|
885
946
|
expect(apiService.removeMember).not.toHaveBeenCalled();
|
|
886
947
|
expect(apiService.removePublicLink).not.toHaveBeenCalled();
|
|
887
948
|
expect(nodesService.notifyNodeChanged).toHaveBeenCalled();
|
|
949
|
+
expect(cache.removeSharedByMeNodeUid).toHaveBeenCalledWith(nodeUid);
|
|
888
950
|
});
|
|
889
951
|
|
|
890
952
|
it('should remove share if everything is manually removed', async () => {
|
|
@@ -899,6 +961,7 @@ describe('SharingManagement', () => {
|
|
|
899
961
|
expect(apiService.deleteExternalInvitation).toHaveBeenCalled();
|
|
900
962
|
expect(apiService.removeMember).toHaveBeenCalled();
|
|
901
963
|
expect(apiService.removePublicLink).toHaveBeenCalled();
|
|
964
|
+
expect(cache.removeSharedByMeNodeUid).toHaveBeenCalledWith(nodeUid);
|
|
902
965
|
});
|
|
903
966
|
});
|
|
904
967
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { c } from 'ttag';
|
|
2
2
|
|
|
3
|
-
import { SessionKey } from '../../crypto';
|
|
3
|
+
import { PrivateKey, SessionKey } from '../../crypto';
|
|
4
4
|
import { ValidationError } from '../../errors';
|
|
5
5
|
import {
|
|
6
6
|
Logger,
|
|
@@ -20,6 +20,7 @@ import { getErrorMessage } from '../errors';
|
|
|
20
20
|
import { SharingAPIService } from './apiService';
|
|
21
21
|
import { PUBLIC_LINK_GENERATED_PASSWORD_LENGTH, SharingCryptoService } from './cryptoService';
|
|
22
22
|
import { SharesService, NodesService, ShareResultWithCreatorEmail, PublicLinkWithCreatorEmail } from './interface';
|
|
23
|
+
import { SharingCache } from './cache';
|
|
23
24
|
|
|
24
25
|
interface InternalShareResult extends ShareResultWithCreatorEmail {
|
|
25
26
|
share: Share;
|
|
@@ -33,6 +34,12 @@ interface Share {
|
|
|
33
34
|
passphraseSessionKey: SessionKey;
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
interface ContextShareAddress {
|
|
38
|
+
addressId: string;
|
|
39
|
+
addressKey: PrivateKey;
|
|
40
|
+
email: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
36
43
|
interface EmailOptions {
|
|
37
44
|
message?: string;
|
|
38
45
|
nodeName?: string;
|
|
@@ -48,6 +55,7 @@ export class SharingManagement {
|
|
|
48
55
|
constructor(
|
|
49
56
|
private logger: Logger,
|
|
50
57
|
private apiService: SharingAPIService,
|
|
58
|
+
private cache: SharingCache,
|
|
51
59
|
private cryptoService: SharingCryptoService,
|
|
52
60
|
private account: ProtonDriveAccount,
|
|
53
61
|
private sharesService: SharesService,
|
|
@@ -55,6 +63,7 @@ export class SharingManagement {
|
|
|
55
63
|
) {
|
|
56
64
|
this.logger = logger;
|
|
57
65
|
this.apiService = apiService;
|
|
66
|
+
this.cache = cache;
|
|
58
67
|
this.cryptoService = cryptoService;
|
|
59
68
|
this.account = account;
|
|
60
69
|
this.sharesService = sharesService;
|
|
@@ -138,18 +147,22 @@ export class SharingManagement {
|
|
|
138
147
|
throw new ValidationError(c('Error').t`Expiration date cannot be in the past`);
|
|
139
148
|
}
|
|
140
149
|
|
|
150
|
+
let contextShareAddress: ContextShareAddress;
|
|
141
151
|
let currentSharing = await this.getInternalSharingInfo(nodeUid);
|
|
142
|
-
if (
|
|
152
|
+
if (currentSharing) {
|
|
153
|
+
contextShareAddress = await this.nodesService.getRootNodeEmailKey(nodeUid);
|
|
154
|
+
} else {
|
|
143
155
|
const node = await this.nodesService.getNode(nodeUid);
|
|
144
|
-
const
|
|
156
|
+
const result = await this.createShare(nodeUid);
|
|
145
157
|
currentSharing = {
|
|
146
|
-
share,
|
|
158
|
+
share: result.share,
|
|
147
159
|
nodeName: node.name.ok ? node.name.value : node.name.error.name,
|
|
148
160
|
protonInvitations: [],
|
|
149
161
|
nonProtonInvitations: [],
|
|
150
162
|
members: [],
|
|
151
163
|
publicLink: undefined,
|
|
152
164
|
};
|
|
165
|
+
contextShareAddress = result.contextShareAddress;
|
|
153
166
|
}
|
|
154
167
|
|
|
155
168
|
const emailOptions: EmailOptions = {
|
|
@@ -187,7 +200,13 @@ export class SharingManagement {
|
|
|
187
200
|
}
|
|
188
201
|
|
|
189
202
|
this.logger.info(`Inviting user ${email} with role ${role} to node ${nodeUid}`);
|
|
190
|
-
const invitation = await this.inviteProtonUser(
|
|
203
|
+
const invitation = await this.inviteProtonUser(
|
|
204
|
+
contextShareAddress,
|
|
205
|
+
currentSharing.share,
|
|
206
|
+
email,
|
|
207
|
+
role,
|
|
208
|
+
emailOptions,
|
|
209
|
+
);
|
|
191
210
|
currentSharing.protonInvitations.push(invitation);
|
|
192
211
|
}
|
|
193
212
|
|
|
@@ -225,7 +244,13 @@ export class SharingManagement {
|
|
|
225
244
|
}
|
|
226
245
|
|
|
227
246
|
this.logger.info(`Inviting external user ${email} with role ${role} to node ${nodeUid}`);
|
|
228
|
-
const invitation = await this.inviteExternalUser(
|
|
247
|
+
const invitation = await this.inviteExternalUser(
|
|
248
|
+
contextShareAddress,
|
|
249
|
+
currentSharing.share,
|
|
250
|
+
email,
|
|
251
|
+
role,
|
|
252
|
+
emailOptions,
|
|
253
|
+
);
|
|
229
254
|
currentSharing.nonProtonInvitations.push(invitation);
|
|
230
255
|
}
|
|
231
256
|
|
|
@@ -241,7 +266,7 @@ export class SharingManagement {
|
|
|
241
266
|
);
|
|
242
267
|
} else {
|
|
243
268
|
this.logger.info(`Sharing via public link with role ${options.role} to node ${nodeUid}`);
|
|
244
|
-
currentSharing.publicLink = await this.shareViaLink(currentSharing.share, options);
|
|
269
|
+
currentSharing.publicLink = await this.shareViaLink(contextShareAddress, currentSharing.share, options);
|
|
245
270
|
}
|
|
246
271
|
}
|
|
247
272
|
|
|
@@ -371,7 +396,7 @@ export class SharingManagement {
|
|
|
371
396
|
};
|
|
372
397
|
}
|
|
373
398
|
|
|
374
|
-
private async createShare(nodeUid: string): Promise<Share> {
|
|
399
|
+
private async createShare(nodeUid: string): Promise<{ share: Share; contextShareAddress: ContextShareAddress }> {
|
|
375
400
|
const node = await this.nodesService.getNode(nodeUid);
|
|
376
401
|
if (!node.parentUid) {
|
|
377
402
|
throw new ValidationError(c('Error').t`Cannot share root folder`);
|
|
@@ -387,26 +412,42 @@ export class SharingManagement {
|
|
|
387
412
|
base64NameKeyPacket: keys.base64NameKeyPacket,
|
|
388
413
|
});
|
|
389
414
|
await this.nodesService.notifyNodeChanged(nodeUid);
|
|
390
|
-
|
|
415
|
+
if (await this.cache.hasSharedByMeNodeUidsLoaded()) {
|
|
416
|
+
await this.cache.addSharedByMeNodeUid(nodeUid);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const share = {
|
|
391
420
|
volumeId,
|
|
392
421
|
shareId,
|
|
393
422
|
creatorEmail: email,
|
|
394
423
|
passphraseSessionKey: keys.shareKey.decrypted.passphraseSessionKey,
|
|
395
424
|
};
|
|
425
|
+
const contextShareAddress = {
|
|
426
|
+
email,
|
|
427
|
+
addressId,
|
|
428
|
+
addressKey,
|
|
429
|
+
};
|
|
430
|
+
return {
|
|
431
|
+
share,
|
|
432
|
+
contextShareAddress,
|
|
433
|
+
};
|
|
396
434
|
}
|
|
397
435
|
|
|
398
436
|
private async deleteShare(shareId: string, nodeUid: string): Promise<void> {
|
|
399
437
|
await this.apiService.deleteShare(shareId);
|
|
400
438
|
await this.nodesService.notifyNodeChanged(nodeUid);
|
|
439
|
+
if (await this.cache.hasSharedByMeNodeUidsLoaded()) {
|
|
440
|
+
await this.cache.removeSharedByMeNodeUid(nodeUid);
|
|
441
|
+
}
|
|
401
442
|
}
|
|
402
443
|
|
|
403
444
|
private async inviteProtonUser(
|
|
445
|
+
inviter: ContextShareAddress,
|
|
404
446
|
share: Share,
|
|
405
447
|
inviteeEmail: string,
|
|
406
448
|
role: MemberRole,
|
|
407
449
|
emailOptions: EmailOptions,
|
|
408
450
|
): Promise<ProtonInvitation> {
|
|
409
|
-
const inviter = await this.sharesService.getContextShareMemberEmailKey(share.shareId);
|
|
410
451
|
const invitationCrypto = await this.cryptoService.encryptInvitation(
|
|
411
452
|
share.passphraseSessionKey,
|
|
412
453
|
inviter.addressKey,
|
|
@@ -461,12 +502,12 @@ export class SharingManagement {
|
|
|
461
502
|
}
|
|
462
503
|
|
|
463
504
|
private async inviteExternalUser(
|
|
505
|
+
inviter: ContextShareAddress,
|
|
464
506
|
share: Share,
|
|
465
507
|
inviteeEmail: string,
|
|
466
508
|
role: MemberRole,
|
|
467
509
|
emailOptions: EmailOptions,
|
|
468
510
|
): Promise<NonProtonInvitation> {
|
|
469
|
-
const inviter = await this.sharesService.getContextShareMemberEmailKey(share.shareId);
|
|
470
511
|
const invitationCrypto = await this.cryptoService.encryptExternalInvitation(
|
|
471
512
|
share.passphraseSessionKey,
|
|
472
513
|
inviter.addressKey,
|
|
@@ -515,21 +556,20 @@ export class SharingManagement {
|
|
|
515
556
|
}
|
|
516
557
|
|
|
517
558
|
private async shareViaLink(
|
|
559
|
+
inviter: ContextShareAddress,
|
|
518
560
|
share: Share,
|
|
519
561
|
options: SharePublicLinkSettingsObject,
|
|
520
562
|
): Promise<PublicLinkWithCreatorEmail> {
|
|
521
|
-
const { email: creatorEmail } = await this.sharesService.getContextShareMemberEmailKey(share.shareId);
|
|
522
|
-
|
|
523
563
|
const generatedPassword = await this.cryptoService.generatePublicLinkPassword();
|
|
524
564
|
const password = options.customPassword ? `${generatedPassword}${options.customPassword}` : generatedPassword;
|
|
525
565
|
|
|
526
566
|
const { crypto, srp } = await this.cryptoService.encryptPublicLink(
|
|
527
|
-
|
|
567
|
+
inviter.email,
|
|
528
568
|
share.passphraseSessionKey,
|
|
529
569
|
password,
|
|
530
570
|
);
|
|
531
571
|
const publicLink = await this.apiService.createPublicLink(share.shareId, {
|
|
532
|
-
creatorEmail,
|
|
572
|
+
creatorEmail: inviter.email,
|
|
533
573
|
role: options.role,
|
|
534
574
|
includesCustomPassword: !!options.customPassword,
|
|
535
575
|
expirationTime: options.expiration ? Math.floor(options.expiration.getTime() / 1000) : undefined,
|
|
@@ -545,7 +585,7 @@ export class SharingManagement {
|
|
|
545
585
|
customPassword: options.customPassword,
|
|
546
586
|
expirationTime: options.expiration,
|
|
547
587
|
numberOfInitializedDownloads: 0,
|
|
548
|
-
creatorEmail,
|
|
588
|
+
creatorEmail: inviter.email,
|
|
549
589
|
};
|
|
550
590
|
}
|
|
551
591
|
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { DriveAPIService, drivePaths, nodeTypeNumberToNodeType } from '../apiService';
|
|
2
|
+
import { Logger } from '../../interface';
|
|
3
|
+
import { makeNodeUid, splitNodeUid } from '../uids';
|
|
4
|
+
import { EncryptedShareCrypto, EncryptedNode } from './interface';
|
|
5
|
+
|
|
6
|
+
const PAGE_SIZE = 50;
|
|
7
|
+
|
|
8
|
+
type GetTokenInfoResponse = drivePaths['/drive/urls/{token}']['get']['responses']['200']['content']['application/json'];
|
|
9
|
+
|
|
10
|
+
type GetTokenFolderChildrenResponse =
|
|
11
|
+
drivePaths['/drive/urls/{token}/folders/{linkID}/children']['get']['responses']['200']['content']['application/json'];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Provides API communication for accessing public link data.
|
|
15
|
+
*
|
|
16
|
+
* The service is responsible for transforming local objects to API payloads
|
|
17
|
+
* and vice versa. It should not contain any business logic.
|
|
18
|
+
*/
|
|
19
|
+
export class SharingPublicAPIService {
|
|
20
|
+
constructor(
|
|
21
|
+
private logger: Logger,
|
|
22
|
+
private apiService: DriveAPIService,
|
|
23
|
+
) {
|
|
24
|
+
this.logger = logger;
|
|
25
|
+
this.apiService = apiService;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async getPublicLinkRoot(token: string): Promise<{
|
|
29
|
+
encryptedNode: EncryptedNode;
|
|
30
|
+
encryptedShare: EncryptedShareCrypto;
|
|
31
|
+
}> {
|
|
32
|
+
const response = await this.apiService.get<GetTokenInfoResponse>(`drive/urls/${token}`);
|
|
33
|
+
const encryptedNode = tokenToEncryptedNode(this.logger, response.Token);
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
encryptedNode: encryptedNode,
|
|
37
|
+
encryptedShare: {
|
|
38
|
+
base64UrlPasswordSalt: response.Token.SharePasswordSalt,
|
|
39
|
+
armoredKey: response.Token.ShareKey,
|
|
40
|
+
armoredPassphrase: response.Token.SharePassphrase,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async *iterateChildren(parentUid: string): AsyncGenerator<EncryptedNode> {
|
|
46
|
+
const { volumeId: token, nodeId } = splitNodeUid(parentUid);
|
|
47
|
+
|
|
48
|
+
let page = 0;
|
|
49
|
+
while (true) {
|
|
50
|
+
const response = await this.apiService.get<GetTokenFolderChildrenResponse>(
|
|
51
|
+
`drive/urls/${token}/folders/${nodeId}/children?Page=${page}&PageSize=${PAGE_SIZE}`,
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
for (const link of response.Links) {
|
|
55
|
+
yield linkToEncryptedNode(this.logger, token, link);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (response.Links.length < PAGE_SIZE) {
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
page++;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function tokenToEncryptedNode(logger: Logger, token: GetTokenInfoResponse['Token']): EncryptedNode {
|
|
67
|
+
const baseNodeMetadata = {
|
|
68
|
+
// Internal metadata
|
|
69
|
+
encryptedName: token.Name,
|
|
70
|
+
|
|
71
|
+
// Basic node metadata
|
|
72
|
+
uid: makeNodeUid(token.Token, token.LinkID),
|
|
73
|
+
parentUid: undefined,
|
|
74
|
+
type: nodeTypeNumberToNodeType(logger, token.LinkType),
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const baseCryptoNodeMetadata = {
|
|
78
|
+
signatureEmail: token.SignatureEmail || undefined,
|
|
79
|
+
armoredKey: token.NodeKey,
|
|
80
|
+
armoredNodePassphrase: token.NodePassphrase,
|
|
81
|
+
armoredNodePassphraseSignature: token.NodePassphraseSignature || undefined,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
if (token.LinkType === 1 && token.NodeHashKey) {
|
|
85
|
+
return {
|
|
86
|
+
...baseNodeMetadata,
|
|
87
|
+
encryptedCrypto: {
|
|
88
|
+
...baseCryptoNodeMetadata,
|
|
89
|
+
folder: {
|
|
90
|
+
armoredHashKey: token.NodeHashKey as string,
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (token.LinkType === 2 && token.ContentKeyPacket) {
|
|
97
|
+
return {
|
|
98
|
+
...baseNodeMetadata,
|
|
99
|
+
totalStorageSize: token.Size || undefined,
|
|
100
|
+
mediaType: token.MIMEType || undefined,
|
|
101
|
+
encryptedCrypto: {
|
|
102
|
+
...baseCryptoNodeMetadata,
|
|
103
|
+
file: {
|
|
104
|
+
base64ContentKeyPacket: token.ContentKeyPacket,
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
throw new Error(`Unknown node type: ${token.LinkType}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function linkToEncryptedNode(
|
|
114
|
+
logger: Logger,
|
|
115
|
+
token: string,
|
|
116
|
+
link: GetTokenFolderChildrenResponse['Links'][0],
|
|
117
|
+
): EncryptedNode {
|
|
118
|
+
const baseNodeMetadata = {
|
|
119
|
+
// Internal metadata
|
|
120
|
+
hash: link.Hash || undefined,
|
|
121
|
+
encryptedName: link.Name,
|
|
122
|
+
|
|
123
|
+
// Basic node metadata
|
|
124
|
+
uid: makeNodeUid(token, link.LinkID),
|
|
125
|
+
parentUid: link.ParentLinkID ? makeNodeUid(token, link.ParentLinkID) : undefined,
|
|
126
|
+
type: nodeTypeNumberToNodeType(logger, link.Type),
|
|
127
|
+
totalStorageSize: link.TotalSize,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const baseCryptoNodeMetadata = {
|
|
131
|
+
signatureEmail: link.SignatureEmail || undefined,
|
|
132
|
+
armoredKey: link.NodeKey,
|
|
133
|
+
armoredNodePassphrase: link.NodePassphrase,
|
|
134
|
+
armoredNodePassphraseSignature: link.NodePassphraseSignature || undefined,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
if (link.Type === 1 && link.FolderProperties) {
|
|
138
|
+
return {
|
|
139
|
+
...baseNodeMetadata,
|
|
140
|
+
encryptedCrypto: {
|
|
141
|
+
...baseCryptoNodeMetadata,
|
|
142
|
+
folder: {
|
|
143
|
+
armoredHashKey: link.FolderProperties.NodeHashKey as string,
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (link.Type === 2 && link.FileProperties?.ContentKeyPacket) {
|
|
150
|
+
return {
|
|
151
|
+
...baseNodeMetadata,
|
|
152
|
+
totalStorageSize: link.FileProperties.ActiveRevision?.Size || undefined,
|
|
153
|
+
mediaType: link.MIMEType || undefined,
|
|
154
|
+
encryptedCrypto: {
|
|
155
|
+
...baseCryptoNodeMetadata,
|
|
156
|
+
file: {
|
|
157
|
+
base64ContentKeyPacket: link.FileProperties.ContentKeyPacket,
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
throw new Error(`Unknown node type: ${link.Type}`);
|
|
164
|
+
}
|