cozy-sharing 38.1.2 → 38.1.3
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 +6 -0
- package/dist/components/Recipient/PermissionTypeMenu.js +79 -12
- package/dist/components/Recipient/PermissionTypeMenu.spec.js +55 -6
- package/dist/helpers/effectiveRecipients.js +2 -1
- package/package.json +3 -3
- package/src/components/Recipient/PermissionTypeMenu.jsx +17 -3
- package/src/components/Recipient/PermissionTypeMenu.spec.jsx +38 -0
- package/src/helpers/effectiveRecipients.js +2 -1
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
|
+
## [38.1.3](https://github.com/cozy/cozy-libs/compare/cozy-sharing@38.1.2...cozy-sharing@38.1.3) (2026-09-23)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- **sharing:** Downgrade member on shared parents when confirming ([3602a68](https://github.com/cozy/cozy-libs/commit/3602a6879e6a7c585f9233d2336812ee9972e612))
|
|
11
|
+
|
|
6
12
|
## [38.1.2](https://github.com/cozy/cozy-libs/compare/cozy-sharing@38.1.1...cozy-sharing@38.1.2) (2026-09-23)
|
|
7
13
|
|
|
8
14
|
### Bug Fixes
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
|
|
2
2
|
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
3
3
|
import _regeneratorRuntime from "@babel/runtime/regenerator";
|
|
4
|
+
|
|
5
|
+
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
|
|
6
|
+
|
|
7
|
+
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
8
|
+
|
|
9
|
+
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
|
|
10
|
+
|
|
4
11
|
import React, { useCallback, useRef, useState } from 'react';
|
|
5
12
|
import { useClient } from 'cozy-client';
|
|
6
13
|
import minilog from 'cozy-minilog';
|
|
@@ -53,38 +60,96 @@ var PermissionTypeMenuComponent = function PermissionTypeMenuComponent(_ref) {
|
|
|
53
60
|
}, []);
|
|
54
61
|
var applyType = useCallback( /*#__PURE__*/function () {
|
|
55
62
|
var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(newType) {
|
|
63
|
+
var _ref3,
|
|
64
|
+
_ref3$withAncestors,
|
|
65
|
+
withAncestors,
|
|
66
|
+
ancestors,
|
|
67
|
+
_iterator,
|
|
68
|
+
_step,
|
|
69
|
+
ancestor,
|
|
70
|
+
_args = arguments;
|
|
71
|
+
|
|
56
72
|
return _regeneratorRuntime.wrap(function _callee$(_context) {
|
|
57
73
|
while (1) switch (_context.prev = _context.next) {
|
|
58
74
|
case 0:
|
|
59
|
-
|
|
60
|
-
_context.
|
|
75
|
+
_ref3 = _args.length > 1 && _args[1] !== undefined ? _args[1] : {}, _ref3$withAncestors = _ref3.withAncestors, withAncestors = _ref3$withAncestors === void 0 ? false : _ref3$withAncestors;
|
|
76
|
+
_context.prev = 1;
|
|
77
|
+
|
|
78
|
+
if (!withAncestors) {
|
|
79
|
+
_context.next = 21;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Ancestors first: only the target's own update refetches its
|
|
84
|
+
// effective recipients, which must already see them downgraded.
|
|
85
|
+
ancestors = ((recipient === null || recipient === void 0 ? void 0 : recipient.sources) || []).filter(function (s) {
|
|
86
|
+
return s.kind === 'ancestor' && !s.read_only;
|
|
87
|
+
});
|
|
88
|
+
_iterator = _createForOfIteratorHelper(ancestors);
|
|
89
|
+
_context.prev = 5;
|
|
90
|
+
|
|
91
|
+
_iterator.s();
|
|
92
|
+
|
|
93
|
+
case 7:
|
|
94
|
+
if ((_step = _iterator.n()).done) {
|
|
95
|
+
_context.next = 13;
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
ancestor = _step.value;
|
|
100
|
+
_context.next = 11;
|
|
101
|
+
return updateSharingMemberType(ancestor.sharing_id, ancestor.member_index, newType);
|
|
102
|
+
|
|
103
|
+
case 11:
|
|
104
|
+
_context.next = 7;
|
|
105
|
+
break;
|
|
106
|
+
|
|
107
|
+
case 13:
|
|
108
|
+
_context.next = 18;
|
|
109
|
+
break;
|
|
110
|
+
|
|
111
|
+
case 15:
|
|
112
|
+
_context.prev = 15;
|
|
113
|
+
_context.t0 = _context["catch"](5);
|
|
114
|
+
|
|
115
|
+
_iterator.e(_context.t0);
|
|
116
|
+
|
|
117
|
+
case 18:
|
|
118
|
+
_context.prev = 18;
|
|
119
|
+
|
|
120
|
+
_iterator.f();
|
|
121
|
+
|
|
122
|
+
return _context.finish(18);
|
|
123
|
+
|
|
124
|
+
case 21:
|
|
125
|
+
_context.next = 23;
|
|
61
126
|
return updateSharingMemberType(sharingId, memberIndex, newType);
|
|
62
127
|
|
|
63
|
-
case
|
|
64
|
-
_context.next =
|
|
128
|
+
case 23:
|
|
129
|
+
_context.next = 29;
|
|
65
130
|
break;
|
|
66
131
|
|
|
67
|
-
case
|
|
68
|
-
_context.prev =
|
|
69
|
-
_context.
|
|
70
|
-
log.error('Failed to change member permission type', _context.
|
|
132
|
+
case 25:
|
|
133
|
+
_context.prev = 25;
|
|
134
|
+
_context.t1 = _context["catch"](1);
|
|
135
|
+
log.error('Failed to change member permission type', _context.t1);
|
|
71
136
|
showAlert({
|
|
72
137
|
message: t('Share.members.error.changePermission'),
|
|
73
138
|
severity: 'error',
|
|
74
139
|
variant: 'filled'
|
|
75
140
|
});
|
|
76
141
|
|
|
77
|
-
case
|
|
142
|
+
case 29:
|
|
78
143
|
case "end":
|
|
79
144
|
return _context.stop();
|
|
80
145
|
}
|
|
81
|
-
}, _callee, null, [[
|
|
146
|
+
}, _callee, null, [[1, 25], [5, 15, 18, 21]]);
|
|
82
147
|
}));
|
|
83
148
|
|
|
84
149
|
return function (_x) {
|
|
85
150
|
return _ref2.apply(this, arguments);
|
|
86
151
|
};
|
|
87
|
-
}(), [memberIndex, sharingId, showAlert, t, updateSharingMemberType]); // Downgrading to viewer on a folder inside a shared parent also reduces
|
|
152
|
+
}(), [memberIndex, recipient, sharingId, showAlert, t, updateSharingMemberType]); // Downgrading to viewer on a folder inside a shared parent also reduces
|
|
88
153
|
// the member's rights on the parent: require explicit confirmation first.
|
|
89
154
|
|
|
90
155
|
var shouldConfirmDowngrade = type === 'two-way' && Boolean(documentPath) && (hasSharedParent === null || hasSharedParent === void 0 ? void 0 : hasSharedParent(documentPath));
|
|
@@ -102,7 +167,9 @@ var PermissionTypeMenuComponent = function PermissionTypeMenuComponent(_ref) {
|
|
|
102
167
|
var handleConfirmDowngrade = useCallback(function () {
|
|
103
168
|
var newType = pendingType;
|
|
104
169
|
setPendingType(null);
|
|
105
|
-
if (newType) applyType(newType
|
|
170
|
+
if (newType) applyType(newType, {
|
|
171
|
+
withAncestors: true
|
|
172
|
+
});
|
|
106
173
|
}, [applyType, pendingType]);
|
|
107
174
|
var handleCancelDowngrade = useCallback(function () {
|
|
108
175
|
setPendingType(null);
|
|
@@ -242,13 +242,62 @@ describe('PermissionTypeMenu component', function () {
|
|
|
242
242
|
}
|
|
243
243
|
}, _callee5);
|
|
244
244
|
})));
|
|
245
|
+
it('should also downgrade the member on read-write ancestor shares when confirming', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee6() {
|
|
246
|
+
var _setup7, getByRole;
|
|
247
|
+
|
|
248
|
+
return _regeneratorRuntime.wrap(function _callee6$(_context6) {
|
|
249
|
+
while (1) switch (_context6.prev = _context6.next) {
|
|
250
|
+
case 0:
|
|
251
|
+
mockUpdateSharingMemberType.mockResolvedValue(undefined);
|
|
252
|
+
_setup7 = setup({
|
|
253
|
+
type: 'two-way',
|
|
254
|
+
document: folderDocument,
|
|
255
|
+
recipient: {
|
|
256
|
+
name: 'Bob',
|
|
257
|
+
sources: [{
|
|
258
|
+
kind: 'self',
|
|
259
|
+
sharing_id: 'sharing-123',
|
|
260
|
+
member_index: 1
|
|
261
|
+
}, {
|
|
262
|
+
kind: 'ancestor',
|
|
263
|
+
sharing_id: 'sharing-parent',
|
|
264
|
+
member_index: 2,
|
|
265
|
+
read_only: false
|
|
266
|
+
}, {
|
|
267
|
+
kind: 'ancestor',
|
|
268
|
+
sharing_id: 'sharing-grandparent',
|
|
269
|
+
member_index: 3,
|
|
270
|
+
read_only: true
|
|
271
|
+
}]
|
|
272
|
+
}
|
|
273
|
+
}), getByRole = _setup7.getByRole;
|
|
274
|
+
fireEvent.click(getByRole('button', {
|
|
275
|
+
name: 'Editor'
|
|
276
|
+
}));
|
|
277
|
+
fireEvent.click(getByRole('menuitem', {
|
|
278
|
+
name: 'Viewer'
|
|
279
|
+
}));
|
|
280
|
+
fireEvent.click(getByRole('button', {
|
|
281
|
+
name: 'Update parent'
|
|
282
|
+
}));
|
|
283
|
+
_context6.next = 7;
|
|
284
|
+
return waitFor(function () {
|
|
285
|
+
expect(mockUpdateSharingMemberType.mock.calls).toEqual([['sharing-parent', 2, 'one-way'], ['sharing-123', 1, 'one-way']]);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
case 7:
|
|
289
|
+
case "end":
|
|
290
|
+
return _context6.stop();
|
|
291
|
+
}
|
|
292
|
+
}, _callee6);
|
|
293
|
+
})));
|
|
245
294
|
it('should not call updateSharingMemberType when cancelling', function () {
|
|
246
|
-
var
|
|
295
|
+
var _setup8 = setup({
|
|
247
296
|
type: 'two-way',
|
|
248
297
|
document: folderDocument
|
|
249
298
|
}),
|
|
250
|
-
getByRole =
|
|
251
|
-
queryByRole =
|
|
299
|
+
getByRole = _setup8.getByRole,
|
|
300
|
+
queryByRole = _setup8.queryByRole;
|
|
252
301
|
|
|
253
302
|
fireEvent.click(getByRole('button', {
|
|
254
303
|
name: 'Editor'
|
|
@@ -267,12 +316,12 @@ describe('PermissionTypeMenu component', function () {
|
|
|
267
316
|
it('should update directly when the folder has no shared parent', function () {
|
|
268
317
|
mockHasSharedParent.mockReturnValue(false);
|
|
269
318
|
|
|
270
|
-
var
|
|
319
|
+
var _setup9 = setup({
|
|
271
320
|
type: 'two-way',
|
|
272
321
|
document: folderDocument
|
|
273
322
|
}),
|
|
274
|
-
getByRole =
|
|
275
|
-
queryByRole =
|
|
323
|
+
getByRole = _setup9.getByRole,
|
|
324
|
+
queryByRole = _setup9.queryByRole;
|
|
276
325
|
|
|
277
326
|
fireEvent.click(getByRole('button', {
|
|
278
327
|
name: 'Editor'
|
|
@@ -28,7 +28,8 @@ var mapToRecipient = function mapToRecipient(effectiveRecipient, source) {
|
|
|
28
28
|
sharingId: source.sharing_id,
|
|
29
29
|
memberIndex: source.member_index,
|
|
30
30
|
avatarPath: "/sharings/".concat(source.sharing_id, "/recipients/").concat(source.member_index, "/avatar"),
|
|
31
|
-
type: effectiveRecipient.read_only ? 'one-way' : 'two-way'
|
|
31
|
+
type: effectiveRecipient.read_only ? 'one-way' : 'two-way',
|
|
32
|
+
sources: effectiveRecipient.sources
|
|
32
33
|
};
|
|
33
34
|
};
|
|
34
35
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cozy-sharing",
|
|
3
|
-
"version": "38.1.
|
|
3
|
+
"version": "38.1.3",
|
|
4
4
|
"description": "Provides sharing login for React applications.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"author": "Cozy",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"cozy-intent": "^2.31.1",
|
|
60
60
|
"cozy-minilog": "^3.10.1",
|
|
61
61
|
"cozy-ui": "^140.5.0",
|
|
62
|
-
"cozy-ui-plus": "^13.0.
|
|
62
|
+
"cozy-ui-plus": "^13.0.7",
|
|
63
63
|
"jest": "30.3.0",
|
|
64
64
|
"jest-environment-jsdom": "30.3.0",
|
|
65
65
|
"react": "16.12.0",
|
|
@@ -85,5 +85,5 @@
|
|
|
85
85
|
"sideEffects": [
|
|
86
86
|
"*.css"
|
|
87
87
|
],
|
|
88
|
-
"gitHead": "
|
|
88
|
+
"gitHead": "8c012d67c3b598402f84b65bdc9f0f6f7e9d4e95"
|
|
89
89
|
}
|
|
@@ -40,8 +40,22 @@ const PermissionTypeMenuComponent = ({
|
|
|
40
40
|
}, [])
|
|
41
41
|
|
|
42
42
|
const applyType = useCallback(
|
|
43
|
-
async newType => {
|
|
43
|
+
async (newType, { withAncestors = false } = {}) => {
|
|
44
44
|
try {
|
|
45
|
+
if (withAncestors) {
|
|
46
|
+
// Ancestors first: only the target's own update refetches its
|
|
47
|
+
// effective recipients, which must already see them downgraded.
|
|
48
|
+
const ancestors = (recipient?.sources || []).filter(
|
|
49
|
+
s => s.kind === 'ancestor' && !s.read_only
|
|
50
|
+
)
|
|
51
|
+
for (const ancestor of ancestors) {
|
|
52
|
+
await updateSharingMemberType(
|
|
53
|
+
ancestor.sharing_id,
|
|
54
|
+
ancestor.member_index,
|
|
55
|
+
newType
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
45
59
|
await updateSharingMemberType(sharingId, memberIndex, newType)
|
|
46
60
|
} catch (error) {
|
|
47
61
|
log.error('Failed to change member permission type', error)
|
|
@@ -52,7 +66,7 @@ const PermissionTypeMenuComponent = ({
|
|
|
52
66
|
})
|
|
53
67
|
}
|
|
54
68
|
},
|
|
55
|
-
[memberIndex, sharingId, showAlert, t, updateSharingMemberType]
|
|
69
|
+
[memberIndex, recipient, sharingId, showAlert, t, updateSharingMemberType]
|
|
56
70
|
)
|
|
57
71
|
|
|
58
72
|
// Downgrading to viewer on a folder inside a shared parent also reduces
|
|
@@ -78,7 +92,7 @@ const PermissionTypeMenuComponent = ({
|
|
|
78
92
|
const handleConfirmDowngrade = useCallback(() => {
|
|
79
93
|
const newType = pendingType
|
|
80
94
|
setPendingType(null)
|
|
81
|
-
if (newType) applyType(newType)
|
|
95
|
+
if (newType) applyType(newType, { withAncestors: true })
|
|
82
96
|
}, [applyType, pendingType])
|
|
83
97
|
|
|
84
98
|
const handleCancelDowngrade = useCallback(() => {
|
|
@@ -179,6 +179,44 @@ describe('PermissionTypeMenu component', () => {
|
|
|
179
179
|
})
|
|
180
180
|
})
|
|
181
181
|
|
|
182
|
+
it('should also downgrade the member on read-write ancestor shares when confirming', async () => {
|
|
183
|
+
mockUpdateSharingMemberType.mockResolvedValue(undefined)
|
|
184
|
+
|
|
185
|
+
const { getByRole } = setup({
|
|
186
|
+
type: 'two-way',
|
|
187
|
+
document: folderDocument,
|
|
188
|
+
recipient: {
|
|
189
|
+
name: 'Bob',
|
|
190
|
+
sources: [
|
|
191
|
+
{ kind: 'self', sharing_id: 'sharing-123', member_index: 1 },
|
|
192
|
+
{
|
|
193
|
+
kind: 'ancestor',
|
|
194
|
+
sharing_id: 'sharing-parent',
|
|
195
|
+
member_index: 2,
|
|
196
|
+
read_only: false
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
kind: 'ancestor',
|
|
200
|
+
sharing_id: 'sharing-grandparent',
|
|
201
|
+
member_index: 3,
|
|
202
|
+
read_only: true
|
|
203
|
+
}
|
|
204
|
+
]
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
fireEvent.click(getByRole('button', { name: 'Editor' }))
|
|
209
|
+
fireEvent.click(getByRole('menuitem', { name: 'Viewer' }))
|
|
210
|
+
fireEvent.click(getByRole('button', { name: 'Update parent' }))
|
|
211
|
+
|
|
212
|
+
await waitFor(() => {
|
|
213
|
+
expect(mockUpdateSharingMemberType.mock.calls).toEqual([
|
|
214
|
+
['sharing-parent', 2, 'one-way'],
|
|
215
|
+
['sharing-123', 1, 'one-way']
|
|
216
|
+
])
|
|
217
|
+
})
|
|
218
|
+
})
|
|
219
|
+
|
|
182
220
|
it('should not call updateSharingMemberType when cancelling', () => {
|
|
183
221
|
const { getByRole, queryByRole } = setup({
|
|
184
222
|
type: 'two-way',
|
|
@@ -24,7 +24,8 @@ const mapToRecipient = (effectiveRecipient, source) => ({
|
|
|
24
24
|
sharingId: source.sharing_id,
|
|
25
25
|
memberIndex: source.member_index,
|
|
26
26
|
avatarPath: `/sharings/${source.sharing_id}/recipients/${source.member_index}/avatar`,
|
|
27
|
-
type: effectiveRecipient.read_only ? 'one-way' : 'two-way'
|
|
27
|
+
type: effectiveRecipient.read_only ? 'one-way' : 'two-way',
|
|
28
|
+
sources: effectiveRecipient.sources
|
|
28
29
|
})
|
|
29
30
|
|
|
30
31
|
export const mapEffectiveRecipients = effectiveRecipients =>
|