cozy-sharing 34.1.0 → 34.1.2

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/CHANGELOG.md CHANGED
@@ -3,6 +3,18 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [34.1.2](https://github.com/cozy/cozy-libs/compare/cozy-sharing@34.1.1...cozy-sharing@34.1.2) (2026-06-25)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **sharing:** Call self revoke for federated recipients ([25e62c2](https://github.com/cozy/cozy-libs/commit/25e62c2416e4b91bdab1e06fbbd77b00574a0b11))
11
+
12
+ ## [34.1.1](https://github.com/cozy/cozy-libs/compare/cozy-sharing@34.1.0...cozy-sharing@34.1.1) (2026-06-25)
13
+
14
+ ### Bug Fixes
15
+
16
+ - **federated:** Show correct recipients for files inside shared folder ([b656e73](https://github.com/cozy/cozy-libs/commit/b656e73240a30303c8063494ee052a197736d07c))
17
+
6
18
  # [34.1.0](https://github.com/cozy/cozy-libs/compare/cozy-sharing@34.0.2...cozy-sharing@34.1.0) (2026-06-25)
7
19
 
8
20
  ### Features
@@ -15,6 +15,7 @@ import { getOrCreateFromArray } from '../../helpers/contacts';
15
15
  import withLocales from '../../hoc/withLocales';
16
16
  import { usePendingRecipients } from '../../hooks/usePendingRecipients';
17
17
  import { useSharingContext } from '../../hooks/useSharingContext';
18
+ import { getRecipientsFromSharing } from '../../state';
18
19
  var styles = {
19
20
  "share-modal-content": "share__share-modal-content___1iqEq",
20
21
  "coz-form": "share__coz-form___1ICST",
@@ -40,9 +41,10 @@ import WhoHasAccess from '../WhoHasAccess';
40
41
  var log = minilog('FederatedFolderModal');
41
42
 
42
43
  var FederatedFolderModalContent = function FederatedFolderModalContent(_ref) {
43
- var _sharedDriveSharing$a;
44
+ var _sharedDriveSharing$a, _sharedDriveSharing$a2;
44
45
 
45
46
  var onClose = _ref.onClose,
47
+ onRevokeSuccess = _ref.onRevokeSuccess,
46
48
  existingDocument = _ref.document,
47
49
  autoOpenShareRestriction = _ref.autoOpenShareRestriction,
48
50
  showGenerateLinkButton = _ref.showGenerateLinkButton;
@@ -59,9 +61,12 @@ var FederatedFolderModalContent = function FederatedFolderModalContent(_ref) {
59
61
  getDocumentPermissions = _useSharingContext.getDocumentPermissions,
60
62
  fetchSharedDriveSharingLinks = _useSharingContext.fetchSharedDriveSharingLinks,
61
63
  getRecipients = _useSharingContext.getRecipients,
64
+ getSharedParentPath = _useSharingContext.getSharedParentPath,
62
65
  hasSharedChild = _useSharingContext.hasSharedChild,
63
66
  hasSharedParent = _useSharingContext.hasSharedParent,
64
- revoke = _useSharingContext.revoke;
67
+ isOwner = _useSharingContext.isOwner,
68
+ revoke = _useSharingContext.revoke,
69
+ revokeSelf = _useSharingContext.revokeSelf;
65
70
 
66
71
  var _useAlert = useAlert(),
67
72
  showAlert = _useAlert.showAlert;
@@ -220,10 +225,83 @@ var FederatedFolderModalContent = function FederatedFolderModalContent(_ref) {
220
225
  };
221
226
  }();
222
227
 
223
- var documentId = (existingDocument === null || existingDocument === void 0 ? void 0 : existingDocument._id) || (existingDocument === null || existingDocument === void 0 ? void 0 : existingDocument.id);
224
- var existingRecipients = documentId ? getRecipients(documentId) : [];
228
+ var documentId = (existingDocument === null || existingDocument === void 0 ? void 0 : existingDocument._id) || (existingDocument === null || existingDocument === void 0 ? void 0 : existingDocument.id); // Owner case: when document is inside a shared folder (via path) but has no driveId,
229
+ // find the parent shared folder to use its _id for recipient/permission lookups.
230
+
231
+ var sharedParentPath = !sharedDriveSharing && hasSharedParentByPath && documentPath ? getSharedParentPath(documentPath) : null;
232
+
233
+ var _useState11 = useState(null),
234
+ _useState12 = _slicedToArray(_useState11, 2),
235
+ sharedParentFolder = _useState12[0],
236
+ setSharedParentFolder = _useState12[1];
237
+
238
+ useEffect(function () {
239
+ if (!sharedParentPath) return;
240
+
241
+ var fetchParentFolder = /*#__PURE__*/function () {
242
+ var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() {
243
+ var res;
244
+ return _regeneratorRuntime.wrap(function _callee2$(_context2) {
245
+ while (1) switch (_context2.prev = _context2.next) {
246
+ case 0:
247
+ _context2.prev = 0;
248
+ _context2.next = 3;
249
+ return client.collection('io.cozy.files').statByPath(sharedParentPath);
250
+
251
+ case 3:
252
+ res = _context2.sent;
253
+ if (res.data) setSharedParentFolder(res.data);
254
+ _context2.next = 10;
255
+ break;
256
+
257
+ case 7:
258
+ _context2.prev = 7;
259
+ _context2.t0 = _context2["catch"](0);
260
+ log.error('Failed to fetch shared parent folder', _context2.t0);
261
+
262
+ case 10:
263
+ case "end":
264
+ return _context2.stop();
265
+ }
266
+ }, _callee2, null, [[0, 7]]);
267
+ }));
268
+
269
+ return function fetchParentFolder() {
270
+ return _ref3.apply(this, arguments);
271
+ };
272
+ }();
273
+
274
+ fetchParentFolder();
275
+ }, [sharedParentPath, client]);
276
+ var sharingReferenceId = !sharedDriveSharing && sharedParentFolder !== null && sharedParentFolder !== void 0 && sharedParentFolder._id ? sharedParentFolder._id : documentId;
277
+ var existingRecipients = sharedDriveSharing !== null && sharedDriveSharing !== void 0 && (_sharedDriveSharing$a2 = sharedDriveSharing.attributes) !== null && _sharedDriveSharing$a2 !== void 0 && _sharedDriveSharing$a2.members && documentId ? getRecipientsFromSharing(sharedDriveSharing, documentId) : sharingReferenceId ? getRecipients(sharingReferenceId) : [];
225
278
  var documentPermissions = documentId ? getDocumentPermissions(documentId) : [];
226
279
  var displayedLink = existingDocument !== null && existingDocument !== void 0 && existingDocument.driveId ? getFederatedShareLink(existingDocument) : getSharingLink(existingDocument === null || existingDocument === void 0 ? void 0 : existingDocument._id);
280
+ var isCurrentUserOwner = documentId ? isOwner(documentId) : false;
281
+
282
+ var handleRevokeSelf = /*#__PURE__*/function () {
283
+ var _ref4 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3(document) {
284
+ return _regeneratorRuntime.wrap(function _callee3$(_context3) {
285
+ while (1) switch (_context3.prev = _context3.next) {
286
+ case 0:
287
+ _context3.next = 2;
288
+ return revokeSelf(document);
289
+
290
+ case 2:
291
+ onRevokeSuccess === null || onRevokeSuccess === void 0 || onRevokeSuccess(document);
292
+
293
+ case 3:
294
+ case "end":
295
+ return _context3.stop();
296
+ }
297
+ }, _callee3);
298
+ }));
299
+
300
+ return function handleRevokeSelf(_x) {
301
+ return _ref4.apply(this, arguments);
302
+ };
303
+ }();
304
+
227
305
  useEffect(function () {
228
306
  if (!(existingDocument !== null && existingDocument !== void 0 && existingDocument.driveId)) return;
229
307
  if (!documentId) return;
@@ -233,39 +311,39 @@ var FederatedFolderModalContent = function FederatedFolderModalContent(_ref) {
233
311
  if (fetchedSharingLinksDocumentId === documentId) return;
234
312
 
235
313
  var fetchSharingLinks = /*#__PURE__*/function () {
236
- var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() {
237
- return _regeneratorRuntime.wrap(function _callee2$(_context2) {
238
- while (1) switch (_context2.prev = _context2.next) {
314
+ var _ref5 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4() {
315
+ return _regeneratorRuntime.wrap(function _callee4$(_context4) {
316
+ while (1) switch (_context4.prev = _context4.next) {
239
317
  case 0:
240
318
  setIsFetchingSharingLinks(true);
241
- _context2.prev = 1;
242
- _context2.next = 4;
319
+ _context4.prev = 1;
320
+ _context4.next = 4;
243
321
  return fetchSharedDriveSharingLinks(existingDocument);
244
322
 
245
323
  case 4:
246
- _context2.next = 9;
324
+ _context4.next = 9;
247
325
  break;
248
326
 
249
327
  case 6:
250
- _context2.prev = 6;
251
- _context2.t0 = _context2["catch"](1);
252
- log.error('Failed to fetch shared drive sharing links', _context2.t0);
328
+ _context4.prev = 6;
329
+ _context4.t0 = _context4["catch"](1);
330
+ log.error('Failed to fetch shared drive sharing links', _context4.t0);
253
331
 
254
332
  case 9:
255
- _context2.prev = 9;
333
+ _context4.prev = 9;
256
334
  setFetchedSharingLinksDocumentId(documentId);
257
335
  setIsFetchingSharingLinks(false);
258
- return _context2.finish(9);
336
+ return _context4.finish(9);
259
337
 
260
338
  case 13:
261
339
  case "end":
262
- return _context2.stop();
340
+ return _context4.stop();
263
341
  }
264
- }, _callee2, null, [[1, 6, 9, 13]]);
342
+ }, _callee4, null, [[1, 6, 9, 13]]);
265
343
  }));
266
344
 
267
345
  return function fetchSharingLinks() {
268
- return _ref3.apply(this, arguments);
346
+ return _ref5.apply(this, arguments);
269
347
  };
270
348
  }();
271
349
 
@@ -310,13 +388,14 @@ var FederatedFolderModalContent = function FederatedFolderModalContent(_ref) {
310
388
  onSelectedOptionChange: setSelectedOption,
311
389
  enableCreateContact: true
312
390
  }))), /*#__PURE__*/React.createElement(WhoHasAccess, {
313
- isOwner: true,
391
+ isOwner: isCurrentUserOwner,
314
392
  isSharedDrive: true,
315
393
  recipients: isSending ? frozenRecipients : existingRecipients,
316
394
  document: isSending ? frozenDoc : existingDocument,
317
395
  documentType: "Files",
318
396
  className: styles['share-dialog-members'],
319
397
  onRevoke: revoke,
398
+ onRevokeSelf: handleRevokeSelf,
320
399
  link: displayedLink
321
400
  })),
322
401
  actions: /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ShareByLink, {
@@ -339,6 +418,7 @@ export var FederatedFolderModal = withLocales(function (props) {
339
418
  });
340
419
  FederatedFolderModal.propTypes = {
341
420
  onClose: PropTypes.func.isRequired,
421
+ onRevokeSuccess: PropTypes.func,
342
422
  document: PropTypes.object.isRequired,
343
423
  autoOpenShareRestriction: PropTypes.bool,
344
424
  showGenerateLinkButton: PropTypes.bool
@@ -1,12 +1,12 @@
1
- import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
1
  import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
3
2
  import _extends from "@babel/runtime/helpers/extends";
3
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
4
+ import _regeneratorRuntime from "@babel/runtime/regenerator";
4
5
 
5
6
  function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
6
7
 
7
8
  function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
8
9
 
9
- import _regeneratorRuntime from "@babel/runtime/regenerator";
10
10
  import { act, fireEvent, render, waitFor } from '@testing-library/react';
11
11
  import React from 'react';
12
12
  import { createMockClient } from 'cozy-client';
@@ -17,6 +17,8 @@ import AppLike from '../SharingBanner/test/AppLike';
17
17
  var mockShare = jest.fn();
18
18
  var mockShareByLink = jest.fn();
19
19
  var mockRevoke = jest.fn();
20
+ var mockRevokeSelf = jest.fn();
21
+ var mockIsOwner = jest.fn();
20
22
  var mockGetSharingLink = jest.fn();
21
23
  var mockGetFederatedShareLink = jest.fn();
22
24
  var mockGetRecipients = jest.fn().mockReturnValue([]);
@@ -34,12 +36,15 @@ jest.mock('../../hooks/useSharingContext', function () {
34
36
  share: mockShare,
35
37
  shareByLink: mockShareByLink,
36
38
  revoke: mockRevoke,
39
+ revokeSelf: mockRevokeSelf,
40
+ isOwner: mockIsOwner,
37
41
  getSharingLink: mockGetSharingLink,
38
42
  getFederatedShareLink: mockGetFederatedShareLink,
39
43
  getDocumentPermissions: mockGetDocumentPermissions,
40
44
  fetchSharedDriveSharingLinks: mockFetchSharedDriveSharingLinks,
41
45
  getOwner: jest.fn(),
42
46
  getRecipients: mockGetRecipients,
47
+ getSharedParentPath: jest.fn().mockReturnValue(null),
43
48
  getSharingById: mockGetSharingById,
44
49
  hasSharedChild: mockHasSharedChild,
45
50
  hasSharedParent: mockHasSharedParent
@@ -122,6 +127,9 @@ describe('FederatedFolderModal', function () {
122
127
  var sharingContextValue;
123
128
  beforeEach(function () {
124
129
  jest.clearAllMocks();
130
+ mockRevoke.mockResolvedValue();
131
+ mockRevokeSelf.mockResolvedValue();
132
+ mockIsOwner.mockReturnValue(false);
125
133
  mockGetDocumentPermissions.mockReturnValue([]);
126
134
  mockFetchSharedDriveSharingLinks.mockResolvedValue([]);
127
135
  mockGetSharingLink.mockReturnValue('https://example.com/share/abc123');
@@ -146,6 +154,9 @@ describe('FederatedFolderModal', function () {
146
154
  }
147
155
  });
148
156
  client = createTestClient();
157
+ client.options = _objectSpread(_objectSpread({}, client.options), {}, {
158
+ uri: 'https://me.mycozy.cloud'
159
+ });
149
160
  sharingContextValue = createSharingContextValue();
150
161
  usePendingRecipients.mockReturnValue({
151
162
  pendingRecipients: [],
@@ -672,35 +683,76 @@ describe('FederatedFolderModal', function () {
672
683
  }, _callee18);
673
684
  })));
674
685
  });
675
- describe('onClose callback', function () {
676
- it('should call onClose when close button is clicked', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee19() {
677
- var _setup16, findByRole, closeButton;
686
+ describe('onRevoke callback', function () {
687
+ var currentUserRecipient = {
688
+ instance: 'https://me.mycozy.cloud',
689
+ memberIndex: 1,
690
+ sharingId: 'sharing-id',
691
+ type: 'two-way'
692
+ };
693
+ it('should call onRevokeSelf and onRevokeSuccess when the current user is revoked', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee19() {
694
+ var onRevokeSuccess, _setup16, findByLabelText;
678
695
 
679
696
  return _regeneratorRuntime.wrap(function _callee19$(_context19) {
680
697
  while (1) switch (_context19.prev = _context19.next) {
681
698
  case 0:
682
- _setup16 = setup(), findByRole = _setup16.findByRole;
683
- _context19.next = 3;
699
+ onRevokeSuccess = jest.fn();
700
+ mockGetRecipients.mockReturnValue([currentUserRecipient]);
701
+ _setup16 = setup({
702
+ onRevokeSuccess: onRevokeSuccess
703
+ }), findByLabelText = _setup16.findByLabelText;
704
+ _context19.t0 = fireEvent;
705
+ _context19.next = 6;
706
+ return findByLabelText('Remove from sharing');
707
+
708
+ case 6:
709
+ _context19.t1 = _context19.sent;
710
+
711
+ _context19.t0.click.call(_context19.t0, _context19.t1);
712
+
713
+ _context19.next = 10;
714
+ return waitFor(function () {
715
+ expect(mockRevokeSelf).toHaveBeenCalledWith(mockDocument);
716
+ expect(mockRevoke).not.toHaveBeenCalled();
717
+ expect(onRevokeSuccess).toHaveBeenCalledWith(mockDocument);
718
+ });
719
+
720
+ case 10:
721
+ case "end":
722
+ return _context19.stop();
723
+ }
724
+ }, _callee19);
725
+ })));
726
+ });
727
+ describe('onClose callback', function () {
728
+ it('should call onClose when close button is clicked', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee20() {
729
+ var _setup17, findByRole, closeButton;
730
+
731
+ return _regeneratorRuntime.wrap(function _callee20$(_context20) {
732
+ while (1) switch (_context20.prev = _context20.next) {
733
+ case 0:
734
+ _setup17 = setup(), findByRole = _setup17.findByRole;
735
+ _context20.next = 3;
684
736
  return findByRole('button', {
685
737
  name: /close/i
686
738
  });
687
739
 
688
740
  case 3:
689
- closeButton = _context19.sent;
741
+ closeButton = _context20.sent;
690
742
  fireEvent.click(closeButton);
691
743
  expect(mockOnClose).toHaveBeenCalled();
692
744
 
693
745
  case 6:
694
746
  case "end":
695
- return _context19.stop();
747
+ return _context20.stop();
696
748
  }
697
- }, _callee19);
749
+ }, _callee20);
698
750
  })));
699
- it('should ask confirmation when close button is clicked with pending recipients', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee20() {
700
- var _setup17, getByRole, findByRole, getByText, closeButton;
751
+ it('should ask confirmation when close button is clicked with pending recipients', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee21() {
752
+ var _setup18, getByRole, findByRole, getByText, closeButton;
701
753
 
702
- return _regeneratorRuntime.wrap(function _callee20$(_context20) {
703
- while (1) switch (_context20.prev = _context20.next) {
754
+ return _regeneratorRuntime.wrap(function _callee21$(_context21) {
755
+ while (1) switch (_context21.prev = _context21.next) {
704
756
  case 0:
705
757
  usePendingRecipients.mockReturnValue({
706
758
  pendingRecipients: [{
@@ -711,14 +763,14 @@ describe('FederatedFolderModal', function () {
711
763
  selectedOption: 'readWrite',
712
764
  setSelectedOption: jest.fn()
713
765
  });
714
- _setup17 = setup(), getByRole = _setup17.getByRole, findByRole = _setup17.findByRole, getByText = _setup17.getByText;
715
- _context20.next = 4;
766
+ _setup18 = setup(), getByRole = _setup18.getByRole, findByRole = _setup18.findByRole, getByText = _setup18.getByText;
767
+ _context21.next = 4;
716
768
  return findByRole('button', {
717
769
  name: /close/i
718
770
  });
719
771
 
720
772
  case 4:
721
- closeButton = _context20.sent;
773
+ closeButton = _context21.sent;
722
774
  fireEvent.click(closeButton);
723
775
  expect(getByText("Discard the changes you haven't saved?")).toBeTruthy();
724
776
  expect(mockOnClose).not.toHaveBeenCalled();
@@ -729,15 +781,15 @@ describe('FederatedFolderModal', function () {
729
781
 
730
782
  case 10:
731
783
  case "end":
732
- return _context20.stop();
784
+ return _context21.stop();
733
785
  }
734
- }, _callee20);
786
+ }, _callee21);
735
787
  })));
736
- it('should close after discard confirmation', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee21() {
737
- var _setup18, getByRole, findByRole, getByText, closeButton;
788
+ it('should close after discard confirmation', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee22() {
789
+ var _setup19, getByRole, findByRole, getByText, closeButton;
738
790
 
739
- return _regeneratorRuntime.wrap(function _callee21$(_context21) {
740
- while (1) switch (_context21.prev = _context21.next) {
791
+ return _regeneratorRuntime.wrap(function _callee22$(_context22) {
792
+ while (1) switch (_context22.prev = _context22.next) {
741
793
  case 0:
742
794
  usePendingRecipients.mockReturnValue({
743
795
  pendingRecipients: [{
@@ -748,14 +800,14 @@ describe('FederatedFolderModal', function () {
748
800
  selectedOption: 'readWrite',
749
801
  setSelectedOption: jest.fn()
750
802
  });
751
- _setup18 = setup(), getByRole = _setup18.getByRole, findByRole = _setup18.findByRole, getByText = _setup18.getByText;
752
- _context21.next = 4;
803
+ _setup19 = setup(), getByRole = _setup19.getByRole, findByRole = _setup19.findByRole, getByText = _setup19.getByText;
804
+ _context22.next = 4;
753
805
  return findByRole('button', {
754
806
  name: /close/i
755
807
  });
756
808
 
757
809
  case 4:
758
- closeButton = _context21.sent;
810
+ closeButton = _context22.sent;
759
811
  fireEvent.click(closeButton);
760
812
  expect(getByText("Discard the changes you haven't saved?")).toBeTruthy();
761
813
  fireEvent.click(getByRole('button', {
@@ -765,9 +817,9 @@ describe('FederatedFolderModal', function () {
765
817
 
766
818
  case 9:
767
819
  case "end":
768
- return _context21.stop();
820
+ return _context22.stop();
769
821
  }
770
- }, _callee21);
822
+ }, _callee22);
771
823
  })));
772
824
  });
773
825
  });
@@ -20,6 +20,8 @@ var log = minilog('MemberRecipientPermissions');
20
20
 
21
21
  var MemberRecipientPermissions = function MemberRecipientPermissions(_ref) {
22
22
  var isOwner = _ref.isOwner,
23
+ _ref$canManageSharing = _ref.canManageSharing,
24
+ canManageSharing = _ref$canManageSharing === void 0 ? isOwner : _ref$canManageSharing,
23
25
  isReadOnly = _ref.isReadOnly,
24
26
  status = _ref.status,
25
27
  instance = _ref.instance,
@@ -54,7 +56,7 @@ var MemberRecipientPermissions = function MemberRecipientPermissions(_ref) {
54
56
 
55
57
  var instanceMatchesClient = instance !== undefined && instance === client.options.uri;
56
58
  var contactIsOwner = status === 'owner';
57
- var shouldShowMenu = !isReadOnly && !revoking && !contactIsOwner && (instanceMatchesClient && !isOwner || isOwner);
59
+ var shouldShowMenu = !isReadOnly && !revoking && !contactIsOwner && (instanceMatchesClient && !isOwner || canManageSharing);
58
60
  var toggleMenu = useCallback(function () {
59
61
  setMenuDisplayed(function (displayed) {
60
62
  return !displayed;
@@ -70,13 +72,13 @@ var MemberRecipientPermissions = function MemberRecipientPermissions(_ref) {
70
72
  setRevoking(true);
71
73
  _context.prev = 1;
72
74
 
73
- if (!isOwner) {
75
+ if (!(instanceMatchesClient && !isOwner)) {
74
76
  _context.next = 7;
75
77
  break;
76
78
  }
77
79
 
78
80
  _context.next = 5;
79
- return onRevoke(document, sharingId, memberIndex);
81
+ return onRevokeSelf(document);
80
82
 
81
83
  case 5:
82
84
  _context.next = 9;
@@ -84,7 +86,7 @@ var MemberRecipientPermissions = function MemberRecipientPermissions(_ref) {
84
86
 
85
87
  case 7:
86
88
  _context.next = 9;
87
- return onRevokeSelf(document);
89
+ return onRevoke(document, sharingId, memberIndex);
88
90
 
89
91
  case 9:
90
92
  _context.prev = 9;
@@ -96,7 +98,7 @@ var MemberRecipientPermissions = function MemberRecipientPermissions(_ref) {
96
98
  return _context.stop();
97
99
  }
98
100
  }, _callee, null, [[1,, 9, 12]]);
99
- })), [isOwner, onRevoke, onRevokeSelf, document, sharingId, memberIndex]);
101
+ })), [document, instanceMatchesClient, isOwner, memberIndex, onRevoke, onRevokeSelf, sharingId]);
100
102
  var setType = useCallback( /*#__PURE__*/function () {
101
103
  var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(newType) {
102
104
  return _regeneratorRuntime.wrap(function _callee2$(_context2) {
@@ -9,6 +9,7 @@ var RecipientList = function RecipientList(_ref) {
9
9
  var recipients = _ref.recipients,
10
10
  recipientsToBeConfirmed = _ref.recipientsToBeConfirmed,
11
11
  isOwner = _ref.isOwner,
12
+ canManageSharing = _ref.canManageSharing,
12
13
  isSharedDrive = _ref.isSharedDrive,
13
14
  isReadOnly = _ref.isReadOnly,
14
15
  document = _ref.document,
@@ -40,6 +41,7 @@ var RecipientList = function RecipientList(_ref) {
40
41
  return /*#__PURE__*/React.createElement(MemberRecipient, _extends({}, recipient, {
41
42
  key: recipient.index,
42
43
  isOwner: isOwner,
44
+ canManageSharing: canManageSharing,
43
45
  isSharedDrive: isSharedDrive,
44
46
  isReadOnly: isReadOnly,
45
47
  document: document,
@@ -63,7 +63,8 @@ export var ShareModal = withLocales(function (props) {
63
63
 
64
64
  if (isFederatedMode && allLoaded) {
65
65
  return /*#__PURE__*/React.createElement(FederatedFolderModal, _extends({
66
- document: document
66
+ document: document,
67
+ onRevokeSuccess: onRevokeSuccess
67
68
  }, rest));
68
69
  }
69
70
 
@@ -30,6 +30,8 @@ var RecipientWaitingForConfirmationAlert = function RecipientWaitingForConfirmat
30
30
  var WhoHasAccess = function WhoHasAccess(_ref2) {
31
31
  var _ref2$isOwner = _ref2.isOwner,
32
32
  isOwner = _ref2$isOwner === void 0 ? false : _ref2$isOwner,
33
+ _ref2$canManageSharin = _ref2.canManageSharing,
34
+ canManageSharing = _ref2$canManageSharin === void 0 ? isOwner : _ref2$canManageSharin,
33
35
  _ref2$isSharedDrive = _ref2.isSharedDrive,
34
36
  isSharedDrive = _ref2$isSharedDrive === void 0 ? false : _ref2$isSharedDrive,
35
37
  _ref2$isReadOnly = _ref2.isReadOnly,
@@ -68,6 +70,7 @@ var WhoHasAccess = function WhoHasAccess(_ref2) {
68
70
  recipients: recipients,
69
71
  recipientsToBeConfirmed: recipientsToBeConfirmed,
70
72
  isOwner: isOwner,
73
+ canManageSharing: canManageSharing,
71
74
  isSharedDrive: isSharedDrive,
72
75
  isReadOnly: isReadOnly,
73
76
  document: document,
@@ -81,6 +84,7 @@ var WhoHasAccess = function WhoHasAccess(_ref2) {
81
84
 
82
85
  WhoHasAccess.propTypes = {
83
86
  isOwner: PropTypes.bool,
87
+ canManageSharing: PropTypes.bool,
84
88
  showOwner: PropTypes.bool,
85
89
  recipients: PropTypes.array.isRequired,
86
90
  recipientsToBeConfirmed: PropTypes.arrayOf(PropTypes.shape({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-sharing",
3
- "version": "34.1.0",
3
+ "version": "34.1.2",
4
4
  "description": "Provides sharing login for React applications.",
5
5
  "main": "dist/index.js",
6
6
  "author": "Cozy",
@@ -85,5 +85,5 @@
85
85
  "sideEffects": [
86
86
  "*.css"
87
87
  ],
88
- "gitHead": "4e29033c8179810abb9897d1a9d6b90446c53f47"
88
+ "gitHead": "3f975f8efa4061617d6fa5df190f1cbb83170f09"
89
89
  }
@@ -16,6 +16,7 @@ import { getOrCreateFromArray } from '../../helpers/contacts'
16
16
  import withLocales from '../../hoc/withLocales'
17
17
  import { usePendingRecipients } from '../../hooks/usePendingRecipients'
18
18
  import { useSharingContext } from '../../hooks/useSharingContext'
19
+ import { getRecipientsFromSharing } from '../../state'
19
20
  import styles from '../../styles/share.styl'
20
21
  import AntivirusAlert from '../AntivirusAlert'
21
22
  import { default as DumbShareByEmail } from '../ShareByEmail'
@@ -26,6 +27,7 @@ const log = minilog('FederatedFolderModal')
26
27
 
27
28
  const FederatedFolderModalContent = ({
28
29
  onClose,
30
+ onRevokeSuccess,
29
31
  document: existingDocument,
30
32
  autoOpenShareRestriction,
31
33
  showGenerateLinkButton
@@ -40,9 +42,12 @@ const FederatedFolderModalContent = ({
40
42
  getDocumentPermissions,
41
43
  fetchSharedDriveSharingLinks,
42
44
  getRecipients,
45
+ getSharedParentPath,
43
46
  hasSharedChild,
44
47
  hasSharedParent,
45
- revoke
48
+ isOwner,
49
+ revoke,
50
+ revokeSelf
46
51
  } = useSharingContext()
47
52
  const { showAlert } = useAlert()
48
53
  const { showConfirmDialog, closeConfirmDialog } = useConfirmDialog()
@@ -178,13 +183,52 @@ const FederatedFolderModalContent = ({
178
183
  }
179
184
 
180
185
  const documentId = existingDocument?._id || existingDocument?.id
181
- const existingRecipients = documentId ? getRecipients(documentId) : []
186
+ // Owner case: when document is inside a shared folder (via path) but has no driveId,
187
+ // find the parent shared folder to use its _id for recipient/permission lookups.
188
+ const sharedParentPath =
189
+ !sharedDriveSharing && hasSharedParentByPath && documentPath
190
+ ? getSharedParentPath(documentPath)
191
+ : null
192
+ const [sharedParentFolder, setSharedParentFolder] = useState(null)
193
+
194
+ useEffect(() => {
195
+ if (!sharedParentPath) return
196
+ const fetchParentFolder = async () => {
197
+ try {
198
+ const res = await client
199
+ .collection('io.cozy.files')
200
+ .statByPath(sharedParentPath)
201
+ if (res.data) setSharedParentFolder(res.data)
202
+ } catch (err) {
203
+ log.error('Failed to fetch shared parent folder', err)
204
+ }
205
+ }
206
+ fetchParentFolder()
207
+ }, [sharedParentPath, client])
208
+
209
+ const sharingReferenceId =
210
+ !sharedDriveSharing && sharedParentFolder?._id
211
+ ? sharedParentFolder._id
212
+ : documentId
213
+
214
+ const existingRecipients =
215
+ sharedDriveSharing?.attributes?.members && documentId
216
+ ? getRecipientsFromSharing(sharedDriveSharing, documentId)
217
+ : sharingReferenceId
218
+ ? getRecipients(sharingReferenceId)
219
+ : []
182
220
  const documentPermissions = documentId
183
221
  ? getDocumentPermissions(documentId)
184
222
  : []
185
223
  const displayedLink = existingDocument?.driveId
186
224
  ? getFederatedShareLink(existingDocument)
187
225
  : getSharingLink(existingDocument?._id)
226
+ const isCurrentUserOwner = documentId ? isOwner(documentId) : false
227
+
228
+ const handleRevokeSelf = async document => {
229
+ await revokeSelf(document)
230
+ onRevokeSuccess?.(document)
231
+ }
188
232
 
189
233
  useEffect(() => {
190
234
  if (!existingDocument?.driveId) return
@@ -278,13 +322,14 @@ const FederatedFolderModalContent = ({
278
322
  )}
279
323
  </div>
280
324
  <WhoHasAccess
281
- isOwner
325
+ isOwner={isCurrentUserOwner}
282
326
  isSharedDrive
283
327
  recipients={isSending ? frozenRecipients : existingRecipients}
284
328
  document={isSending ? frozenDoc : existingDocument}
285
329
  documentType="Files"
286
330
  className={styles['share-dialog-members']}
287
331
  onRevoke={revoke}
332
+ onRevokeSelf={handleRevokeSelf}
288
333
  link={displayedLink}
289
334
  />
290
335
  </div>
@@ -320,6 +365,7 @@ export const FederatedFolderModal = withLocales(props => (
320
365
 
321
366
  FederatedFolderModal.propTypes = {
322
367
  onClose: PropTypes.func.isRequired,
368
+ onRevokeSuccess: PropTypes.func,
323
369
  document: PropTypes.object.isRequired,
324
370
  autoOpenShareRestriction: PropTypes.bool,
325
371
  showGenerateLinkButton: PropTypes.bool
@@ -11,6 +11,8 @@ import AppLike from '../SharingBanner/test/AppLike'
11
11
  const mockShare = jest.fn()
12
12
  const mockShareByLink = jest.fn()
13
13
  const mockRevoke = jest.fn()
14
+ const mockRevokeSelf = jest.fn()
15
+ const mockIsOwner = jest.fn()
14
16
  const mockGetSharingLink = jest.fn()
15
17
  const mockGetFederatedShareLink = jest.fn()
16
18
  const mockGetRecipients = jest.fn().mockReturnValue([])
@@ -27,12 +29,15 @@ jest.mock('../../hooks/useSharingContext', () => ({
27
29
  share: mockShare,
28
30
  shareByLink: mockShareByLink,
29
31
  revoke: mockRevoke,
32
+ revokeSelf: mockRevokeSelf,
33
+ isOwner: mockIsOwner,
30
34
  getSharingLink: mockGetSharingLink,
31
35
  getFederatedShareLink: mockGetFederatedShareLink,
32
36
  getDocumentPermissions: mockGetDocumentPermissions,
33
37
  fetchSharedDriveSharingLinks: mockFetchSharedDriveSharingLinks,
34
38
  getOwner: jest.fn(),
35
39
  getRecipients: mockGetRecipients,
40
+ getSharedParentPath: jest.fn().mockReturnValue(null),
36
41
  getSharingById: mockGetSharingById,
37
42
  hasSharedChild: mockHasSharedChild,
38
43
  hasSharedParent: mockHasSharedParent
@@ -100,6 +105,9 @@ describe('FederatedFolderModal', () => {
100
105
 
101
106
  beforeEach(() => {
102
107
  jest.clearAllMocks()
108
+ mockRevoke.mockResolvedValue()
109
+ mockRevokeSelf.mockResolvedValue()
110
+ mockIsOwner.mockReturnValue(false)
103
111
  mockGetDocumentPermissions.mockReturnValue([])
104
112
  mockFetchSharedDriveSharingLinks.mockResolvedValue([])
105
113
  mockGetSharingLink.mockReturnValue('https://example.com/share/abc123')
@@ -124,6 +132,10 @@ describe('FederatedFolderModal', () => {
124
132
  }
125
133
  })
126
134
  client = createTestClient()
135
+ client.options = {
136
+ ...client.options,
137
+ uri: 'https://me.mycozy.cloud'
138
+ }
127
139
  sharingContextValue = createSharingContextValue()
128
140
  usePendingRecipients.mockReturnValue({
129
141
  pendingRecipients: [],
@@ -437,6 +449,30 @@ describe('FederatedFolderModal', () => {
437
449
  })
438
450
  })
439
451
 
452
+ describe('onRevoke callback', () => {
453
+ const currentUserRecipient = {
454
+ instance: 'https://me.mycozy.cloud',
455
+ memberIndex: 1,
456
+ sharingId: 'sharing-id',
457
+ type: 'two-way'
458
+ }
459
+
460
+ it('should call onRevokeSelf and onRevokeSuccess when the current user is revoked', async () => {
461
+ const onRevokeSuccess = jest.fn()
462
+ mockGetRecipients.mockReturnValue([currentUserRecipient])
463
+
464
+ const { findByLabelText } = setup({ onRevokeSuccess })
465
+
466
+ fireEvent.click(await findByLabelText('Remove from sharing'))
467
+
468
+ await waitFor(() => {
469
+ expect(mockRevokeSelf).toHaveBeenCalledWith(mockDocument)
470
+ expect(mockRevoke).not.toHaveBeenCalled()
471
+ expect(onRevokeSuccess).toHaveBeenCalledWith(mockDocument)
472
+ })
473
+ })
474
+ })
475
+
440
476
  describe('onClose callback', () => {
441
477
  it('should call onClose when close button is clicked', async () => {
442
478
  const { findByRole } = setup()
@@ -20,6 +20,7 @@ const log = minilog('MemberRecipientPermissions')
20
20
 
21
21
  const MemberRecipientPermissions = ({
22
22
  isOwner,
23
+ canManageSharing = isOwner,
23
24
  isReadOnly,
24
25
  status,
25
26
  instance,
@@ -46,7 +47,7 @@ const MemberRecipientPermissions = ({
46
47
  !isReadOnly &&
47
48
  !revoking &&
48
49
  !contactIsOwner &&
49
- ((instanceMatchesClient && !isOwner) || isOwner)
50
+ ((instanceMatchesClient && !isOwner) || canManageSharing)
50
51
 
51
52
  const toggleMenu = useCallback(() => {
52
53
  setMenuDisplayed(displayed => !displayed)
@@ -58,15 +59,23 @@ const MemberRecipientPermissions = ({
58
59
  const handleRevocation = useCallback(async () => {
59
60
  setRevoking(true)
60
61
  try {
61
- if (isOwner) {
62
- await onRevoke(document, sharingId, memberIndex)
63
- } else {
62
+ if (instanceMatchesClient && !isOwner) {
64
63
  await onRevokeSelf(document)
64
+ } else {
65
+ await onRevoke(document, sharingId, memberIndex)
65
66
  }
66
67
  } finally {
67
68
  setRevoking(false)
68
69
  }
69
- }, [isOwner, onRevoke, onRevokeSelf, document, sharingId, memberIndex])
70
+ }, [
71
+ document,
72
+ instanceMatchesClient,
73
+ isOwner,
74
+ memberIndex,
75
+ onRevoke,
76
+ onRevokeSelf,
77
+ sharingId
78
+ ])
70
79
 
71
80
  const setType = useCallback(
72
81
  async newType => {
@@ -9,6 +9,7 @@ const RecipientList = ({
9
9
  recipients,
10
10
  recipientsToBeConfirmed,
11
11
  isOwner,
12
+ canManageSharing,
12
13
  isSharedDrive,
13
14
  isReadOnly,
14
15
  document,
@@ -50,6 +51,7 @@ const RecipientList = ({
50
51
  {...recipient}
51
52
  key={recipient.index}
52
53
  isOwner={isOwner}
54
+ canManageSharing={canManageSharing}
53
55
  isSharedDrive={isSharedDrive}
54
56
  isReadOnly={isReadOnly}
55
57
  document={document}
@@ -38,7 +38,13 @@ export const ShareModal = withLocales(props => {
38
38
  if (isEditable) {
39
39
  const isFederatedMode = flag('drive.federated-shared-folder.enabled')
40
40
  if (isFederatedMode && allLoaded) {
41
- return <FederatedFolderModal document={document} {...rest} />
41
+ return (
42
+ <FederatedFolderModal
43
+ document={document}
44
+ onRevokeSuccess={onRevokeSuccess}
45
+ {...rest}
46
+ />
47
+ )
42
48
  }
43
49
 
44
50
  return <EditableSharingModal document={document} {...rest} />
@@ -28,6 +28,7 @@ const RecipientWaitingForConfirmationAlert = ({ recipientsToBeConfirmed }) => {
28
28
 
29
29
  const WhoHasAccess = ({
30
30
  isOwner = false,
31
+ canManageSharing = isOwner,
31
32
  isSharedDrive = false,
32
33
  isReadOnly = false,
33
34
  showOwner = true,
@@ -70,6 +71,7 @@ const WhoHasAccess = ({
70
71
  recipients={recipients}
71
72
  recipientsToBeConfirmed={recipientsToBeConfirmed}
72
73
  isOwner={isOwner}
74
+ canManageSharing={canManageSharing}
73
75
  isSharedDrive={isSharedDrive}
74
76
  isReadOnly={isReadOnly}
75
77
  document={document}
@@ -86,6 +88,7 @@ const WhoHasAccess = ({
86
88
 
87
89
  WhoHasAccess.propTypes = {
88
90
  isOwner: PropTypes.bool,
91
+ canManageSharing: PropTypes.bool,
89
92
  showOwner: PropTypes.bool,
90
93
  recipients: PropTypes.array.isRequired,
91
94
  recipientsToBeConfirmed: PropTypes.arrayOf(