cozy-sharing 33.2.2 → 33.3.1
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 +12 -0
- package/dist/components/ShareRestrictionModal/helpers.js +21 -3
- package/dist/components/ShareRestrictionModal/helpers.spec.js +87 -13
- package/dist/index.js +1 -0
- package/dist/state.js +10 -0
- package/dist/state.spec.js +48 -1
- package/package.json +2 -2
- package/src/components/ShareRestrictionModal/helpers.js +23 -3
- package/src/components/ShareRestrictionModal/helpers.spec.js +69 -3
- package/src/index.jsx +1 -0
- package/src/state.js +12 -0
- package/src/state.spec.js +58 -0
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
|
+
## [33.3.1](https://github.com/cozy/cozy-libs/compare/cozy-sharing@33.3.0...cozy-sharing@33.3.1) (2026-06-10)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- **cozy-sharing:** Expire share links at the end of the selected day ([db81ed9](https://github.com/cozy/cozy-libs/commit/db81ed936a15850f278404511c1b6faec0fc7a54))
|
|
11
|
+
|
|
12
|
+
# [33.3.0](https://github.com/cozy/cozy-libs/compare/cozy-sharing@33.2.2...cozy-sharing@33.3.0) (2026-06-10)
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
- **cozy-sharing:** Expose recipients from sharing ([4751522](https://github.com/cozy/cozy-libs/commit/47515222a60ec54b0537b6ca6b5cfab3e6613638))
|
|
17
|
+
|
|
6
18
|
## [33.2.2](https://github.com/cozy/cozy-libs/compare/cozy-sharing@33.2.1...cozy-sharing@33.2.2) (2026-06-10)
|
|
7
19
|
|
|
8
20
|
### Bug Fixes
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
|
|
2
2
|
import _regeneratorRuntime from "@babel/runtime/regenerator";
|
|
3
|
+
import { endOfDay } from 'date-fns';
|
|
3
4
|
import { generateWebLink } from 'cozy-client';
|
|
4
5
|
import minilog from 'cozy-minilog';
|
|
5
6
|
import { getAppSlugFromDocumentType } from '../../helpers/link';
|
|
@@ -7,6 +8,21 @@ import { getShortcode } from '../../helpers/shortcodes';
|
|
|
7
8
|
var log = minilog('ShareRestrictionModal/helpers');
|
|
8
9
|
export var WRITE_PERMS = ['GET', 'POST', 'PUT', 'PATCH'];
|
|
9
10
|
export var READ_ONLY_PERMS = ['GET'];
|
|
11
|
+
/**
|
|
12
|
+
* A share link expires at the END of the selected day, so picking today keeps
|
|
13
|
+
* the link reachable for the rest of today and expires it the next day.
|
|
14
|
+
* Without this, the picked day defaults to its midnight (start of day), which
|
|
15
|
+
* is already in the past and makes the link unreachable as soon as it is set.
|
|
16
|
+
* @param {Date|string|null|undefined} date - The picked expiration day
|
|
17
|
+
* @returns {Date|null|undefined} - The same value normalized to end of day
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export var toExpirationDate = function toExpirationDate(date) {
|
|
21
|
+
if (!date) return date;
|
|
22
|
+
var parsed = date instanceof Date ? date : new Date(date);
|
|
23
|
+
if (parsed.toString() === 'Invalid Date') return date;
|
|
24
|
+
return endOfDay(parsed);
|
|
25
|
+
};
|
|
10
26
|
/**
|
|
11
27
|
* Create a sharing link for a file with specified options
|
|
12
28
|
* @param {object} options
|
|
@@ -250,7 +266,8 @@ export var makeTTL = function makeTTL(selectedDate) {
|
|
|
250
266
|
|
|
251
267
|
if (selectedDateConverted instanceof Date) {
|
|
252
268
|
var now = new Date();
|
|
253
|
-
var
|
|
269
|
+
var expiration = toExpirationDate(selectedDateConverted);
|
|
270
|
+
var ttl = expiration > now ? "".concat(Math.round((expiration - now) / 1000), "s") : undefined;
|
|
254
271
|
return ttl;
|
|
255
272
|
}
|
|
256
273
|
|
|
@@ -326,13 +343,14 @@ export var createPermissions = /*#__PURE__*/function () {
|
|
|
326
343
|
|
|
327
344
|
export var updatePermissions = /*#__PURE__*/function () {
|
|
328
345
|
var _ref9 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4(_ref8) {
|
|
329
|
-
var file, t, dateToggle, selectedDate, passwordToggle, password, editingRights, documentType, updateDocumentPermissions, showAlert, expiresAt, ensurePassword, verbs;
|
|
346
|
+
var file, t, dateToggle, selectedDate, passwordToggle, password, editingRights, documentType, updateDocumentPermissions, showAlert, _toExpirationDate, expiresAt, ensurePassword, verbs;
|
|
347
|
+
|
|
330
348
|
return _regeneratorRuntime.wrap(function _callee4$(_context4) {
|
|
331
349
|
while (1) switch (_context4.prev = _context4.next) {
|
|
332
350
|
case 0:
|
|
333
351
|
file = _ref8.file, t = _ref8.t, dateToggle = _ref8.dateToggle, selectedDate = _ref8.selectedDate, passwordToggle = _ref8.passwordToggle, password = _ref8.password, editingRights = _ref8.editingRights, documentType = _ref8.documentType, updateDocumentPermissions = _ref8.updateDocumentPermissions, showAlert = _ref8.showAlert;
|
|
334
352
|
_context4.prev = 1;
|
|
335
|
-
expiresAt = dateToggle ? (selectedDate === null ||
|
|
353
|
+
expiresAt = dateToggle ? ((_toExpirationDate = toExpirationDate(selectedDate)) === null || _toExpirationDate === void 0 ? void 0 : _toExpirationDate.toISOString()) || undefined : '';
|
|
336
354
|
ensurePassword = passwordToggle ? password || undefined : '';
|
|
337
355
|
verbs = editingRights === 'readOnly' ? READ_ONLY_PERMS : WRITE_PERMS;
|
|
338
356
|
return _context4.abrupt("return", updateDocumentPermissions(file, {
|
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
|
|
2
2
|
import _regeneratorRuntime from "@babel/runtime/regenerator";
|
|
3
|
-
import {
|
|
3
|
+
import { endOfDay } from 'date-fns';
|
|
4
|
+
import { makeTTL, revokePermissions, toExpirationDate, updatePermissions } from './helpers';
|
|
4
5
|
describe('ShareRestrictionModal/helpers', function () {
|
|
6
|
+
describe('toExpirationDate', function () {
|
|
7
|
+
it('returns falsy values unchanged', function () {
|
|
8
|
+
expect(toExpirationDate(null)).toBeNull();
|
|
9
|
+
expect(toExpirationDate(undefined)).toBeUndefined();
|
|
10
|
+
expect(toExpirationDate('')).toBe('');
|
|
11
|
+
});
|
|
12
|
+
it('normalizes a Date to the end of its day', function () {
|
|
13
|
+
var date = new Date(2100, 0, 1, 9, 30);
|
|
14
|
+
expect(toExpirationDate(date)).toEqual(endOfDay(date));
|
|
15
|
+
});
|
|
16
|
+
it('accepts an ISO string', function () {
|
|
17
|
+
var iso = '2100-01-01T09:30:00.000Z';
|
|
18
|
+
expect(toExpirationDate(iso)).toEqual(endOfDay(new Date(iso)));
|
|
19
|
+
});
|
|
20
|
+
});
|
|
5
21
|
describe('makeTTL', function () {
|
|
6
22
|
it('sould return undefined', function () {
|
|
7
23
|
expect(makeTTL()).toBeUndefined();
|
|
@@ -15,6 +31,18 @@ describe('ShareRestrictionModal/helpers', function () {
|
|
|
15
31
|
expect(makeTTL(new Date('2100-01-01T00:00:00.000Z'))).toBeDefined();
|
|
16
32
|
expect(makeTTL('2100-01-01T00:00:00.000Z')).toBeDefined();
|
|
17
33
|
});
|
|
34
|
+
it('keeps a link picked for today valid until the end of the day', function () {
|
|
35
|
+
// Regression: picking "today" used to resolve to midnight (already past),
|
|
36
|
+
// which dropped the TTL and the link never expired or was unreachable.
|
|
37
|
+
jest.useFakeTimers();
|
|
38
|
+
jest.setSystemTime(new Date('2100-01-01T12:00:00.000Z'));
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
expect(makeTTL(new Date())).toBeDefined();
|
|
42
|
+
} finally {
|
|
43
|
+
jest.useRealTimers();
|
|
44
|
+
}
|
|
45
|
+
});
|
|
18
46
|
});
|
|
19
47
|
describe('updatePermissions', function () {
|
|
20
48
|
var documentType = 'Files';
|
|
@@ -163,7 +191,7 @@ describe('ShareRestrictionModal/helpers', function () {
|
|
|
163
191
|
}, _callee4);
|
|
164
192
|
})));
|
|
165
193
|
it('should call updateDocumentPermissions with expected date & password if their switches are true', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee5() {
|
|
166
|
-
var updateDocumentPermissions, file;
|
|
194
|
+
var updateDocumentPermissions, file, selectedDate;
|
|
167
195
|
return _regeneratorRuntime.wrap(function _callee5$(_context5) {
|
|
168
196
|
while (1) switch (_context5.prev = _context5.next) {
|
|
169
197
|
case 0:
|
|
@@ -171,12 +199,13 @@ describe('ShareRestrictionModal/helpers', function () {
|
|
|
171
199
|
file = {
|
|
172
200
|
_id: '123'
|
|
173
201
|
};
|
|
174
|
-
|
|
202
|
+
selectedDate = new Date('2100-01-01T00:00:00.000Z');
|
|
203
|
+
_context5.next = 5;
|
|
175
204
|
return updatePermissions({
|
|
176
205
|
file: file,
|
|
177
206
|
t: jest.fn(),
|
|
178
207
|
dateToggle: true,
|
|
179
|
-
selectedDate:
|
|
208
|
+
selectedDate: selectedDate,
|
|
180
209
|
passwordToggle: true,
|
|
181
210
|
password: '1234',
|
|
182
211
|
editingRights: 'readOnly',
|
|
@@ -185,32 +214,77 @@ describe('ShareRestrictionModal/helpers', function () {
|
|
|
185
214
|
showAlert: jest.fn()
|
|
186
215
|
});
|
|
187
216
|
|
|
188
|
-
case
|
|
217
|
+
case 5:
|
|
189
218
|
expect(updateDocumentPermissions).toHaveBeenCalledWith(file, {
|
|
190
|
-
expiresAt:
|
|
219
|
+
expiresAt: endOfDay(selectedDate).toISOString(),
|
|
191
220
|
password: '1234',
|
|
192
221
|
verbs: ['GET']
|
|
193
222
|
});
|
|
194
223
|
|
|
195
|
-
case
|
|
224
|
+
case 6:
|
|
196
225
|
case "end":
|
|
197
226
|
return _context5.stop();
|
|
198
227
|
}
|
|
199
228
|
}, _callee5);
|
|
200
229
|
})));
|
|
230
|
+
it('sets expiration to the end of the selected day so picking today stays valid', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee6() {
|
|
231
|
+
var updateDocumentPermissions, file, today, expiresAt;
|
|
232
|
+
return _regeneratorRuntime.wrap(function _callee6$(_context6) {
|
|
233
|
+
while (1) switch (_context6.prev = _context6.next) {
|
|
234
|
+
case 0:
|
|
235
|
+
// Freeze time so the "expires in the future" assertion can't flake near
|
|
236
|
+
// midnight or under slow CI.
|
|
237
|
+
jest.useFakeTimers();
|
|
238
|
+
jest.setSystemTime(new Date('2100-01-01T12:00:00.000Z'));
|
|
239
|
+
_context6.prev = 2;
|
|
240
|
+
updateDocumentPermissions = jest.fn();
|
|
241
|
+
file = {
|
|
242
|
+
_id: '123'
|
|
243
|
+
};
|
|
244
|
+
today = new Date();
|
|
245
|
+
_context6.next = 8;
|
|
246
|
+
return updatePermissions({
|
|
247
|
+
file: file,
|
|
248
|
+
t: jest.fn(),
|
|
249
|
+
dateToggle: true,
|
|
250
|
+
selectedDate: today,
|
|
251
|
+
passwordToggle: false,
|
|
252
|
+
password: '',
|
|
253
|
+
editingRights: 'readOnly',
|
|
254
|
+
documentType: documentType,
|
|
255
|
+
updateDocumentPermissions: updateDocumentPermissions,
|
|
256
|
+
showAlert: jest.fn()
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
case 8:
|
|
260
|
+
expiresAt = updateDocumentPermissions.mock.calls[0][1].expiresAt;
|
|
261
|
+
expect(expiresAt).toBe(endOfDay(today).toISOString());
|
|
262
|
+
expect(new Date(expiresAt).getTime()).toBeGreaterThan(Date.now());
|
|
263
|
+
|
|
264
|
+
case 11:
|
|
265
|
+
_context6.prev = 11;
|
|
266
|
+
jest.useRealTimers();
|
|
267
|
+
return _context6.finish(11);
|
|
268
|
+
|
|
269
|
+
case 14:
|
|
270
|
+
case "end":
|
|
271
|
+
return _context6.stop();
|
|
272
|
+
}
|
|
273
|
+
}, _callee6, null, [[2,, 11, 14]]);
|
|
274
|
+
})));
|
|
201
275
|
});
|
|
202
276
|
describe('revokePermissions', function () {
|
|
203
277
|
var documentType = 'Files';
|
|
204
|
-
it('should call revokeSharingLink', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function
|
|
278
|
+
it('should call revokeSharingLink', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee7() {
|
|
205
279
|
var revokeSharingLink, file;
|
|
206
|
-
return _regeneratorRuntime.wrap(function
|
|
207
|
-
while (1) switch (
|
|
280
|
+
return _regeneratorRuntime.wrap(function _callee7$(_context7) {
|
|
281
|
+
while (1) switch (_context7.prev = _context7.next) {
|
|
208
282
|
case 0:
|
|
209
283
|
revokeSharingLink = jest.fn();
|
|
210
284
|
file = {
|
|
211
285
|
_id: '123'
|
|
212
286
|
};
|
|
213
|
-
|
|
287
|
+
_context7.next = 4;
|
|
214
288
|
return revokePermissions({
|
|
215
289
|
file: file,
|
|
216
290
|
t: jest.fn(),
|
|
@@ -224,9 +298,9 @@ describe('ShareRestrictionModal/helpers', function () {
|
|
|
224
298
|
|
|
225
299
|
case 5:
|
|
226
300
|
case "end":
|
|
227
|
-
return
|
|
301
|
+
return _context7.stop();
|
|
228
302
|
}
|
|
229
|
-
},
|
|
303
|
+
}, _callee7);
|
|
230
304
|
})));
|
|
231
305
|
});
|
|
232
306
|
});
|
package/dist/index.js
CHANGED
|
@@ -27,6 +27,7 @@ export { useSharingInfos } from './components/SharingBanner/hooks/useSharingInfo
|
|
|
27
27
|
export { ConfirmTrustedRecipientsDialog } from './ConfirmTrustedRecipientsDialog';
|
|
28
28
|
export { ShareButtonWithRecipients } from './ShareButtonWithRecipients';
|
|
29
29
|
export { DOCUMENT_TYPE } from './helpers/documentType';
|
|
30
|
+
export { getRecipientsFromSharing } from './state';
|
|
30
31
|
export { default as OpenSharingLinkButton } from './OpenSharingLinkButton';
|
|
31
32
|
export { default as OpenSharingLinkFabButton } from './OpenSharingLinkFabButton';
|
|
32
33
|
export { NativeFileSharingProvider, useNativeFileSharing } from './providers/NativeFileSharingProvider';
|
package/dist/state.js
CHANGED
|
@@ -426,7 +426,17 @@ export var getOwner = function getOwner(state, docId) {
|
|
|
426
426
|
};
|
|
427
427
|
export var getRecipients = function getRecipients(state, docId) {
|
|
428
428
|
var sharings = getDocumentSharings(state, docId);
|
|
429
|
+
return getRecipientsFromSharings(sharings, docId);
|
|
430
|
+
};
|
|
431
|
+
export var getRecipientsFromSharing = function getRecipientsFromSharing(sharing, docId) {
|
|
432
|
+
if (!sharing) {
|
|
433
|
+
return [];
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return getRecipientsFromSharings([sharing], docId);
|
|
437
|
+
};
|
|
429
438
|
|
|
439
|
+
var getRecipientsFromSharings = function getRecipientsFromSharings(sharings, docId) {
|
|
430
440
|
if (flag('sharing.show-recipient-groups')) {
|
|
431
441
|
return getRecipientsWithGroups(sharings, docId);
|
|
432
442
|
}
|
package/dist/state.spec.js
CHANGED
|
@@ -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, updateSharing, matchingInstanceName, getSharingLink, hasSharedParent, hasSharedChild, getSharedDocIdsBySharings, getSharingType, getPermissionDocIds, getDocumentSharingType, getSharedParentPath, getExternalSharingIds, getRecipientsWithGroups } from './state';
|
|
7
|
+
import reducer, { receiveSharings, addSharing, addSharingLink, updateSharingLink, revokeSharingLink, getRecipients, getRecipientsFromSharing, 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 () {
|
|
@@ -430,6 +430,53 @@ describe('Sharing state', function () {
|
|
|
430
430
|
avatarPath: '/sharings/sharing_5/recipients/1/avatar'
|
|
431
431
|
}]);
|
|
432
432
|
});
|
|
433
|
+
it('should list recipients from a shared drive sharing', function () {
|
|
434
|
+
var sharing = {
|
|
435
|
+
id: 'shared_drive_id',
|
|
436
|
+
drive: true,
|
|
437
|
+
attributes: {
|
|
438
|
+
members: [{
|
|
439
|
+
email: 'jane@doe.com',
|
|
440
|
+
instance: 'http://cozy.tools:8080',
|
|
441
|
+
name: 'Jane Doe',
|
|
442
|
+
status: 'owner'
|
|
443
|
+
}, {
|
|
444
|
+
email: 'john@doe.com',
|
|
445
|
+
instance: 'http://cozy.local:8080',
|
|
446
|
+
name: 'John Doe',
|
|
447
|
+
status: 'ready'
|
|
448
|
+
}],
|
|
449
|
+
rules: [{
|
|
450
|
+
doctype: 'io.cozy.files',
|
|
451
|
+
values: ['shared_drive_root'],
|
|
452
|
+
add: 'sync',
|
|
453
|
+
update: 'sync',
|
|
454
|
+
remove: 'sync'
|
|
455
|
+
}]
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
expect(getRecipientsFromSharing(sharing, 'child_file_id')).toEqual([{
|
|
459
|
+
email: 'jane@doe.com',
|
|
460
|
+
instance: 'http://cozy.tools:8080',
|
|
461
|
+
name: 'Jane Doe',
|
|
462
|
+
sharingId: 'shared_drive_id',
|
|
463
|
+
index: 'sharing-shared_drive_id-member-0',
|
|
464
|
+
memberIndex: 0,
|
|
465
|
+
status: 'owner',
|
|
466
|
+
type: 'two-way',
|
|
467
|
+
avatarPath: '/sharings/shared_drive_id/recipients/0/avatar'
|
|
468
|
+
}, {
|
|
469
|
+
email: 'john@doe.com',
|
|
470
|
+
instance: 'http://cozy.local:8080',
|
|
471
|
+
name: 'John Doe',
|
|
472
|
+
sharingId: 'shared_drive_id',
|
|
473
|
+
index: 'sharing-shared_drive_id-member-1',
|
|
474
|
+
memberIndex: 1,
|
|
475
|
+
status: 'ready',
|
|
476
|
+
type: 'two-way',
|
|
477
|
+
avatarPath: '/sharings/shared_drive_id/recipients/1/avatar'
|
|
478
|
+
}]);
|
|
479
|
+
});
|
|
433
480
|
});
|
|
434
481
|
});
|
|
435
482
|
describe('finding matching instance names', function () {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cozy-sharing",
|
|
3
|
-
"version": "33.
|
|
3
|
+
"version": "33.3.1",
|
|
4
4
|
"description": "Provides sharing login for React applications.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"author": "Cozy",
|
|
@@ -83,5 +83,5 @@
|
|
|
83
83
|
"sideEffects": [
|
|
84
84
|
"*.css"
|
|
85
85
|
],
|
|
86
|
-
"gitHead": "
|
|
86
|
+
"gitHead": "80c72e3af992596597b9319ee436ec8d238c9967"
|
|
87
87
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { endOfDay } from 'date-fns'
|
|
2
|
+
|
|
1
3
|
import { generateWebLink } from 'cozy-client'
|
|
2
4
|
import minilog from 'cozy-minilog'
|
|
3
5
|
|
|
@@ -9,6 +11,21 @@ const log = minilog('ShareRestrictionModal/helpers')
|
|
|
9
11
|
export const WRITE_PERMS = ['GET', 'POST', 'PUT', 'PATCH']
|
|
10
12
|
export const READ_ONLY_PERMS = ['GET']
|
|
11
13
|
|
|
14
|
+
/**
|
|
15
|
+
* A share link expires at the END of the selected day, so picking today keeps
|
|
16
|
+
* the link reachable for the rest of today and expires it the next day.
|
|
17
|
+
* Without this, the picked day defaults to its midnight (start of day), which
|
|
18
|
+
* is already in the past and makes the link unreachable as soon as it is set.
|
|
19
|
+
* @param {Date|string|null|undefined} date - The picked expiration day
|
|
20
|
+
* @returns {Date|null|undefined} - The same value normalized to end of day
|
|
21
|
+
*/
|
|
22
|
+
export const toExpirationDate = date => {
|
|
23
|
+
if (!date) return date
|
|
24
|
+
const parsed = date instanceof Date ? date : new Date(date)
|
|
25
|
+
if (parsed.toString() === 'Invalid Date') return date
|
|
26
|
+
return endOfDay(parsed)
|
|
27
|
+
}
|
|
28
|
+
|
|
12
29
|
/**
|
|
13
30
|
* Create a sharing link for a file with specified options
|
|
14
31
|
* @param {object} options
|
|
@@ -161,9 +178,10 @@ export const makeTTL = selectedDate => {
|
|
|
161
178
|
}
|
|
162
179
|
if (selectedDateConverted instanceof Date) {
|
|
163
180
|
const now = new Date()
|
|
181
|
+
const expiration = toExpirationDate(selectedDateConverted)
|
|
164
182
|
const ttl =
|
|
165
|
-
|
|
166
|
-
? `${Math.round((
|
|
183
|
+
expiration > now
|
|
184
|
+
? `${Math.round((expiration - now) / 1000)}s`
|
|
167
185
|
: undefined
|
|
168
186
|
return ttl
|
|
169
187
|
}
|
|
@@ -239,7 +257,9 @@ export const updatePermissions = async ({
|
|
|
239
257
|
showAlert
|
|
240
258
|
}) => {
|
|
241
259
|
try {
|
|
242
|
-
const expiresAt = dateToggle
|
|
260
|
+
const expiresAt = dateToggle
|
|
261
|
+
? toExpirationDate(selectedDate)?.toISOString() || undefined
|
|
262
|
+
: ''
|
|
243
263
|
const ensurePassword = passwordToggle ? password || undefined : ''
|
|
244
264
|
const verbs = editingRights === 'readOnly' ? READ_ONLY_PERMS : WRITE_PERMS
|
|
245
265
|
return updateDocumentPermissions(file, {
|
|
@@ -1,6 +1,29 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { endOfDay } from 'date-fns'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
makeTTL,
|
|
5
|
+
revokePermissions,
|
|
6
|
+
toExpirationDate,
|
|
7
|
+
updatePermissions
|
|
8
|
+
} from './helpers'
|
|
2
9
|
|
|
3
10
|
describe('ShareRestrictionModal/helpers', () => {
|
|
11
|
+
describe('toExpirationDate', () => {
|
|
12
|
+
it('returns falsy values unchanged', () => {
|
|
13
|
+
expect(toExpirationDate(null)).toBeNull()
|
|
14
|
+
expect(toExpirationDate(undefined)).toBeUndefined()
|
|
15
|
+
expect(toExpirationDate('')).toBe('')
|
|
16
|
+
})
|
|
17
|
+
it('normalizes a Date to the end of its day', () => {
|
|
18
|
+
const date = new Date(2100, 0, 1, 9, 30)
|
|
19
|
+
expect(toExpirationDate(date)).toEqual(endOfDay(date))
|
|
20
|
+
})
|
|
21
|
+
it('accepts an ISO string', () => {
|
|
22
|
+
const iso = '2100-01-01T09:30:00.000Z'
|
|
23
|
+
expect(toExpirationDate(iso)).toEqual(endOfDay(new Date(iso)))
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
|
|
4
27
|
describe('makeTTL', () => {
|
|
5
28
|
it('sould return undefined', () => {
|
|
6
29
|
expect(makeTTL()).toBeUndefined()
|
|
@@ -14,6 +37,17 @@ describe('ShareRestrictionModal/helpers', () => {
|
|
|
14
37
|
expect(makeTTL(new Date('2100-01-01T00:00:00.000Z'))).toBeDefined()
|
|
15
38
|
expect(makeTTL('2100-01-01T00:00:00.000Z')).toBeDefined()
|
|
16
39
|
})
|
|
40
|
+
it('keeps a link picked for today valid until the end of the day', () => {
|
|
41
|
+
// Regression: picking "today" used to resolve to midnight (already past),
|
|
42
|
+
// which dropped the TTL and the link never expired or was unreachable.
|
|
43
|
+
jest.useFakeTimers()
|
|
44
|
+
jest.setSystemTime(new Date('2100-01-01T12:00:00.000Z'))
|
|
45
|
+
try {
|
|
46
|
+
expect(makeTTL(new Date())).toBeDefined()
|
|
47
|
+
} finally {
|
|
48
|
+
jest.useRealTimers()
|
|
49
|
+
}
|
|
50
|
+
})
|
|
17
51
|
})
|
|
18
52
|
|
|
19
53
|
describe('updatePermissions', () => {
|
|
@@ -118,11 +152,12 @@ describe('ShareRestrictionModal/helpers', () => {
|
|
|
118
152
|
const updateDocumentPermissions = jest.fn()
|
|
119
153
|
const file = { _id: '123' }
|
|
120
154
|
|
|
155
|
+
const selectedDate = new Date('2100-01-01T00:00:00.000Z')
|
|
121
156
|
await updatePermissions({
|
|
122
157
|
file,
|
|
123
158
|
t: jest.fn(),
|
|
124
159
|
dateToggle: true,
|
|
125
|
-
selectedDate
|
|
160
|
+
selectedDate,
|
|
126
161
|
passwordToggle: true,
|
|
127
162
|
password: '1234',
|
|
128
163
|
editingRights: 'readOnly',
|
|
@@ -132,11 +167,42 @@ describe('ShareRestrictionModal/helpers', () => {
|
|
|
132
167
|
})
|
|
133
168
|
|
|
134
169
|
expect(updateDocumentPermissions).toHaveBeenCalledWith(file, {
|
|
135
|
-
expiresAt:
|
|
170
|
+
expiresAt: endOfDay(selectedDate).toISOString(),
|
|
136
171
|
password: '1234',
|
|
137
172
|
verbs: ['GET']
|
|
138
173
|
})
|
|
139
174
|
})
|
|
175
|
+
|
|
176
|
+
it('sets expiration to the end of the selected day so picking today stays valid', async () => {
|
|
177
|
+
// Freeze time so the "expires in the future" assertion can't flake near
|
|
178
|
+
// midnight or under slow CI.
|
|
179
|
+
jest.useFakeTimers()
|
|
180
|
+
jest.setSystemTime(new Date('2100-01-01T12:00:00.000Z'))
|
|
181
|
+
try {
|
|
182
|
+
const updateDocumentPermissions = jest.fn()
|
|
183
|
+
const file = { _id: '123' }
|
|
184
|
+
const today = new Date()
|
|
185
|
+
|
|
186
|
+
await updatePermissions({
|
|
187
|
+
file,
|
|
188
|
+
t: jest.fn(),
|
|
189
|
+
dateToggle: true,
|
|
190
|
+
selectedDate: today,
|
|
191
|
+
passwordToggle: false,
|
|
192
|
+
password: '',
|
|
193
|
+
editingRights: 'readOnly',
|
|
194
|
+
documentType,
|
|
195
|
+
updateDocumentPermissions,
|
|
196
|
+
showAlert: jest.fn()
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
const { expiresAt } = updateDocumentPermissions.mock.calls[0][1]
|
|
200
|
+
expect(expiresAt).toBe(endOfDay(today).toISOString())
|
|
201
|
+
expect(new Date(expiresAt).getTime()).toBeGreaterThan(Date.now())
|
|
202
|
+
} finally {
|
|
203
|
+
jest.useRealTimers()
|
|
204
|
+
}
|
|
205
|
+
})
|
|
140
206
|
})
|
|
141
207
|
|
|
142
208
|
describe('revokePermissions', () => {
|
package/src/index.jsx
CHANGED
|
@@ -31,6 +31,7 @@ export { useSharingInfos } from './components/SharingBanner/hooks/useSharingInfo
|
|
|
31
31
|
export { ConfirmTrustedRecipientsDialog } from './ConfirmTrustedRecipientsDialog'
|
|
32
32
|
export { ShareButtonWithRecipients } from './ShareButtonWithRecipients'
|
|
33
33
|
export { DOCUMENT_TYPE } from './helpers/documentType'
|
|
34
|
+
export { getRecipientsFromSharing } from './state'
|
|
34
35
|
export { default as OpenSharingLinkButton } from './OpenSharingLinkButton'
|
|
35
36
|
export { default as OpenSharingLinkFabButton } from './OpenSharingLinkFabButton'
|
|
36
37
|
export {
|
package/src/state.js
CHANGED
|
@@ -338,6 +338,18 @@ export const getOwner = (state, docId) =>
|
|
|
338
338
|
|
|
339
339
|
export const getRecipients = (state, docId) => {
|
|
340
340
|
const sharings = getDocumentSharings(state, docId)
|
|
341
|
+
return getRecipientsFromSharings(sharings, docId)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export const getRecipientsFromSharing = (sharing, docId) => {
|
|
345
|
+
if (!sharing) {
|
|
346
|
+
return []
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return getRecipientsFromSharings([sharing], docId)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const getRecipientsFromSharings = (sharings, docId) => {
|
|
341
353
|
if (flag('sharing.show-recipient-groups')) {
|
|
342
354
|
return getRecipientsWithGroups(sharings, docId)
|
|
343
355
|
}
|
package/src/state.spec.js
CHANGED
|
@@ -5,6 +5,7 @@ import reducer, {
|
|
|
5
5
|
updateSharingLink,
|
|
6
6
|
revokeSharingLink,
|
|
7
7
|
getRecipients,
|
|
8
|
+
getRecipientsFromSharing,
|
|
8
9
|
revokeRecipient,
|
|
9
10
|
revokeSelf,
|
|
10
11
|
updateSharing,
|
|
@@ -540,6 +541,63 @@ describe('Sharing state', () => {
|
|
|
540
541
|
}
|
|
541
542
|
])
|
|
542
543
|
})
|
|
544
|
+
|
|
545
|
+
it('should list recipients from a shared drive sharing', () => {
|
|
546
|
+
const sharing = {
|
|
547
|
+
id: 'shared_drive_id',
|
|
548
|
+
drive: true,
|
|
549
|
+
attributes: {
|
|
550
|
+
members: [
|
|
551
|
+
{
|
|
552
|
+
email: 'jane@doe.com',
|
|
553
|
+
instance: 'http://cozy.tools:8080',
|
|
554
|
+
name: 'Jane Doe',
|
|
555
|
+
status: 'owner'
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
email: 'john@doe.com',
|
|
559
|
+
instance: 'http://cozy.local:8080',
|
|
560
|
+
name: 'John Doe',
|
|
561
|
+
status: 'ready'
|
|
562
|
+
}
|
|
563
|
+
],
|
|
564
|
+
rules: [
|
|
565
|
+
{
|
|
566
|
+
doctype: 'io.cozy.files',
|
|
567
|
+
values: ['shared_drive_root'],
|
|
568
|
+
add: 'sync',
|
|
569
|
+
update: 'sync',
|
|
570
|
+
remove: 'sync'
|
|
571
|
+
}
|
|
572
|
+
]
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
expect(getRecipientsFromSharing(sharing, 'child_file_id')).toEqual([
|
|
577
|
+
{
|
|
578
|
+
email: 'jane@doe.com',
|
|
579
|
+
instance: 'http://cozy.tools:8080',
|
|
580
|
+
name: 'Jane Doe',
|
|
581
|
+
sharingId: 'shared_drive_id',
|
|
582
|
+
index: 'sharing-shared_drive_id-member-0',
|
|
583
|
+
memberIndex: 0,
|
|
584
|
+
status: 'owner',
|
|
585
|
+
type: 'two-way',
|
|
586
|
+
avatarPath: '/sharings/shared_drive_id/recipients/0/avatar'
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
email: 'john@doe.com',
|
|
590
|
+
instance: 'http://cozy.local:8080',
|
|
591
|
+
name: 'John Doe',
|
|
592
|
+
sharingId: 'shared_drive_id',
|
|
593
|
+
index: 'sharing-shared_drive_id-member-1',
|
|
594
|
+
memberIndex: 1,
|
|
595
|
+
status: 'ready',
|
|
596
|
+
type: 'two-way',
|
|
597
|
+
avatarPath: '/sharings/shared_drive_id/recipients/1/avatar'
|
|
598
|
+
}
|
|
599
|
+
])
|
|
600
|
+
})
|
|
543
601
|
})
|
|
544
602
|
})
|
|
545
603
|
|