@protontech/drive-sdk 0.14.4 → 0.14.5
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/account.d.ts +2 -1
- package/dist/internal/nodes/cryptoService.d.ts +3 -2
- package/dist/internal/nodes/cryptoService.js +11 -1
- package/dist/internal/nodes/cryptoService.js.map +1 -1
- package/dist/internal/nodes/cryptoService.test.js +40 -2
- package/dist/internal/nodes/cryptoService.test.js.map +1 -1
- package/dist/internal/nodes/index.js +1 -1
- package/dist/internal/nodes/index.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.d.ts +2 -2
- package/dist/internal/nodes/nodesManagement.js +1 -1
- package/dist/internal/nodes/nodesManagement.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.test.js +30 -0
- package/dist/internal/nodes/nodesManagement.test.js.map +1 -1
- package/dist/internal/photos/index.js +1 -1
- package/dist/internal/photos/index.js.map +1 -1
- package/dist/internal/sharing/apiService.d.ts +1 -1
- package/dist/internal/sharing/apiService.js +2 -2
- package/dist/internal/sharing/apiService.js.map +1 -1
- package/dist/internal/sharing/cryptoService.d.ts +1 -1
- package/dist/internal/sharing/cryptoService.js +2 -2
- package/dist/internal/sharing/cryptoService.js.map +1 -1
- package/dist/internal/sharing/sharingManagement.d.ts +2 -2
- package/dist/internal/sharing/sharingManagement.js +29 -2
- package/dist/internal/sharing/sharingManagement.js.map +1 -1
- package/dist/internal/sharing/sharingManagement.test.js +48 -0
- package/dist/internal/sharing/sharingManagement.test.js.map +1 -1
- package/dist/internal/sharingPublic/index.js +1 -1
- package/dist/internal/sharingPublic/index.js.map +1 -1
- package/dist/protonDriveClient.d.ts +11 -2
- package/dist/protonDriveClient.js +12 -0
- package/dist/protonDriveClient.js.map +1 -1
- package/dist/protonDrivePhotosClient.d.ts +10 -1
- package/dist/protonDrivePhotosClient.js +12 -0
- package/dist/protonDrivePhotosClient.js.map +1 -1
- package/package.json +1 -1
- package/src/interface/account.ts +2 -1
- package/src/internal/nodes/cryptoService.test.ts +52 -2
- package/src/internal/nodes/cryptoService.ts +16 -1
- package/src/internal/nodes/index.ts +1 -1
- package/src/internal/nodes/nodesManagement.test.ts +38 -2
- package/src/internal/nodes/nodesManagement.ts +15 -3
- package/src/internal/photos/index.ts +1 -1
- package/src/internal/sharing/apiService.ts +2 -1
- package/src/internal/sharing/cryptoService.ts +2 -1
- package/src/internal/sharing/sharingManagement.test.ts +73 -0
- package/src/internal/sharing/sharingManagement.ts +47 -3
- package/src/internal/sharingPublic/index.ts +7 -1
- package/src/protonDriveClient.ts +19 -1
- package/src/protonDrivePhotosClient.ts +17 -0
|
@@ -4,9 +4,9 @@ import { NodesCryptoService } from './cryptoService';
|
|
|
4
4
|
import { NodesAccess } from './nodesAccess';
|
|
5
5
|
import { DecryptedNode } from './interface';
|
|
6
6
|
import { NodesManagement } from './nodesManagement';
|
|
7
|
-
import { NodeResult } from '../../interface';
|
|
7
|
+
import { NodeResult, NodeResultWithError } from '../../interface';
|
|
8
8
|
import { NodeOutOfSyncError } from './errors';
|
|
9
|
-
import { ValidationError } from '../../errors';
|
|
9
|
+
import { NodeWithSameNameExistsValidationError, ValidationError } from '../../errors';
|
|
10
10
|
|
|
11
11
|
describe('NodesManagement', () => {
|
|
12
12
|
let apiService: NodeAPIService;
|
|
@@ -263,6 +263,42 @@ describe('NodesManagement', () => {
|
|
|
263
263
|
);
|
|
264
264
|
});
|
|
265
265
|
|
|
266
|
+
it('moveNodes yields NodeWithSameNameExistsValidationError in case of duplicate node name', async () => {
|
|
267
|
+
const encryptedCrypto = {
|
|
268
|
+
encryptedName: 'movedArmoredNodeName',
|
|
269
|
+
hash: 'movedHash',
|
|
270
|
+
armoredNodePassphrase: 'movedArmoredNodePassphrase',
|
|
271
|
+
armoredNodePassphraseSignature: 'movedArmoredNodePassphraseSignature',
|
|
272
|
+
signatureEmail: 'movedSignatureEmail',
|
|
273
|
+
nameSignatureEmail: 'movedNameSignatureEmail',
|
|
274
|
+
};
|
|
275
|
+
cryptoService.encryptNodeWithNewParent = jest.fn().mockResolvedValue(encryptedCrypto);
|
|
276
|
+
const error = new NodeWithSameNameExistsValidationError('Node with same name exists', 2500, 'existingNodeUid');
|
|
277
|
+
apiService.moveNode = jest.fn().mockRejectedValue(error);
|
|
278
|
+
|
|
279
|
+
const results: NodeResultWithError[] = [];
|
|
280
|
+
for await (const result of management.moveNodes(['nodeUid'], 'newParentNodeUid')) {
|
|
281
|
+
results.push(result);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
expect(results).toHaveLength(1);
|
|
285
|
+
expect(results[0]).toEqual({ uid: 'nodeUid', ok: false, error });
|
|
286
|
+
expect(results[0].ok === false && results[0].error).toBeInstanceOf(NodeWithSameNameExistsValidationError);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('moveNodes yields NodeResultWithError with Error on failure', async () => {
|
|
290
|
+
const error = new Error('move failed');
|
|
291
|
+
cryptoService.encryptNodeWithNewParent = jest.fn().mockRejectedValue(error);
|
|
292
|
+
|
|
293
|
+
const results: NodeResultWithError[] = [];
|
|
294
|
+
for await (const result of management.moveNodes(['nodeUid'], 'newParentNodeUid')) {
|
|
295
|
+
results.push(result);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
expect(results).toHaveLength(1);
|
|
299
|
+
expect(results[0]).toEqual({ uid: 'nodeUid', ok: false, error });
|
|
300
|
+
});
|
|
301
|
+
|
|
266
302
|
it('copyNode manages copy and updates cache', async () => {
|
|
267
303
|
const encryptedCrypto = {
|
|
268
304
|
encryptedName: 'copiedArmoredNodeName',
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { c } from 'ttag';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
MemberRole,
|
|
5
|
+
NodeType,
|
|
6
|
+
NodeResult,
|
|
7
|
+
NodeResultWithNewUid,
|
|
8
|
+
resultOk,
|
|
9
|
+
InvalidNameError,
|
|
10
|
+
NodeResultWithError,
|
|
11
|
+
} from '../../interface';
|
|
4
12
|
import { AbortError, ValidationError } from '../../errors';
|
|
5
13
|
import { createErrorFromUnknown, getErrorMessage } from '../errors';
|
|
6
14
|
import { splitNodeUid } from '../uids';
|
|
@@ -107,7 +115,11 @@ export abstract class NodesManagementBase<
|
|
|
107
115
|
}
|
|
108
116
|
|
|
109
117
|
// Improvement requested: move nodes in parallel
|
|
110
|
-
async *moveNodes(
|
|
118
|
+
async *moveNodes(
|
|
119
|
+
nodeUids: string[],
|
|
120
|
+
newParentNodeUid: string,
|
|
121
|
+
signal?: AbortSignal,
|
|
122
|
+
): AsyncGenerator<NodeResultWithError> {
|
|
111
123
|
for (const nodeUid of nodeUids) {
|
|
112
124
|
if (signal?.aborted) {
|
|
113
125
|
throw new AbortError(c('Error').t`Move operation aborted`);
|
|
@@ -122,7 +134,7 @@ export abstract class NodesManagementBase<
|
|
|
122
134
|
yield {
|
|
123
135
|
uid: nodeUid,
|
|
124
136
|
ok: false,
|
|
125
|
-
error: getErrorMessage(error),
|
|
137
|
+
error: error instanceof Error ? error : new Error(getErrorMessage(error), { cause: error }),
|
|
126
138
|
};
|
|
127
139
|
}
|
|
128
140
|
}
|
|
@@ -123,7 +123,7 @@ export function initPhotosNodesModule(
|
|
|
123
123
|
const cache = new PhotosNodesCache(telemetry.getLogger('nodes-cache'), driveEntitiesCache);
|
|
124
124
|
const cryptoCache = new NodesCryptoCache(telemetry.getLogger('nodes-cache'), driveCryptoCache);
|
|
125
125
|
const cryptoReporter = new NodesCryptoReporter(telemetry, sharesService);
|
|
126
|
-
const cryptoService = new NodesCryptoService(telemetry, driveCrypto, account, cryptoReporter);
|
|
126
|
+
const cryptoService = new NodesCryptoService(telemetry, driveCrypto, account, sharesService, cryptoReporter);
|
|
127
127
|
const nodesAccess = new PhotosNodesAccess(telemetry, api, cache, cryptoCache, cryptoService, sharesService);
|
|
128
128
|
const nodesEventHandler = new NodesEventsHandler(telemetry.getLogger('nodes-events'), cache);
|
|
129
129
|
const nodesManagement = new PhotosNodesManagement(api, cryptoCache, cryptoService, nodesAccess);
|
|
@@ -433,6 +433,7 @@ export class SharingAPIService {
|
|
|
433
433
|
shareId: string,
|
|
434
434
|
invitation: EncryptedInvitationRequest,
|
|
435
435
|
emailDetails: { message?: string; nodeName?: string } = {},
|
|
436
|
+
externalInvitationId: string | null = null,
|
|
436
437
|
): Promise<EncryptedInvitation> {
|
|
437
438
|
const response = await this.apiService.post<PostInviteProtonUserRequest, PostInviteProtonUserResponse>(
|
|
438
439
|
`drive/v2/shares/${shareId}/invitations`,
|
|
@@ -443,7 +444,7 @@ export class SharingAPIService {
|
|
|
443
444
|
Permissions: memberRoleToPermission(invitation.role),
|
|
444
445
|
KeyPacket: invitation.base64KeyPacket,
|
|
445
446
|
KeyPacketSignature: invitation.base64KeyPacketSignature,
|
|
446
|
-
ExternalInvitationID:
|
|
447
|
+
ExternalInvitationID: externalInvitationId,
|
|
447
448
|
},
|
|
448
449
|
EmailDetails: {
|
|
449
450
|
Message: emailDetails.message,
|
|
@@ -182,11 +182,12 @@ export class SharingCryptoService {
|
|
|
182
182
|
shareSessionKey: SessionKey,
|
|
183
183
|
inviterKey: PrivateKey,
|
|
184
184
|
inviteeEmail: string,
|
|
185
|
+
forceRefreshKeys?: boolean,
|
|
185
186
|
): Promise<{
|
|
186
187
|
base64KeyPacket: string;
|
|
187
188
|
base64KeyPacketSignature: string;
|
|
188
189
|
}> {
|
|
189
|
-
const inviteePublicKeys = await this.account.getPublicKeys(inviteeEmail);
|
|
190
|
+
const inviteePublicKeys = await this.account.getPublicKeys(inviteeEmail, forceRefreshKeys);
|
|
190
191
|
const result = await this.driveCrypto.encryptInvitation(shareSessionKey, inviteePublicKeys[0], inviterKey);
|
|
191
192
|
return result;
|
|
192
193
|
}
|
|
@@ -1138,4 +1138,77 @@ describe('SharingManagement', () => {
|
|
|
1138
1138
|
expect(apiService.resendExternalInvitationEmail).not.toHaveBeenCalled();
|
|
1139
1139
|
});
|
|
1140
1140
|
});
|
|
1141
|
+
|
|
1142
|
+
describe('convertNonProtonInvitation', () => {
|
|
1143
|
+
const nodeUid = 'volumeId~nodeId';
|
|
1144
|
+
const externalInvitationId = 'inv123';
|
|
1145
|
+
const externalInvitationUid = `${DEFAULT_SHARE_ID}~${externalInvitationId}`;
|
|
1146
|
+
const externalInvitation: NonProtonInvitation = {
|
|
1147
|
+
uid: externalInvitationUid,
|
|
1148
|
+
inviteeEmail: 'external@example.com',
|
|
1149
|
+
addedByEmail: resultOk('inviter@example.com'),
|
|
1150
|
+
role: MemberRole.Viewer,
|
|
1151
|
+
invitationTime: new Date(),
|
|
1152
|
+
state: NonProtonInvitationState.Pending,
|
|
1153
|
+
};
|
|
1154
|
+
|
|
1155
|
+
beforeEach(() => {
|
|
1156
|
+
nodesService.getNode = jest.fn().mockResolvedValue({
|
|
1157
|
+
nodeUid,
|
|
1158
|
+
shareId: DEFAULT_SHARE_ID,
|
|
1159
|
+
directRole: MemberRole.Admin,
|
|
1160
|
+
name: { ok: true, value: 'name' },
|
|
1161
|
+
});
|
|
1162
|
+
apiService.getShareExternalInvitations = jest.fn().mockResolvedValue([externalInvitation]);
|
|
1163
|
+
});
|
|
1164
|
+
|
|
1165
|
+
it('should throw if caller is not admin', async () => {
|
|
1166
|
+
nodesService.getNode = jest.fn().mockResolvedValue({
|
|
1167
|
+
nodeUid,
|
|
1168
|
+
shareId: DEFAULT_SHARE_ID,
|
|
1169
|
+
directRole: MemberRole.Viewer,
|
|
1170
|
+
name: { ok: true, value: 'name' },
|
|
1171
|
+
});
|
|
1172
|
+
|
|
1173
|
+
await expect(
|
|
1174
|
+
sharingManagement.convertNonProtonInvitation(nodeUid, externalInvitationUid),
|
|
1175
|
+
).rejects.toThrow(ValidationError);
|
|
1176
|
+
});
|
|
1177
|
+
|
|
1178
|
+
it('should throw if no sharing info found', async () => {
|
|
1179
|
+
nodesService.getNode = jest.fn().mockResolvedValue({
|
|
1180
|
+
nodeUid,
|
|
1181
|
+
shareId: undefined,
|
|
1182
|
+
directRole: MemberRole.Admin,
|
|
1183
|
+
name: { ok: true, value: 'name' },
|
|
1184
|
+
});
|
|
1185
|
+
|
|
1186
|
+
await expect(
|
|
1187
|
+
sharingManagement.convertNonProtonInvitation(nodeUid, externalInvitationUid),
|
|
1188
|
+
).rejects.toThrow(ValidationError);
|
|
1189
|
+
});
|
|
1190
|
+
|
|
1191
|
+
it('should throw if external invitation ID is not found', async () => {
|
|
1192
|
+
await expect(
|
|
1193
|
+
sharingManagement.convertNonProtonInvitation(nodeUid, 'unknownShareId~unknownInvId'),
|
|
1194
|
+
).rejects.toThrow(ValidationError);
|
|
1195
|
+
});
|
|
1196
|
+
|
|
1197
|
+
it('should invite proton user with force-refreshed keys and the external invitation ID', async () => {
|
|
1198
|
+
await sharingManagement.convertNonProtonInvitation(nodeUid, externalInvitationUid);
|
|
1199
|
+
|
|
1200
|
+
expect(cryptoService.encryptInvitation).toHaveBeenCalledWith(
|
|
1201
|
+
expect.anything(),
|
|
1202
|
+
expect.anything(),
|
|
1203
|
+
externalInvitation.inviteeEmail,
|
|
1204
|
+
true,
|
|
1205
|
+
);
|
|
1206
|
+
expect(apiService.inviteProtonUser).toHaveBeenCalledWith(
|
|
1207
|
+
DEFAULT_SHARE_ID,
|
|
1208
|
+
expect.objectContaining({ inviteeEmail: externalInvitation.inviteeEmail, role: externalInvitation.role }),
|
|
1209
|
+
{},
|
|
1210
|
+
externalInvitationId,
|
|
1211
|
+
);
|
|
1212
|
+
});
|
|
1213
|
+
});
|
|
1141
1214
|
});
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
SharePublicLinkSettingsObject,
|
|
17
17
|
} from '../../interface';
|
|
18
18
|
import { ErrorCode } from '../apiService';
|
|
19
|
-
import { splitNodeUid } from '../uids';
|
|
19
|
+
import { splitNodeUid, splitInvitationUid } from '../uids';
|
|
20
20
|
import { getErrorMessage } from '../errors';
|
|
21
21
|
import { SharingAPIService } from './apiService';
|
|
22
22
|
import { PUBLIC_LINK_GENERATED_PASSWORD_LENGTH, SharingCryptoService } from './cryptoService';
|
|
@@ -596,8 +596,52 @@ export class SharingManagement {
|
|
|
596
596
|
await this.apiService.deleteExternalInvitation(invitationUid);
|
|
597
597
|
}
|
|
598
598
|
|
|
599
|
-
|
|
600
|
-
|
|
599
|
+
async convertNonProtonInvitation(nodeUid: string, nonProtonInvitationUid: string): Promise<ProtonInvitation> {
|
|
600
|
+
const { invitationId: externalInvitationId } = splitInvitationUid(nonProtonInvitationUid);
|
|
601
|
+
|
|
602
|
+
const node = await this.nodesService.getNode(nodeUid);
|
|
603
|
+
if (node.directRole !== MemberRole.Admin) {
|
|
604
|
+
throw new ValidationError(c('Error').t`Only admins can convert non-Proton invitations`);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const [currentSharing, inviter] = await Promise.all([
|
|
608
|
+
this.getInternalSharingInfo(nodeUid),
|
|
609
|
+
this.nodesService.getRootNodeEmailKey(nodeUid),
|
|
610
|
+
]);
|
|
611
|
+
if (!currentSharing) {
|
|
612
|
+
throw new ValidationError(c('Error').t`The node is not shared anymore`);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
const externalInvitation = currentSharing.nonProtonInvitations.find(
|
|
616
|
+
(invitation) => invitation.uid === nonProtonInvitationUid,
|
|
617
|
+
);
|
|
618
|
+
if (!externalInvitation) {
|
|
619
|
+
throw new ValidationError(c('Error').t`Invitation not found`);
|
|
620
|
+
}
|
|
621
|
+
this.logger.info(
|
|
622
|
+
`Converting non-Proton invitation for ${externalInvitation.inviteeEmail} to internal for node ${nodeUid}`,
|
|
623
|
+
);
|
|
624
|
+
const invitationCrypto = await this.cryptoService.encryptInvitation(
|
|
625
|
+
currentSharing.share.passphraseSessionKey,
|
|
626
|
+
inviter.addressKey,
|
|
627
|
+
externalInvitation.inviteeEmail,
|
|
628
|
+
true, // Force refresh keys: the invitee just created a Proton account, so we have "absent" keys in cache
|
|
629
|
+
);
|
|
630
|
+
const encryptedInvitation = await this.apiService.inviteProtonUser(
|
|
631
|
+
currentSharing.share.shareId,
|
|
632
|
+
{
|
|
633
|
+
addedByEmail: inviter.email,
|
|
634
|
+
inviteeEmail: externalInvitation.inviteeEmail,
|
|
635
|
+
role: externalInvitation.role,
|
|
636
|
+
...invitationCrypto,
|
|
637
|
+
},
|
|
638
|
+
{},
|
|
639
|
+
externalInvitationId,
|
|
640
|
+
);
|
|
641
|
+
return {
|
|
642
|
+
...encryptedInvitation,
|
|
643
|
+
addedByEmail: resultOk(encryptedInvitation.addedByEmail),
|
|
644
|
+
};
|
|
601
645
|
}
|
|
602
646
|
|
|
603
647
|
private async removeMember(memberUid: string): Promise<void> {
|
|
@@ -100,7 +100,13 @@ export function initSharingPublicNodesModule(
|
|
|
100
100
|
const cache = new NodesCache(telemetry.getLogger('nodes-cache'), driveEntitiesCache);
|
|
101
101
|
const cryptoCache = new NodesCryptoCache(telemetry.getLogger('nodes-cache'), driveCryptoCache);
|
|
102
102
|
const cryptoReporter = new SharingPublicCryptoReporter(telemetry);
|
|
103
|
-
const cryptoService = new SharingPublicNodesCryptoService(
|
|
103
|
+
const cryptoService = new SharingPublicNodesCryptoService(
|
|
104
|
+
telemetry,
|
|
105
|
+
driveCrypto,
|
|
106
|
+
account,
|
|
107
|
+
sharesService,
|
|
108
|
+
cryptoReporter,
|
|
109
|
+
);
|
|
104
110
|
const nodesAccess = new SharingPublicNodesAccess(
|
|
105
111
|
telemetry,
|
|
106
112
|
api,
|
package/src/protonDriveClient.ts
CHANGED
|
@@ -8,11 +8,13 @@ import {
|
|
|
8
8
|
MaybeNode,
|
|
9
9
|
MaybeMissingNode,
|
|
10
10
|
NodeResult,
|
|
11
|
+
NodeResultWithError,
|
|
11
12
|
NodeResultWithNewUid,
|
|
12
13
|
Revision,
|
|
13
14
|
RevisionOrUid,
|
|
14
15
|
ShareNodeSettings,
|
|
15
16
|
UnshareNodeSettings,
|
|
17
|
+
ProtonInvitation,
|
|
16
18
|
ProtonInvitationOrUid,
|
|
17
19
|
NonProtonInvitationOrUid,
|
|
18
20
|
ProtonInvitationWithNode,
|
|
@@ -420,7 +422,7 @@ export class ProtonDriveClient {
|
|
|
420
422
|
nodeUids: NodeOrUid[],
|
|
421
423
|
newParentNodeUid: NodeOrUid,
|
|
422
424
|
signal?: AbortSignal,
|
|
423
|
-
): AsyncGenerator<
|
|
425
|
+
): AsyncGenerator<NodeResultWithError> {
|
|
424
426
|
this.logger.info(`Moving ${nodeUids.length} nodes to ${getUid(newParentNodeUid)}`);
|
|
425
427
|
yield* this.nodes.management.moveNodes(getUids(nodeUids), getUid(newParentNodeUid), signal);
|
|
426
428
|
}
|
|
@@ -749,6 +751,22 @@ export class ProtonDriveClient {
|
|
|
749
751
|
return this.sharing.management.unshareNode(getUid(nodeUid), settings);
|
|
750
752
|
}
|
|
751
753
|
|
|
754
|
+
/**
|
|
755
|
+
* Convert a non-Proton invitation to an internal invitation.
|
|
756
|
+
* This is called automatically in the background when the SDK receives
|
|
757
|
+
* a metadata update event, but can also be triggered manually.
|
|
758
|
+
*
|
|
759
|
+
* @param nodeUid - Node entity or its UID string.
|
|
760
|
+
* @param invitationOrUid - Non-Proton invitation entity or its UID string.
|
|
761
|
+
*/
|
|
762
|
+
async convertNonProtonInvitation(
|
|
763
|
+
nodeUid: NodeOrUid,
|
|
764
|
+
invitationOrUid: NonProtonInvitationOrUid,
|
|
765
|
+
): Promise<ProtonInvitation> {
|
|
766
|
+
this.logger.info(`Converting non-Proton invitation ${getUid(invitationOrUid)} for node ${getUid(nodeUid)}`);
|
|
767
|
+
return this.sharing.management.convertNonProtonInvitation(getUid(nodeUid), getUid(invitationOrUid));
|
|
768
|
+
}
|
|
769
|
+
|
|
752
770
|
/**
|
|
753
771
|
* Resend the invitation email to shared node.
|
|
754
772
|
*
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
ShareNodeSettings,
|
|
14
14
|
ShareResult,
|
|
15
15
|
UnshareNodeSettings,
|
|
16
|
+
ProtonInvitation,
|
|
16
17
|
ProtonInvitationOrUid,
|
|
17
18
|
NonProtonInvitationOrUid,
|
|
18
19
|
ProtonInvitationWithNode,
|
|
@@ -408,6 +409,22 @@ export class ProtonDrivePhotosClient {
|
|
|
408
409
|
return this.sharing.management.unshareNode(getUid(nodeUid), settings);
|
|
409
410
|
}
|
|
410
411
|
|
|
412
|
+
/**
|
|
413
|
+
* Convert a non-Proton invitation to an internal invitation.
|
|
414
|
+
* This is called automatically in the background when the SDK receives
|
|
415
|
+
* a metadata update event, but can also be triggered manually.
|
|
416
|
+
*
|
|
417
|
+
* @param nodeUid - Node entity or its UID string.
|
|
418
|
+
* @param invitationOrUid - Non-Proton invitation entity or its UID string.
|
|
419
|
+
*/
|
|
420
|
+
async convertNonProtonInvitation(
|
|
421
|
+
nodeUid: NodeOrUid,
|
|
422
|
+
invitationOrUid: NonProtonInvitationOrUid,
|
|
423
|
+
): Promise<ProtonInvitation> {
|
|
424
|
+
this.logger.info(`Converting non-Proton invitation ${getUid(invitationOrUid)} for node ${getUid(nodeUid)}`);
|
|
425
|
+
return this.sharing.management.convertNonProtonInvitation(getUid(nodeUid), getUid(invitationOrUid));
|
|
426
|
+
}
|
|
427
|
+
|
|
411
428
|
/**
|
|
412
429
|
* Resend the invitation email to shared node.
|
|
413
430
|
*
|