cozy-sharing 33.3.1 → 33.3.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,12 @@
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
+ ## [33.3.2](https://github.com/cozy/cozy-libs/compare/cozy-sharing@33.3.1...cozy-sharing@33.3.2) (2026-06-11)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **cozy-sharing:** Use readonly endpoint in updateSharingMemberType ([f4914b6](https://github.com/cozy/cozy-libs/commit/f4914b61daaf5c20b17c4b9f1ddda511860a56ac))
11
+
6
12
  ## [33.3.1](https://github.com/cozy/cozy-libs/compare/cozy-sharing@33.3.0...cozy-sharing@33.3.1) (2026-06-10)
7
13
 
8
14
  ### Bug Fixes
@@ -471,7 +471,7 @@ export var SharingProvider = /*#__PURE__*/function (_Component) {
471
471
  */
472
472
  _defineProperty(_this2, "updateSharingMemberType", /*#__PURE__*/function () {
473
473
  var _ref12 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee10(sharingId, memberIndex, newType) {
474
- var sharing, member, matchedContacts, contact, readOnly;
474
+ var sharing, member, makeReadOnly;
475
475
  return _regeneratorRuntime.wrap(function _callee10$(_context10) {
476
476
  while (1) switch (_context10.prev = _context10.next) {
477
477
  case 0:
@@ -495,62 +495,49 @@ export var SharingProvider = /*#__PURE__*/function (_Component) {
495
495
  throw new Error('Member not found');
496
496
 
497
497
  case 6:
498
- _context10.next = 8;
499
- return _this2.props.client.collection('io.cozy.contacts').find({
500
- email: {
501
- $elemMatch: {
502
- address: member.email
503
- }
504
- },
505
- _id: {
506
- $gt: null
507
- }
508
- }, {
509
- indexedFields: ['_id']
510
- });
498
+ makeReadOnly = newType === 'one-way';
511
499
 
512
- case 8:
513
- matchedContacts = _context10.sent;
514
-
515
- if (matchedContacts.data.length) {
516
- _context10.next = 11;
500
+ if (!(newType !== 'one-way' && newType !== 'two-way')) {
501
+ _context10.next = 9;
517
502
  break;
518
503
  }
519
504
 
520
- throw new Error("Contact not found for member ".concat(member.email));
505
+ throw new Error('Unsupported sharing type: ' + newType);
521
506
 
522
- case 11:
523
- contact = matchedContacts.data[0]; // Revoke (API call only, no state dispatch to avoid UI flickering)
507
+ case 9:
508
+ _context10.prev = 9;
524
509
 
525
- _context10.next = 14;
526
- return _this2.sharingCol.revokeRecipient(sharing, memberIndex);
510
+ if (!makeReadOnly) {
511
+ _context10.next = 15;
512
+ break;
513
+ }
527
514
 
528
- case 14:
529
- // Re-add with the new permission type
530
- readOnly = newType === 'one-way';
531
- _context10.prev = 15;
532
- _context10.next = 18;
533
- return _this2.addRecipients({
534
- document: sharing,
535
- recipients: readOnly ? [] : [contact],
536
- readOnlyRecipients: readOnly ? [contact] : []
537
- });
515
+ _context10.next = 13;
516
+ return _this2.sharingCol.setReadOnly(sharing, memberIndex);
538
517
 
539
- case 18:
540
- _context10.next = 24;
518
+ case 13:
519
+ _context10.next = 17;
541
520
  break;
542
521
 
543
- case 20:
544
- _context10.prev = 20;
545
- _context10.t0 = _context10["catch"](15);
546
- log.error("Failed to re-add member ".concat(member.email, " after revocation. The member has been revoked but could not be re-added with the new permission type."), _context10.t0);
522
+ case 15:
523
+ _context10.next = 17;
524
+ return _this2.sharingCol.setReadWrite(sharing, memberIndex);
525
+
526
+ case 17:
527
+ _context10.next = 23;
528
+ break;
529
+
530
+ case 19:
531
+ _context10.prev = 19;
532
+ _context10.t0 = _context10["catch"](9);
533
+ log.error("Failed to change member ".concat(member.email, " permission type to ").concat(newType), _context10.t0);
547
534
  throw _context10.t0;
548
535
 
549
- case 24:
536
+ case 23:
550
537
  case "end":
551
538
  return _context10.stop();
552
539
  }
553
- }, _callee10, null, [[15, 20]]);
540
+ }, _callee10, null, [[9, 19]]);
554
541
  }));
555
542
 
556
543
  return function (_x16, _x17, _x18) {
@@ -412,12 +412,6 @@ describe('updateDocumentPermissions', function () {
412
412
  })));
413
413
  });
414
414
  describe('updateSharingMemberType', function () {
415
- var mockContact = {
416
- _id: 'contact-1',
417
- email: [{
418
- address: 'bob@bob.cozy'
419
- }]
420
- };
421
415
  var mockSharing = {
422
416
  id: 'sharing-123',
423
417
  type: 'io.cozy.sharings',
@@ -432,20 +426,14 @@ describe('updateSharingMemberType', function () {
432
426
  }
433
427
  };
434
428
  var instance;
435
- var mockContactsCollection;
436
429
  beforeEach(function () {
437
- mockContactsCollection = {
438
- find: jest.fn().mockResolvedValue({
439
- data: [mockContact]
440
- })
441
- };
442
430
  var mockClient = {
443
431
  getStackClient: function getStackClient() {
444
432
  return {
445
433
  uri: 'http://cozy.local'
446
434
  };
447
435
  },
448
- collection: jest.fn().mockReturnValue(mockContactsCollection)
436
+ collection: jest.fn().mockReturnValue({})
449
437
  };
450
438
  instance = new SharingProvider({
451
439
  client: mockClient
@@ -454,9 +442,10 @@ describe('updateSharingMemberType', function () {
454
442
  sharings: [mockSharing]
455
443
  });
456
444
  instance.sharingCol = {
457
- revokeRecipient: jest.fn().mockResolvedValue({})
445
+ setReadOnly: jest.fn().mockResolvedValue({}),
446
+ setReadWrite: jest.fn().mockResolvedValue({})
458
447
  };
459
- jest.spyOn(instance, 'addRecipients').mockResolvedValue({});
448
+ jest.spyOn(instance, 'setState');
460
449
  });
461
450
  it('should throw when sharing is not found', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee8() {
462
451
  return _regeneratorRuntime.wrap(function _callee8$(_context8) {
@@ -484,36 +473,33 @@ describe('updateSharingMemberType', function () {
484
473
  }
485
474
  }, _callee9);
486
475
  })));
487
- it('should throw when contact is not found', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee10() {
476
+ it('should call setReadOnly when switching to one-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee10() {
488
477
  return _regeneratorRuntime.wrap(function _callee10$(_context10) {
489
478
  while (1) switch (_context10.prev = _context10.next) {
490
479
  case 0:
491
- mockContactsCollection.find.mockResolvedValue({
492
- data: []
493
- });
494
- _context10.next = 3;
495
- return expect(instance.updateSharingMemberType('sharing-123', 1, 'one-way')).rejects.toThrow('Contact not found');
480
+ _context10.next = 2;
481
+ return instance.updateSharingMemberType('sharing-123', 1, 'one-way');
496
482
 
497
- case 3:
483
+ case 2:
484
+ expect(instance.sharingCol.setReadOnly).toHaveBeenCalledWith(mockSharing, 1);
485
+ expect(instance.sharingCol.setReadWrite).not.toHaveBeenCalled();
486
+
487
+ case 4:
498
488
  case "end":
499
489
  return _context10.stop();
500
490
  }
501
491
  }, _callee10);
502
492
  })));
503
- it('should revoke and re-add with readOnly when switching to one-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee11() {
493
+ it('should call setReadWrite when switching to two-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee11() {
504
494
  return _regeneratorRuntime.wrap(function _callee11$(_context11) {
505
495
  while (1) switch (_context11.prev = _context11.next) {
506
496
  case 0:
507
497
  _context11.next = 2;
508
- return instance.updateSharingMemberType('sharing-123', 1, 'one-way');
498
+ return instance.updateSharingMemberType('sharing-123', 1, 'two-way');
509
499
 
510
500
  case 2:
511
- expect(instance.sharingCol.revokeRecipient).toHaveBeenCalledWith(mockSharing, 1);
512
- expect(instance.addRecipients).toHaveBeenCalledWith({
513
- document: mockSharing,
514
- recipients: [],
515
- readOnlyRecipients: [mockContact]
516
- });
501
+ expect(instance.sharingCol.setReadWrite).toHaveBeenCalledWith(mockSharing, 1);
502
+ expect(instance.sharingCol.setReadOnly).not.toHaveBeenCalled();
517
503
 
518
504
  case 4:
519
505
  case "end":
@@ -521,34 +507,31 @@ describe('updateSharingMemberType', function () {
521
507
  }
522
508
  }, _callee11);
523
509
  })));
524
- it('should revoke and re-add as read-write when switching to two-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee12() {
510
+ it('should not dispatch a state update realtime refreshes the store', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee12() {
525
511
  return _regeneratorRuntime.wrap(function _callee12$(_context12) {
526
512
  while (1) switch (_context12.prev = _context12.next) {
527
513
  case 0:
528
514
  _context12.next = 2;
529
- return instance.updateSharingMemberType('sharing-123', 1, 'two-way');
515
+ return instance.updateSharingMemberType('sharing-123', 1, 'one-way');
530
516
 
531
517
  case 2:
532
- expect(instance.sharingCol.revokeRecipient).toHaveBeenCalledWith(mockSharing, 1);
533
- expect(instance.addRecipients).toHaveBeenCalledWith({
534
- document: mockSharing,
535
- recipients: [mockContact],
536
- readOnlyRecipients: []
537
- });
518
+ expect(instance.setState).not.toHaveBeenCalled();
538
519
 
539
- case 4:
520
+ case 3:
540
521
  case "end":
541
522
  return _context12.stop();
542
523
  }
543
524
  }, _callee12);
544
525
  })));
545
- it('should rethrow error if addRecipients fails after revocation', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee13() {
526
+ it('should not dispatch a state update for two-way — realtime refreshes the store', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee13() {
546
527
  return _regeneratorRuntime.wrap(function _callee13$(_context13) {
547
528
  while (1) switch (_context13.prev = _context13.next) {
548
529
  case 0:
549
- instance.addRecipients.mockRejectedValue(new Error('Network error'));
550
- _context13.next = 3;
551
- return expect(instance.updateSharingMemberType('sharing-123', 1, 'one-way')).rejects.toThrow('Network error');
530
+ _context13.next = 2;
531
+ return instance.updateSharingMemberType('sharing-123', 1, 'two-way');
532
+
533
+ case 2:
534
+ expect(instance.setState).not.toHaveBeenCalled();
552
535
 
553
536
  case 3:
554
537
  case "end":
@@ -556,6 +539,51 @@ describe('updateSharingMemberType', function () {
556
539
  }
557
540
  }, _callee13);
558
541
  })));
542
+ it('should rethrow error if setReadOnly fails', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee14() {
543
+ return _regeneratorRuntime.wrap(function _callee14$(_context14) {
544
+ while (1) switch (_context14.prev = _context14.next) {
545
+ case 0:
546
+ instance.sharingCol.setReadOnly.mockRejectedValue(new Error('Network error'));
547
+ _context14.next = 3;
548
+ return expect(instance.updateSharingMemberType('sharing-123', 1, 'one-way')).rejects.toThrow('Network error');
549
+
550
+ case 3:
551
+ case "end":
552
+ return _context14.stop();
553
+ }
554
+ }, _callee14);
555
+ })));
556
+ it('should rethrow error if setReadWrite fails', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee15() {
557
+ return _regeneratorRuntime.wrap(function _callee15$(_context15) {
558
+ while (1) switch (_context15.prev = _context15.next) {
559
+ case 0:
560
+ instance.sharingCol.setReadWrite.mockRejectedValue(new Error('Network error'));
561
+ _context15.next = 3;
562
+ return expect(instance.updateSharingMemberType('sharing-123', 1, 'two-way')).rejects.toThrow('Network error');
563
+
564
+ case 3:
565
+ case "end":
566
+ return _context15.stop();
567
+ }
568
+ }, _callee15);
569
+ })));
570
+ it('should not dispatch state update if API call fails', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee16() {
571
+ return _regeneratorRuntime.wrap(function _callee16$(_context16) {
572
+ while (1) switch (_context16.prev = _context16.next) {
573
+ case 0:
574
+ instance.sharingCol.setReadOnly.mockRejectedValue(new Error('Network error'));
575
+ _context16.next = 3;
576
+ return expect(instance.updateSharingMemberType('sharing-123', 1, 'one-way')).rejects.toThrow('Network error');
577
+
578
+ case 3:
579
+ expect(instance.setState).not.toHaveBeenCalled();
580
+
581
+ case 4:
582
+ case "end":
583
+ return _context16.stop();
584
+ }
585
+ }, _callee16);
586
+ })));
559
587
  }); // TODO Convert with react-testing-library
560
588
  // describe('hasWriteAccess', () => {
561
589
  // it('tells if a doc is writtable', () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-sharing",
3
- "version": "33.3.1",
3
+ "version": "33.3.2",
4
4
  "description": "Provides sharing login for React applications.",
5
5
  "main": "dist/index.js",
6
6
  "author": "Cozy",
@@ -53,7 +53,7 @@
53
53
  "babel-jest": "30.3.0",
54
54
  "babel-plugin-css-modules-transform": "1.6.2",
55
55
  "babel-plugin-inline-react-svg": "1.1.2",
56
- "cozy-client": "^60.24.0",
56
+ "cozy-client": "^60.27.0",
57
57
  "cozy-device-helper": "^4.0.0",
58
58
  "cozy-intent": "^2.31.1",
59
59
  "cozy-minilog": "^3.10.1",
@@ -68,7 +68,7 @@
68
68
  "twake-i18n": "^0.4.1"
69
69
  },
70
70
  "peerDependencies": {
71
- "cozy-client": ">=60.24.0",
71
+ "cozy-client": ">=60.27.0",
72
72
  "cozy-device-helper": ">=4.0.0",
73
73
  "cozy-flags": ">=4.8.1",
74
74
  "cozy-intent": ">=2.29.1",
@@ -83,5 +83,5 @@
83
83
  "sideEffects": [
84
84
  "*.css"
85
85
  ],
86
- "gitHead": "80c72e3af992596597b9319ee436ec8d238c9967"
86
+ "gitHead": "08272a4e1b47e169b6f4af662e30a40e4e9a4124"
87
87
  }
@@ -143,7 +143,7 @@ export class SharingProvider extends Component {
143
143
  this.fetchAllSharings()
144
144
  if (!client.plugins.realtime) {
145
145
  // eslint-disable-next-line
146
- console.warn(
146
+ console.warn(
147
147
  `You should register the realtime plugin to your CozyClient instance see https://docs.cozy.io/en/cozy-realtime/#example`
148
148
  )
149
149
  } else {
@@ -353,36 +353,19 @@ export class SharingProvider extends Component {
353
353
  const member = sharing.attributes.members[memberIndex]
354
354
  if (!member) throw new Error('Member not found')
355
355
 
356
- // Find the contact by email (same pattern as helpers/contacts.js)
357
- const matchedContacts = await this.props.client
358
- .collection('io.cozy.contacts')
359
- .find(
360
- {
361
- email: { $elemMatch: { address: member.email } },
362
- _id: { $gt: null }
363
- },
364
- { indexedFields: ['_id'] }
365
- )
366
-
367
- if (!matchedContacts.data.length) {
368
- throw new Error(`Contact not found for member ${member.email}`)
356
+ const makeReadOnly = newType === 'one-way'
357
+ if (newType !== 'one-way' && newType !== 'two-way') {
358
+ throw new Error('Unsupported sharing type: ' + newType)
369
359
  }
370
- const contact = matchedContacts.data[0]
371
-
372
- // Revoke (API call only, no state dispatch to avoid UI flickering)
373
- await this.sharingCol.revokeRecipient(sharing, memberIndex)
374
-
375
- // Re-add with the new permission type
376
- const readOnly = newType === 'one-way'
377
360
  try {
378
- await this.addRecipients({
379
- document: sharing,
380
- recipients: readOnly ? [] : [contact],
381
- readOnlyRecipients: readOnly ? [contact] : []
382
- })
361
+ if (makeReadOnly) {
362
+ await this.sharingCol.setReadOnly(sharing, memberIndex)
363
+ } else {
364
+ await this.sharingCol.setReadWrite(sharing, memberIndex)
365
+ }
383
366
  } catch (error) {
384
367
  log.error(
385
- `Failed to re-add member ${member.email} after revocation. The member has been revoked but could not be re-added with the new permission type.`,
368
+ `Failed to change member ${member.email} permission type to ${newType}`,
386
369
  error
387
370
  )
388
371
  throw error
@@ -292,7 +292,6 @@ describe('updateDocumentPermissions', () => {
292
292
  })
293
293
 
294
294
  describe('updateSharingMemberType', () => {
295
- const mockContact = { _id: 'contact-1', email: [{ address: 'bob@bob.cozy' }] }
296
295
  const mockSharing = {
297
296
  id: 'sharing-123',
298
297
  type: 'io.cozy.sharings',
@@ -305,16 +304,11 @@ describe('updateSharingMemberType', () => {
305
304
  }
306
305
 
307
306
  let instance
308
- let mockContactsCollection
309
307
 
310
308
  beforeEach(() => {
311
- mockContactsCollection = {
312
- find: jest.fn().mockResolvedValue({ data: [mockContact] })
313
- }
314
-
315
309
  const mockClient = {
316
310
  getStackClient: () => ({ uri: 'http://cozy.local' }),
317
- collection: jest.fn().mockReturnValue(mockContactsCollection)
311
+ collection: jest.fn().mockReturnValue({})
318
312
  }
319
313
 
320
314
  instance = new SharingProvider({ client: mockClient })
@@ -323,9 +317,10 @@ describe('updateSharingMemberType', () => {
323
317
  sharings: [mockSharing]
324
318
  }
325
319
  instance.sharingCol = {
326
- revokeRecipient: jest.fn().mockResolvedValue({})
320
+ setReadOnly: jest.fn().mockResolvedValue({}),
321
+ setReadWrite: jest.fn().mockResolvedValue({})
327
322
  }
328
- jest.spyOn(instance, 'addRecipients').mockResolvedValue({})
323
+ jest.spyOn(instance, 'setState')
329
324
  })
330
325
 
331
326
  it('should throw when sharing is not found', async () => {
@@ -340,48 +335,65 @@ describe('updateSharingMemberType', () => {
340
335
  ).rejects.toThrow('Member not found')
341
336
  })
342
337
 
343
- it('should throw when contact is not found', async () => {
344
- mockContactsCollection.find.mockResolvedValue({ data: [] })
338
+ it('should call setReadOnly when switching to one-way', async () => {
339
+ await instance.updateSharingMemberType('sharing-123', 1, 'one-way')
345
340
 
346
- await expect(
347
- instance.updateSharingMemberType('sharing-123', 1, 'one-way')
348
- ).rejects.toThrow('Contact not found')
341
+ expect(instance.sharingCol.setReadOnly).toHaveBeenCalledWith(mockSharing, 1)
342
+ expect(instance.sharingCol.setReadWrite).not.toHaveBeenCalled()
349
343
  })
350
344
 
351
- it('should revoke and re-add with readOnly when switching to one-way', async () => {
352
- await instance.updateSharingMemberType('sharing-123', 1, 'one-way')
345
+ it('should call setReadWrite when switching to two-way', async () => {
346
+ await instance.updateSharingMemberType('sharing-123', 1, 'two-way')
353
347
 
354
- expect(instance.sharingCol.revokeRecipient).toHaveBeenCalledWith(
348
+ expect(instance.sharingCol.setReadWrite).toHaveBeenCalledWith(
355
349
  mockSharing,
356
350
  1
357
351
  )
358
- expect(instance.addRecipients).toHaveBeenCalledWith({
359
- document: mockSharing,
360
- recipients: [],
361
- readOnlyRecipients: [mockContact]
362
- })
352
+ expect(instance.sharingCol.setReadOnly).not.toHaveBeenCalled()
363
353
  })
364
354
 
365
- it('should revoke and re-add as read-write when switching to two-way', async () => {
355
+ it('should not dispatch a state update realtime refreshes the store', async () => {
356
+ await instance.updateSharingMemberType('sharing-123', 1, 'one-way')
357
+
358
+ expect(instance.setState).not.toHaveBeenCalled()
359
+ })
360
+
361
+ it('should not dispatch a state update for two-way — realtime refreshes the store', async () => {
366
362
  await instance.updateSharingMemberType('sharing-123', 1, 'two-way')
367
363
 
368
- expect(instance.sharingCol.revokeRecipient).toHaveBeenCalledWith(
369
- mockSharing,
370
- 1
364
+ expect(instance.setState).not.toHaveBeenCalled()
365
+ })
366
+
367
+ it('should rethrow error if setReadOnly fails', async () => {
368
+ instance.sharingCol.setReadOnly.mockRejectedValue(
369
+ new Error('Network error')
371
370
  )
372
- expect(instance.addRecipients).toHaveBeenCalledWith({
373
- document: mockSharing,
374
- recipients: [mockContact],
375
- readOnlyRecipients: []
376
- })
371
+
372
+ await expect(
373
+ instance.updateSharingMemberType('sharing-123', 1, 'one-way')
374
+ ).rejects.toThrow('Network error')
375
+ })
376
+
377
+ it('should rethrow error if setReadWrite fails', async () => {
378
+ instance.sharingCol.setReadWrite.mockRejectedValue(
379
+ new Error('Network error')
380
+ )
381
+
382
+ await expect(
383
+ instance.updateSharingMemberType('sharing-123', 1, 'two-way')
384
+ ).rejects.toThrow('Network error')
377
385
  })
378
386
 
379
- it('should rethrow error if addRecipients fails after revocation', async () => {
380
- instance.addRecipients.mockRejectedValue(new Error('Network error'))
387
+ it('should not dispatch state update if API call fails', async () => {
388
+ instance.sharingCol.setReadOnly.mockRejectedValue(
389
+ new Error('Network error')
390
+ )
381
391
 
382
392
  await expect(
383
393
  instance.updateSharingMemberType('sharing-123', 1, 'one-way')
384
394
  ).rejects.toThrow('Network error')
395
+
396
+ expect(instance.setState).not.toHaveBeenCalled()
385
397
  })
386
398
  })
387
399