cozy-sharing 27.0.6 → 27.1.0

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
+ # [27.1.0](https://github.com/cozy/cozy-libs/compare/cozy-sharing@27.0.6...cozy-sharing@27.1.0) (2025-11-25)
7
+
8
+ ### Features
9
+
10
+ - Enhance sharing logic to retain shared drives without recipients ([ab91186](https://github.com/cozy/cozy-libs/commit/ab911869e5f49aafaf5ca16b3323bd9826072b35))
11
+
6
12
  ## [27.0.6](https://github.com/cozy/cozy-libs/compare/cozy-sharing@27.0.5...cozy-sharing@27.0.6) (2025-11-14)
7
13
 
8
14
  ### Bug Fixes
package/dist/state.js CHANGED
@@ -39,7 +39,9 @@ export var receiveSharings = function receiveSharings(_ref) {
39
39
  type: RECEIVE_SHARINGS,
40
40
  data: {
41
41
  sharings: sharings.filter(function (s) {
42
- return !areAllRecipientsRevoked(s) && !hasBeenSelfRevoked(s, instanceUri);
42
+ return (// Shared drives without recipients are valid and should be kept
43
+ (isSharingADrive(s) || !areAllRecipientsRevoked(s)) && !hasBeenSelfRevoked(s, instanceUri)
44
+ );
43
45
  }),
44
46
  permissions: permissions,
45
47
  apps: apps
@@ -235,7 +237,7 @@ var byDocId = function byDocId() {
235
237
  case REVOKE_GROUP:
236
238
  case REVOKE_RECIPIENT:
237
239
  case UPDATE_SHARING:
238
- if (areAllRecipientsRevoked(action.sharing)) {
240
+ if (!isSharingADrive(action.sharing) && areAllRecipientsRevoked(action.sharing)) {
239
241
  return forgetSharing(state, action.sharing);
240
242
  }
241
243
 
@@ -367,7 +369,7 @@ var sharedPaths = function sharedPaths() {
367
369
 
368
370
  case REVOKE_GROUP:
369
371
  case REVOKE_RECIPIENT:
370
- if (areAllRecipientsRevoked(action.sharing)) {
372
+ if (!isSharingADrive(action.sharing) && areAllRecipientsRevoked(action.sharing)) {
371
373
  return state.filter(function (p) {
372
374
  return p !== action.path;
373
375
  });
@@ -667,6 +669,10 @@ export var getPermissionDocIds = function getPermissionDocIds(perm) {
667
669
  }, []);
668
670
  };
669
671
 
672
+ var isSharingADrive = function isSharingADrive(sharing) {
673
+ return sharing.attributes.drive === true;
674
+ };
675
+
670
676
  var areAllRecipientsRevoked = function areAllRecipientsRevoked(sharing) {
671
677
  return sharing.attributes.owner && sharing.attributes.members.filter(function (m) {
672
678
  return m.status !== 'revoked';
@@ -4,7 +4,7 @@ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (O
4
4
 
5
5
  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; }
6
6
 
7
- import reducer, { receiveSharings, addSharing, addSharingLink, updateSharingLink, revokeSharingLink, getRecipients, revokeRecipient, revokeSelf, matchingInstanceName, getSharingLink, hasSharedParent, hasSharedChild, getSharedDocIdsBySharings, getSharingType, getPermissionDocIds, getDocumentSharingType, getSharedParentPath, getExternalSharingIds, getRecipientsWithGroups } from './state';
7
+ import reducer, { receiveSharings, addSharing, addSharingLink, updateSharingLink, revokeSharingLink, getRecipients, revokeRecipient, revokeSelf, updateSharing, matchingInstanceName, getSharingLink, hasSharedParent, hasSharedChild, getSharedDocIdsBySharings, getSharingType, getPermissionDocIds, getDocumentSharingType, getSharedParentPath, getExternalSharingIds, getRecipientsWithGroups } from './state';
8
8
  import { SHARING_1, SHARING_2, SHARING_3, SHARING_READ_ONLY, PERM_1, PERM_2, PERM_WITHOUT_DOC, SHARING_NO_ACTIVE, SHARING_WITH_SHORTCUT, APPS } from '../__tests__/fixtures';
9
9
  describe('Sharing state', function () {
10
10
  it('should have a default state', function () {
@@ -69,6 +69,133 @@ describe('Sharing state', function () {
69
69
  });
70
70
  expect(state.sharings).toEqual([SHARING_1]);
71
71
  });
72
+ it('should not filter out shared drives without recipients', function () {
73
+ var SHARED_DRIVE_WITHOUT_RECIPIENTS = _objectSpread(_objectSpread({}, SHARING_1), {}, {
74
+ id: 'shared_drive_no_recipients',
75
+ attributes: _objectSpread(_objectSpread({}, SHARING_1.attributes), {}, {
76
+ drive: true,
77
+ members: [{
78
+ status: 'owner',
79
+ name: 'Jane Doe',
80
+ email: 'jane@doe.com',
81
+ instance: 'http://cozy.tools:8080'
82
+ }],
83
+ rules: [{
84
+ title: 'My Shared Drive',
85
+ doctype: 'io.cozy.files',
86
+ values: ['shared_drive_folder'],
87
+ add: 'sync',
88
+ update: 'sync',
89
+ remove: 'sync'
90
+ }]
91
+ })
92
+ });
93
+
94
+ var state = reducer(undefined, receiveSharings({
95
+ sharings: [SHARING_1, SHARED_DRIVE_WITHOUT_RECIPIENTS]
96
+ }));
97
+ expect(state.byDocId).toEqual({
98
+ folder_1: {
99
+ sharings: [SHARING_1.id],
100
+ permissions: []
101
+ },
102
+ shared_drive_folder: {
103
+ sharings: [SHARED_DRIVE_WITHOUT_RECIPIENTS.id],
104
+ permissions: []
105
+ }
106
+ });
107
+ expect(state.sharings).toEqual([SHARING_1, SHARED_DRIVE_WITHOUT_RECIPIENTS]);
108
+ });
109
+ it('should not forget shared drives without recipients when updating', function () {
110
+ var SHARED_DRIVE_WITHOUT_RECIPIENTS = _objectSpread(_objectSpread({}, SHARING_1), {}, {
111
+ id: 'shared_drive_no_recipients',
112
+ attributes: _objectSpread(_objectSpread({}, SHARING_1.attributes), {}, {
113
+ drive: true,
114
+ members: [{
115
+ status: 'owner',
116
+ name: 'Jane Doe',
117
+ email: 'jane@doe.com',
118
+ instance: 'http://cozy.tools:8080'
119
+ }],
120
+ rules: [{
121
+ title: 'My Shared Drive',
122
+ doctype: 'io.cozy.files',
123
+ values: ['shared_drive_folder'],
124
+ add: 'sync',
125
+ update: 'sync',
126
+ remove: 'sync'
127
+ }]
128
+ })
129
+ });
130
+
131
+ var initialState = reducer(undefined, receiveSharings({
132
+ sharings: [SHARED_DRIVE_WITHOUT_RECIPIENTS]
133
+ }));
134
+
135
+ var updatedSharing = _objectSpread(_objectSpread({}, SHARED_DRIVE_WITHOUT_RECIPIENTS), {}, {
136
+ attributes: _objectSpread(_objectSpread({}, SHARED_DRIVE_WITHOUT_RECIPIENTS.attributes), {}, {
137
+ description: 'Updated description'
138
+ })
139
+ });
140
+
141
+ var state = reducer(initialState, updateSharing(updatedSharing));
142
+ expect(state.byDocId).toEqual({
143
+ shared_drive_folder: {
144
+ sharings: [SHARED_DRIVE_WITHOUT_RECIPIENTS.id],
145
+ permissions: []
146
+ }
147
+ });
148
+ expect(state.sharings).toEqual([updatedSharing]);
149
+ });
150
+ it('should not remove shared drive path when revoking last recipient', function () {
151
+ var SHARED_DRIVE_WITH_ONE_RECIPIENT = _objectSpread(_objectSpread({}, SHARING_1), {}, {
152
+ id: 'shared_drive_one_recipient',
153
+ attributes: _objectSpread(_objectSpread({}, SHARING_1.attributes), {}, {
154
+ drive: true,
155
+ members: [{
156
+ status: 'owner',
157
+ name: 'Jane Doe',
158
+ email: 'jane@doe.com',
159
+ instance: 'http://cozy.tools:8080'
160
+ }, {
161
+ status: 'ready',
162
+ name: 'John Doe',
163
+ email: 'john@doe.com',
164
+ instance: 'http://cozy.local:8080'
165
+ }],
166
+ rules: [{
167
+ title: 'My Shared Drive',
168
+ doctype: 'io.cozy.files',
169
+ values: ['shared_drive_folder'],
170
+ add: 'sync',
171
+ update: 'sync',
172
+ remove: 'sync'
173
+ }]
174
+ })
175
+ });
176
+
177
+ var sharedDrivePath = '/shared_drive_folder';
178
+ var initialState = reducer(reducer(undefined, receiveSharings({
179
+ sharings: [SHARED_DRIVE_WITH_ONE_RECIPIENT]
180
+ })), addSharing(SHARED_DRIVE_WITH_ONE_RECIPIENT, sharedDrivePath));
181
+
182
+ var sharingAfterRevoke = _objectSpread(_objectSpread({}, SHARED_DRIVE_WITH_ONE_RECIPIENT), {}, {
183
+ attributes: _objectSpread(_objectSpread({}, SHARED_DRIVE_WITH_ONE_RECIPIENT.attributes), {}, {
184
+ members: [SHARED_DRIVE_WITH_ONE_RECIPIENT.attributes.members[0], _objectSpread(_objectSpread({}, SHARED_DRIVE_WITH_ONE_RECIPIENT.attributes.members[1]), {}, {
185
+ status: 'revoked'
186
+ })]
187
+ })
188
+ });
189
+
190
+ var state = reducer(initialState, revokeRecipient(sharingAfterRevoke, 1, sharedDrivePath));
191
+ expect(state.sharedPaths).toContain(sharedDrivePath);
192
+ expect(state.byDocId).toEqual({
193
+ shared_drive_folder: {
194
+ sharings: [SHARED_DRIVE_WITH_ONE_RECIPIENT.id],
195
+ permissions: []
196
+ }
197
+ });
198
+ });
72
199
  it('should index received permissions', function () {
73
200
  var state = reducer(undefined, receiveSharings({
74
201
  sharings: [SHARING_1, SHARING_2],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-sharing",
3
- "version": "27.0.6",
3
+ "version": "27.1.0",
4
4
  "description": "Provides sharing login for React applications.",
5
5
  "main": "dist/index.js",
6
6
  "author": "Cozy",
@@ -81,5 +81,5 @@
81
81
  "sideEffects": [
82
82
  "*.css"
83
83
  ],
84
- "gitHead": "3a0c0103636a6365a7d2a380960d7e7d773078eb"
84
+ "gitHead": "208d6d5eb0fdc5c3d22ca611e9e0ae48282b6708"
85
85
  }
package/src/state.js CHANGED
@@ -27,7 +27,10 @@ export const receiveSharings = ({
27
27
  type: RECEIVE_SHARINGS,
28
28
  data: {
29
29
  sharings: sharings.filter(
30
- s => !areAllRecipientsRevoked(s) && !hasBeenSelfRevoked(s, instanceUri)
30
+ s =>
31
+ // Shared drives without recipients are valid and should be kept
32
+ (isSharingADrive(s) || !areAllRecipientsRevoked(s)) &&
33
+ !hasBeenSelfRevoked(s, instanceUri)
31
34
  ),
32
35
  permissions,
33
36
  apps
@@ -181,7 +184,10 @@ const byDocId = (state = {}, action) => {
181
184
  case REVOKE_GROUP:
182
185
  case REVOKE_RECIPIENT:
183
186
  case UPDATE_SHARING:
184
- if (areAllRecipientsRevoked(action.sharing)) {
187
+ if (
188
+ !isSharingADrive(action.sharing) &&
189
+ areAllRecipientsRevoked(action.sharing)
190
+ ) {
185
191
  return forgetSharing(state, action.sharing)
186
192
  }
187
193
  return state
@@ -275,7 +281,10 @@ const sharedPaths = (state = [], action) => {
275
281
  return newState
276
282
  case REVOKE_GROUP:
277
283
  case REVOKE_RECIPIENT:
278
- if (areAllRecipientsRevoked(action.sharing)) {
284
+ if (
285
+ !isSharingADrive(action.sharing) &&
286
+ areAllRecipientsRevoked(action.sharing)
287
+ ) {
279
288
  return state.filter(p => p !== action.path)
280
289
  }
281
290
  return state
@@ -551,6 +560,8 @@ export const getPermissionDocIds = perm =>
551
560
  )
552
561
  .reduce((acc, val) => [...acc, ...val], [])
553
562
 
563
+ const isSharingADrive = sharing => sharing.attributes.drive === true
564
+
554
565
  const areAllRecipientsRevoked = sharing =>
555
566
  sharing.attributes.owner &&
556
567
  sharing.attributes.members.filter(m => m.status !== 'revoked').length === 1
package/src/state.spec.js CHANGED
@@ -7,6 +7,7 @@ import reducer, {
7
7
  getRecipients,
8
8
  revokeRecipient,
9
9
  revokeSelf,
10
+ updateSharing,
10
11
  matchingInstanceName,
11
12
  getSharingLink,
12
13
  hasSharedParent,
@@ -98,6 +99,168 @@ describe('Sharing state', () => {
98
99
  expect(state.sharings).toEqual([SHARING_1])
99
100
  })
100
101
 
102
+ it('should not filter out shared drives without recipients', () => {
103
+ const SHARED_DRIVE_WITHOUT_RECIPIENTS = {
104
+ ...SHARING_1,
105
+ id: 'shared_drive_no_recipients',
106
+ attributes: {
107
+ ...SHARING_1.attributes,
108
+ drive: true,
109
+ members: [
110
+ {
111
+ status: 'owner',
112
+ name: 'Jane Doe',
113
+ email: 'jane@doe.com',
114
+ instance: 'http://cozy.tools:8080'
115
+ }
116
+ ],
117
+ rules: [
118
+ {
119
+ title: 'My Shared Drive',
120
+ doctype: 'io.cozy.files',
121
+ values: ['shared_drive_folder'],
122
+ add: 'sync',
123
+ update: 'sync',
124
+ remove: 'sync'
125
+ }
126
+ ]
127
+ }
128
+ }
129
+ const state = reducer(
130
+ undefined,
131
+ receiveSharings({
132
+ sharings: [SHARING_1, SHARED_DRIVE_WITHOUT_RECIPIENTS]
133
+ })
134
+ )
135
+ expect(state.byDocId).toEqual({
136
+ folder_1: { sharings: [SHARING_1.id], permissions: [] },
137
+ shared_drive_folder: {
138
+ sharings: [SHARED_DRIVE_WITHOUT_RECIPIENTS.id],
139
+ permissions: []
140
+ }
141
+ })
142
+ expect(state.sharings).toEqual([SHARING_1, SHARED_DRIVE_WITHOUT_RECIPIENTS])
143
+ })
144
+
145
+ it('should not forget shared drives without recipients when updating', () => {
146
+ const SHARED_DRIVE_WITHOUT_RECIPIENTS = {
147
+ ...SHARING_1,
148
+ id: 'shared_drive_no_recipients',
149
+ attributes: {
150
+ ...SHARING_1.attributes,
151
+ drive: true,
152
+ members: [
153
+ {
154
+ status: 'owner',
155
+ name: 'Jane Doe',
156
+ email: 'jane@doe.com',
157
+ instance: 'http://cozy.tools:8080'
158
+ }
159
+ ],
160
+ rules: [
161
+ {
162
+ title: 'My Shared Drive',
163
+ doctype: 'io.cozy.files',
164
+ values: ['shared_drive_folder'],
165
+ add: 'sync',
166
+ update: 'sync',
167
+ remove: 'sync'
168
+ }
169
+ ]
170
+ }
171
+ }
172
+ const initialState = reducer(
173
+ undefined,
174
+ receiveSharings({
175
+ sharings: [SHARED_DRIVE_WITHOUT_RECIPIENTS]
176
+ })
177
+ )
178
+ const updatedSharing = {
179
+ ...SHARED_DRIVE_WITHOUT_RECIPIENTS,
180
+ attributes: {
181
+ ...SHARED_DRIVE_WITHOUT_RECIPIENTS.attributes,
182
+ description: 'Updated description'
183
+ }
184
+ }
185
+ const state = reducer(initialState, updateSharing(updatedSharing))
186
+ expect(state.byDocId).toEqual({
187
+ shared_drive_folder: {
188
+ sharings: [SHARED_DRIVE_WITHOUT_RECIPIENTS.id],
189
+ permissions: []
190
+ }
191
+ })
192
+ expect(state.sharings).toEqual([updatedSharing])
193
+ })
194
+
195
+ it('should not remove shared drive path when revoking last recipient', () => {
196
+ const SHARED_DRIVE_WITH_ONE_RECIPIENT = {
197
+ ...SHARING_1,
198
+ id: 'shared_drive_one_recipient',
199
+ attributes: {
200
+ ...SHARING_1.attributes,
201
+ drive: true,
202
+ members: [
203
+ {
204
+ status: 'owner',
205
+ name: 'Jane Doe',
206
+ email: 'jane@doe.com',
207
+ instance: 'http://cozy.tools:8080'
208
+ },
209
+ {
210
+ status: 'ready',
211
+ name: 'John Doe',
212
+ email: 'john@doe.com',
213
+ instance: 'http://cozy.local:8080'
214
+ }
215
+ ],
216
+ rules: [
217
+ {
218
+ title: 'My Shared Drive',
219
+ doctype: 'io.cozy.files',
220
+ values: ['shared_drive_folder'],
221
+ add: 'sync',
222
+ update: 'sync',
223
+ remove: 'sync'
224
+ }
225
+ ]
226
+ }
227
+ }
228
+ const sharedDrivePath = '/shared_drive_folder'
229
+ const initialState = reducer(
230
+ reducer(
231
+ undefined,
232
+ receiveSharings({
233
+ sharings: [SHARED_DRIVE_WITH_ONE_RECIPIENT]
234
+ })
235
+ ),
236
+ addSharing(SHARED_DRIVE_WITH_ONE_RECIPIENT, sharedDrivePath)
237
+ )
238
+ const sharingAfterRevoke = {
239
+ ...SHARED_DRIVE_WITH_ONE_RECIPIENT,
240
+ attributes: {
241
+ ...SHARED_DRIVE_WITH_ONE_RECIPIENT.attributes,
242
+ members: [
243
+ SHARED_DRIVE_WITH_ONE_RECIPIENT.attributes.members[0],
244
+ {
245
+ ...SHARED_DRIVE_WITH_ONE_RECIPIENT.attributes.members[1],
246
+ status: 'revoked'
247
+ }
248
+ ]
249
+ }
250
+ }
251
+ const state = reducer(
252
+ initialState,
253
+ revokeRecipient(sharingAfterRevoke, 1, sharedDrivePath)
254
+ )
255
+ expect(state.sharedPaths).toContain(sharedDrivePath)
256
+ expect(state.byDocId).toEqual({
257
+ shared_drive_folder: {
258
+ sharings: [SHARED_DRIVE_WITH_ONE_RECIPIENT.id],
259
+ permissions: []
260
+ }
261
+ })
262
+ })
263
+
101
264
  it('should index received permissions', () => {
102
265
  const state = reducer(
103
266
  undefined,