cozy-client 60.32.0 → 60.33.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.
@@ -3126,7 +3126,7 @@ var CozyClient = /*#__PURE__*/function () {
3126
3126
  CozyClient.hooks = CozyClient.hooks || {};
3127
3127
  CozyClient.fetchPolicies = _policies.default; //COZY_CLIENT_VERSION_PACKAGE in replaced by babel. See babel config
3128
3128
 
3129
- CozyClient.version = "60.32.0";
3129
+ CozyClient.version = "60.33.0";
3130
3130
 
3131
3131
  _microee.default.mixin(CozyClient);
3132
3132
 
@@ -24,9 +24,28 @@ var _jest$requireActual = jest.requireActual('cozy-stack-client'),
24
24
  OriginalOAuthClient = _jest$requireActual.OAuthClient,
25
25
  normalizeDoc = _jest$requireActual.normalizeDoc,
26
26
  FetchError = _jest$requireActual.FetchError;
27
+ /**
28
+ * @param {string} fileId - The target file or folder id
29
+ * @param {object} [options] - Query options
30
+ * @param {string|null} [options.driveId] - The shared drive id
31
+ * @returns {Promise<{data: object[], meta: {file_id: string}}>} The mocked response
32
+ */
33
+
27
34
 
28
35
  exports.FetchError = FetchError;
29
36
  exports.normalizeDoc = normalizeDoc;
37
+
38
+ function fetchEffectiveRecipients(fileId) {
39
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
40
+ void options;
41
+ return Promise.resolve({
42
+ data: [],
43
+ meta: {
44
+ file_id: fileId
45
+ }
46
+ });
47
+ }
48
+
30
49
  var collectionMock = {
31
50
  all: jest.fn(function () {
32
51
  return Promise.resolve();
@@ -55,7 +74,8 @@ var collectionMock = {
55
74
  }),
56
75
  findReferencedBy: jest.fn(function () {
57
76
  return Promise.resolve();
58
- })
77
+ }),
78
+ fetchEffectiveRecipients: jest.fn(fetchEffectiveRecipients)
59
79
  };
60
80
 
61
81
  var MockedStackClient = /*#__PURE__*/function (_StackClient) {
package/dist/const.js CHANGED
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.DOCTYPE_FILES_ENCRYPTION = exports.DOCTYPE_PERMISSIONS = exports.DOCTYPE_FILES = exports.REGISTRATION_ABORT = void 0;
6
+ exports.DOCTYPE_SHARING_RECIPIENTS = exports.DOCTYPE_FILES_ENCRYPTION = exports.DOCTYPE_PERMISSIONS = exports.DOCTYPE_FILES = exports.REGISTRATION_ABORT = void 0;
7
7
  var REGISTRATION_ABORT = 'REGISTRATION_ABORT';
8
8
  exports.REGISTRATION_ABORT = REGISTRATION_ABORT;
9
9
  var DOCTYPE_FILES = 'io.cozy.files';
@@ -11,4 +11,6 @@ exports.DOCTYPE_FILES = DOCTYPE_FILES;
11
11
  var DOCTYPE_PERMISSIONS = 'io.cozy.permissions';
12
12
  exports.DOCTYPE_PERMISSIONS = DOCTYPE_PERMISSIONS;
13
13
  var DOCTYPE_FILES_ENCRYPTION = 'io.cozy.files.encryption';
14
- exports.DOCTYPE_FILES_ENCRYPTION = DOCTYPE_FILES_ENCRYPTION;
14
+ exports.DOCTYPE_FILES_ENCRYPTION = DOCTYPE_FILES_ENCRYPTION;
15
+ var DOCTYPE_SHARING_RECIPIENTS = 'io.cozy.sharings.recipients';
16
+ exports.DOCTYPE_SHARING_RECIPIENTS = DOCTYPE_SHARING_RECIPIENTS;
@@ -208,53 +208,69 @@ var StackLink = /*#__PURE__*/function (_CozyLink) {
208
208
 
209
209
  case 8:
210
210
  if (!_context2.t0) {
211
- _context2.next = 10;
211
+ _context2.next = 12;
212
212
  break;
213
213
  }
214
214
 
215
+ if (!(operation.doctype === _const.DOCTYPE_SHARING_RECIPIENTS)) {
216
+ _context2.next = 11;
217
+ break;
218
+ }
219
+
220
+ throw new Error('Effective recipients cannot be fetched offline');
221
+
222
+ case 11:
215
223
  return _context2.abrupt("return", forward(operation, options));
216
224
 
217
- case 10:
218
- _context2.prev = 10;
225
+ case 12:
226
+ _context2.prev = 12;
219
227
 
220
228
  if (!operation.mutationType) {
221
- _context2.next = 15;
229
+ _context2.next = 17;
222
230
  break;
223
231
  }
224
232
 
225
- _context2.next = 14;
233
+ _context2.next = 16;
226
234
  return this.executeMutation(operation, options, result, forward);
227
235
 
228
- case 14:
236
+ case 16:
229
237
  return _context2.abrupt("return", _context2.sent);
230
238
 
231
- case 15:
232
- _context2.next = 17;
239
+ case 17:
240
+ _context2.next = 19;
233
241
  return this.executeQuery(operation);
234
242
 
235
- case 17:
243
+ case 19:
236
244
  return _context2.abrupt("return", _context2.sent);
237
245
 
238
- case 20:
239
- _context2.prev = 20;
240
- _context2.t1 = _context2["catch"](10);
246
+ case 22:
247
+ _context2.prev = 22;
248
+ _context2.t1 = _context2["catch"](12);
241
249
 
242
250
  if (!(forceLink !== 'stack' && (0, _utils.isReactNativeOfflineError)(_context2.t1))) {
243
- _context2.next = 24;
251
+ _context2.next = 28;
252
+ break;
253
+ }
254
+
255
+ if (!(operation.doctype === _const.DOCTYPE_SHARING_RECIPIENTS)) {
256
+ _context2.next = 27;
244
257
  break;
245
258
  }
246
259
 
260
+ throw _context2.t1;
261
+
262
+ case 27:
247
263
  return _context2.abrupt("return", forward(operation, options));
248
264
 
249
- case 24:
265
+ case 28:
250
266
  throw _context2.t1;
251
267
 
252
- case 25:
268
+ case 29:
253
269
  case "end":
254
270
  return _context2.stop();
255
271
  }
256
272
  }
257
- }, _callee2, this, [[10, 20]]);
273
+ }, _callee2, this, [[12, 22]]);
258
274
  }));
259
275
 
260
276
  function request(_x, _x2, _x3, _x4) {
@@ -295,128 +311,204 @@ var StackLink = /*#__PURE__*/function (_CozyLink) {
295
311
 
296
312
  }, {
297
313
  key: "executeQuery",
298
- value: function executeQuery(query) {
299
- var doctype = query.doctype,
300
- selector = query.selector,
301
- id = query.id,
302
- ids = query.ids,
303
- referenced = query.referenced,
304
- sharingId = query.sharingId,
305
- rawOptions = (0, _objectWithoutProperties2.default)(query, ["doctype", "selector", "id", "ids", "referenced", "sharingId"]);
306
-
307
- var options = _objectSpread(_objectSpread({}, rawOptions), {}, {
308
- driveId: undefined
309
- });
310
-
311
- if (!doctype) {
312
- _logger.default.warn('Bad query', query);
313
-
314
- throw new Error('No doctype found in a query definition');
315
- }
314
+ value: function () {
315
+ var _executeQuery = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee4(query) {
316
+ var doctype, selector, id, ids, referenced, sharingId, rawOptions, options, _selector$drive_id, fileId, driveId, response, collection;
316
317
 
317
- if (doctype === _const.DOCTYPE_FILES && sharingId) {
318
- options = _objectSpread(_objectSpread({}, options), {}, {
319
- driveId: sharingId
320
- });
321
- }
318
+ return _regenerator.default.wrap(function _callee4$(_context4) {
319
+ while (1) {
320
+ switch (_context4.prev = _context4.next) {
321
+ case 0:
322
+ doctype = query.doctype, selector = query.selector, id = query.id, ids = query.ids, referenced = query.referenced, sharingId = query.sharingId, rawOptions = (0, _objectWithoutProperties2.default)(query, ["doctype", "selector", "id", "ids", "referenced", "sharingId"]);
323
+ options = _objectSpread(_objectSpread({}, rawOptions), {}, {
324
+ driveId: undefined
325
+ });
322
326
 
323
- var collection = this.stackClient.collection(doctype, options);
327
+ if (doctype) {
328
+ _context4.next = 5;
329
+ break;
330
+ }
324
331
 
325
- if (id) {
326
- return collection.get(id, query);
327
- }
332
+ _logger.default.warn('Bad query', query);
328
333
 
329
- if (ids) {
330
- return collection.getAll(ids);
331
- }
334
+ throw new Error('No doctype found in a query definition');
332
335
 
333
- if (referenced) {
334
- return collection.findReferencedBy(referenced, options);
335
- }
336
+ case 5:
337
+ if (doctype === _const.DOCTYPE_FILES && sharingId) {
338
+ options = _objectSpread(_objectSpread({}, options), {}, {
339
+ driveId: sharingId
340
+ });
341
+ }
342
+
343
+ if (!(doctype === _const.DOCTYPE_SHARING_RECIPIENTS)) {
344
+ _context4.next = 15;
345
+ break;
346
+ }
347
+
348
+ fileId = selector === null || selector === void 0 ? void 0 : selector.file_id;
349
+ driveId = (_selector$drive_id = selector === null || selector === void 0 ? void 0 : selector.drive_id) !== null && _selector$drive_id !== void 0 ? _selector$drive_id : null;
350
+
351
+ if (fileId) {
352
+ _context4.next = 11;
353
+ break;
354
+ }
355
+
356
+ throw new Error("".concat(_const.DOCTYPE_SHARING_RECIPIENTS, " must be queried with effectiveRecipients"));
336
357
 
337
- if (hasFindOptions(query)) {
338
- return collection.find(selector, options);
339
- } else {
340
- return collection.all(options);
358
+ case 11:
359
+ _context4.next = 13;
360
+ return this.stackClient.collection('io.cozy.sharings').fetchEffectiveRecipients(fileId, {
361
+ driveId: driveId
362
+ });
363
+
364
+ case 13:
365
+ response = _context4.sent;
366
+ return _context4.abrupt("return", _objectSpread(_objectSpread({}, response), {}, {
367
+ data: response.data.map(function (recipient) {
368
+ var recipientId = recipient.id,
369
+ attributes = (0, _objectWithoutProperties2.default)(recipient, ["id"]); // Recipient attributes vary by target, so IDs include the query scope.
370
+
371
+ var scope = driveId ? "drive/".concat(encodeURIComponent(driveId)) : 'personal';
372
+ var id = "".concat(scope, "/file/").concat(encodeURIComponent(fileId), "/recipient/").concat(encodeURIComponent(recipientId));
373
+ return _objectSpread({
374
+ id: id,
375
+ _id: id,
376
+ _type: _const.DOCTYPE_SHARING_RECIPIENTS,
377
+ recipient_id: recipientId,
378
+ file_id: fileId,
379
+ drive_id: driveId
380
+ }, attributes);
381
+ })
382
+ }));
383
+
384
+ case 15:
385
+ collection = this.stackClient.collection(doctype, options);
386
+
387
+ if (!id) {
388
+ _context4.next = 18;
389
+ break;
390
+ }
391
+
392
+ return _context4.abrupt("return", collection.get(id, query));
393
+
394
+ case 18:
395
+ if (!ids) {
396
+ _context4.next = 20;
397
+ break;
398
+ }
399
+
400
+ return _context4.abrupt("return", collection.getAll(ids));
401
+
402
+ case 20:
403
+ if (!referenced) {
404
+ _context4.next = 22;
405
+ break;
406
+ }
407
+
408
+ return _context4.abrupt("return", collection.findReferencedBy(referenced, options));
409
+
410
+ case 22:
411
+ if (!hasFindOptions(query)) {
412
+ _context4.next = 26;
413
+ break;
414
+ }
415
+
416
+ return _context4.abrupt("return", collection.find(selector, options));
417
+
418
+ case 26:
419
+ return _context4.abrupt("return", collection.all(options));
420
+
421
+ case 27:
422
+ case "end":
423
+ return _context4.stop();
424
+ }
425
+ }
426
+ }, _callee4, this);
427
+ }));
428
+
429
+ function executeQuery(_x7) {
430
+ return _executeQuery.apply(this, arguments);
341
431
  }
342
- }
432
+
433
+ return executeQuery;
434
+ }()
343
435
  }, {
344
436
  key: "executeMutation",
345
437
  value: function () {
346
- var _executeMutation = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee4(mutation, options, result, forward) {
438
+ var _executeMutation = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee5(mutation, options, result, forward) {
347
439
  var mutationType, doc, docs, props, updateAllResp;
348
- return _regenerator.default.wrap(function _callee4$(_context4) {
440
+ return _regenerator.default.wrap(function _callee5$(_context5) {
349
441
  while (1) {
350
- switch (_context4.prev = _context4.next) {
442
+ switch (_context5.prev = _context5.next) {
351
443
  case 0:
352
444
  mutationType = mutation.mutationType, doc = mutation.document, docs = mutation.documents, props = (0, _objectWithoutProperties2.default)(mutation, ["mutationType", "document", "documents"]);
353
- _context4.t0 = mutationType;
354
- _context4.next = _context4.t0 === _dsl.MutationTypes.CREATE_DOCUMENT ? 4 : _context4.t0 === _dsl.MutationTypes.UPDATE_DOCUMENTS ? 5 : _context4.t0 === _dsl.MutationTypes.UPDATE_DOCUMENT ? 9 : _context4.t0 === _dsl.MutationTypes.DELETE_DOCUMENT ? 10 : _context4.t0 === _dsl.MutationTypes.DELETE_DOCUMENTS ? 11 : _context4.t0 === _dsl.MutationTypes.ADD_REFERENCES_TO ? 12 : _context4.t0 === _dsl.MutationTypes.REMOVE_REFERENCES_TO ? 13 : _context4.t0 === _dsl.MutationTypes.ADD_REFERENCED_BY ? 14 : _context4.t0 === _dsl.MutationTypes.REMOVE_REFERENCED_BY ? 19 : _context4.t0 === _dsl.MutationTypes.UPLOAD_FILE ? 24 : 25;
445
+ _context5.t0 = mutationType;
446
+ _context5.next = _context5.t0 === _dsl.MutationTypes.CREATE_DOCUMENT ? 4 : _context5.t0 === _dsl.MutationTypes.UPDATE_DOCUMENTS ? 5 : _context5.t0 === _dsl.MutationTypes.UPDATE_DOCUMENT ? 9 : _context5.t0 === _dsl.MutationTypes.DELETE_DOCUMENT ? 10 : _context5.t0 === _dsl.MutationTypes.DELETE_DOCUMENTS ? 11 : _context5.t0 === _dsl.MutationTypes.ADD_REFERENCES_TO ? 12 : _context5.t0 === _dsl.MutationTypes.REMOVE_REFERENCES_TO ? 13 : _context5.t0 === _dsl.MutationTypes.ADD_REFERENCED_BY ? 14 : _context5.t0 === _dsl.MutationTypes.REMOVE_REFERENCED_BY ? 19 : _context5.t0 === _dsl.MutationTypes.UPLOAD_FILE ? 24 : 25;
355
447
  break;
356
448
 
357
449
  case 4:
358
- return _context4.abrupt("return", this.stackClient.collection(doc._type).create(doc));
450
+ return _context5.abrupt("return", this.stackClient.collection(doc._type).create(doc));
359
451
 
360
452
  case 5:
361
- _context4.next = 7;
453
+ _context5.next = 7;
362
454
  return this.stackClient.collection(docs[0]._type).updateAll(docs);
363
455
 
364
456
  case 7:
365
- updateAllResp = _context4.sent;
366
- return _context4.abrupt("return", transformBulkDocsResponse(updateAllResp, docs));
457
+ updateAllResp = _context5.sent;
458
+ return _context5.abrupt("return", transformBulkDocsResponse(updateAllResp, docs));
367
459
 
368
460
  case 9:
369
- return _context4.abrupt("return", this.stackClient.collection(doc._type).update(doc));
461
+ return _context5.abrupt("return", this.stackClient.collection(doc._type).update(doc));
370
462
 
371
463
  case 10:
372
- return _context4.abrupt("return", this.stackClient.collection(doc._type).destroy(doc));
464
+ return _context5.abrupt("return", this.stackClient.collection(doc._type).destroy(doc));
373
465
 
374
466
  case 11:
375
- return _context4.abrupt("return", this.stackClient.collection(doc._type).destroyAll(docs));
467
+ return _context5.abrupt("return", this.stackClient.collection(doc._type).destroyAll(docs));
376
468
 
377
469
  case 12:
378
- return _context4.abrupt("return", this.stackClient.collection(props.referencedDocuments[0]._type).addReferencesTo(doc, props.referencedDocuments));
470
+ return _context5.abrupt("return", this.stackClient.collection(props.referencedDocuments[0]._type).addReferencesTo(doc, props.referencedDocuments));
379
471
 
380
472
  case 13:
381
- return _context4.abrupt("return", this.stackClient.collection(props.referencedDocuments[0]._type).removeReferencesTo(doc, props.referencedDocuments));
473
+ return _context5.abrupt("return", this.stackClient.collection(props.referencedDocuments[0]._type).removeReferencesTo(doc, props.referencedDocuments));
382
474
 
383
475
  case 14:
384
476
  if (!(doc._type === _const.DOCTYPE_FILES)) {
385
- _context4.next = 18;
477
+ _context5.next = 18;
386
478
  break;
387
479
  }
388
480
 
389
- return _context4.abrupt("return", this.stackClient.collection(_const.DOCTYPE_FILES).addReferencedBy(doc, props.referencedDocuments));
481
+ return _context5.abrupt("return", this.stackClient.collection(_const.DOCTYPE_FILES).addReferencedBy(doc, props.referencedDocuments));
390
482
 
391
483
  case 18:
392
484
  throw new Error('The document type should be io.cozy.files');
393
485
 
394
486
  case 19:
395
487
  if (!(doc._type === _const.DOCTYPE_FILES)) {
396
- _context4.next = 23;
488
+ _context5.next = 23;
397
489
  break;
398
490
  }
399
491
 
400
- return _context4.abrupt("return", this.stackClient.collection(_const.DOCTYPE_FILES).removeReferencedBy(doc, props.referencedDocuments));
492
+ return _context5.abrupt("return", this.stackClient.collection(_const.DOCTYPE_FILES).removeReferencedBy(doc, props.referencedDocuments));
401
493
 
402
494
  case 23:
403
495
  throw new Error('The document type should be io.cozy.files');
404
496
 
405
497
  case 24:
406
- return _context4.abrupt("return", this.stackClient.collection(_const.DOCTYPE_FILES).upload(props.file, props.dirPath));
498
+ return _context5.abrupt("return", this.stackClient.collection(_const.DOCTYPE_FILES).upload(props.file, props.dirPath));
407
499
 
408
500
  case 25:
409
- return _context4.abrupt("return", forward(mutation, options, result));
501
+ return _context5.abrupt("return", forward(mutation, options, result));
410
502
 
411
503
  case 26:
412
504
  case "end":
413
- return _context4.stop();
505
+ return _context5.stop();
414
506
  }
415
507
  }
416
- }, _callee4, this);
508
+ }, _callee5, this);
417
509
  }));
418
510
 
419
- function executeMutation(_x7, _x8, _x9, _x10) {
511
+ function executeMutation(_x8, _x9, _x10, _x11) {
420
512
  return _executeMutation.apply(this, arguments);
421
513
  }
422
514
 
@@ -134,6 +134,96 @@ describe('StackLink', () => {
134
134
  expect.anything()
135
135
  )
136
136
  })
137
+
138
+ it('should fetch and normalize effective recipients for a classic sharing', async () => {
139
+ const query = Q('io.cozy.sharings.recipients').effectiveRecipients(
140
+ 'file/1'
141
+ )
142
+ stackClient.collection().fetchEffectiveRecipients.mockResolvedValue({
143
+ data: [
144
+ {
145
+ id: 'https://alice.example',
146
+ name: 'Alice',
147
+ read_only: false
148
+ }
149
+ ],
150
+ meta: { file_id: 'file/1' }
151
+ })
152
+
153
+ const response = await link.request(query)
154
+
155
+ expect(stackClient.collection).toHaveBeenCalledWith('io.cozy.sharings')
156
+ expect(
157
+ stackClient.collection().fetchEffectiveRecipients
158
+ ).toHaveBeenCalledWith('file/1', { driveId: null })
159
+ expect(response).toEqual({
160
+ data: [
161
+ {
162
+ id: 'personal/file/file%2F1/recipient/https%3A%2F%2Falice.example',
163
+ _id: 'personal/file/file%2F1/recipient/https%3A%2F%2Falice.example',
164
+ _type: 'io.cozy.sharings.recipients',
165
+ recipient_id: 'https://alice.example',
166
+ file_id: 'file/1',
167
+ drive_id: null,
168
+ name: 'Alice',
169
+ read_only: false
170
+ }
171
+ ],
172
+ meta: { file_id: 'file/1' }
173
+ })
174
+ })
175
+
176
+ it('should fetch effective recipients in a shared drive scope', async () => {
177
+ const query = Q('io.cozy.sharings.recipients').effectiveRecipients(
178
+ 'file-1',
179
+ { driveId: 'drive/1' }
180
+ )
181
+ stackClient.collection().fetchEffectiveRecipients.mockResolvedValue({
182
+ data: [{ id: 'alice@example.test', name: 'Alice' }]
183
+ })
184
+
185
+ const response = await link.request(query)
186
+
187
+ expect(
188
+ stackClient.collection().fetchEffectiveRecipients
189
+ ).toHaveBeenCalledWith('file-1', { driveId: 'drive/1' })
190
+ expect(response.data[0]).toMatchObject({
191
+ id: 'drive/drive%2F1/file/file-1/recipient/alice%40example.test',
192
+ recipient_id: 'alice@example.test',
193
+ file_id: 'file-1',
194
+ drive_id: 'drive/1'
195
+ })
196
+ })
197
+
198
+ it('should scope virtual document ids by target', async () => {
199
+ stackClient.collection().fetchEffectiveRecipients.mockResolvedValue({
200
+ data: [{ id: 'sharing-1:0', name: 'Alice' }]
201
+ })
202
+
203
+ const fileResponse = await link.request(
204
+ Q('io.cozy.sharings.recipients').effectiveRecipients('file-1')
205
+ )
206
+ const childResponse = await link.request(
207
+ Q('io.cozy.sharings.recipients').effectiveRecipients('child-1')
208
+ )
209
+
210
+ expect(fileResponse.data[0].recipient_id).toBe('sharing-1:0')
211
+ expect(childResponse.data[0].recipient_id).toBe('sharing-1:0')
212
+ expect(fileResponse.data[0].id).not.toBe(childResponse.data[0].id)
213
+ })
214
+
215
+ it('should not forward effective recipients queries when offline', async () => {
216
+ const query = Q('io.cozy.sharings.recipients').effectiveRecipients(
217
+ 'file-1'
218
+ )
219
+ const forward = jest.fn()
220
+ link.isOnline = jest.fn().mockResolvedValue(false)
221
+
222
+ await expect(link.request(query, {}, null, forward)).rejects.toThrow(
223
+ 'Effective recipients cannot be fetched offline'
224
+ )
225
+ expect(forward).not.toHaveBeenCalled()
226
+ })
137
227
  })
138
228
 
139
229
  describe('reset', () => {
@@ -15,6 +15,8 @@ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/creat
15
15
 
16
16
  var _findKey = _interopRequireDefault(require("lodash/findKey"));
17
17
 
18
+ var _const = require("../const");
19
+
18
20
  var _logger = _interopRequireDefault(require("../logger"));
19
21
 
20
22
  function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
@@ -460,6 +462,38 @@ var QueryDefinition = /*#__PURE__*/function () {
460
462
  sharingId: id
461
463
  }));
462
464
  }
465
+ /**
466
+ * Query all recipients who can access a file or folder, either through a
467
+ * direct sharing on the target or through a sharing inherited from an ancestor.
468
+ *
469
+ * @param {string} fileId - The target file or folder id
470
+ * @param {{driveId?: string|null}} [options] - Query options; driveId scopes a shared drive and defaults to null
471
+ * @returns {QueryDefinition} The QueryDefinition object.
472
+ */
473
+
474
+ }, {
475
+ key: "effectiveRecipients",
476
+ value: function effectiveRecipients(fileId) {
477
+ var _options$driveId;
478
+
479
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
480
+
481
+ if (this.doctype !== _const.DOCTYPE_SHARING_RECIPIENTS) {
482
+ throw new Error("effectiveRecipients must be called on ".concat(_const.DOCTYPE_SHARING_RECIPIENTS));
483
+ }
484
+
485
+ if (!fileId) {
486
+ throw new Error('effectiveRecipients called with undefined fileId');
487
+ }
488
+
489
+ var driveId = (_options$driveId = options.driveId) !== null && _options$driveId !== void 0 ? _options$driveId : null;
490
+ return new QueryDefinition(_objectSpread(_objectSpread({}, this.toDefinition()), {}, {
491
+ selector: {
492
+ file_id: fileId,
493
+ drive_id: driveId
494
+ }
495
+ }));
496
+ }
463
497
  }, {
464
498
  key: "toDefinition",
465
499
  value: function toDefinition() {
@@ -44,6 +44,38 @@ describe('QueryDefinition', () => {
44
44
  })
45
45
  })
46
46
 
47
+ it('should build an effective recipients query for a classic sharing', () => {
48
+ const query = Q('io.cozy.sharings.recipients').effectiveRecipients('file-1')
49
+
50
+ expect(query).toMatchObject({
51
+ doctype: 'io.cozy.sharings.recipients',
52
+ selector: { file_id: 'file-1', drive_id: null }
53
+ })
54
+ })
55
+
56
+ it('should build an effective recipients query for a shared drive', () => {
57
+ const query = Q('io.cozy.sharings.recipients').effectiveRecipients(
58
+ 'file-1',
59
+ { driveId: 'drive-1' }
60
+ )
61
+
62
+ expect(query).toMatchObject({
63
+ selector: { file_id: 'file-1', drive_id: 'drive-1' }
64
+ })
65
+ })
66
+
67
+ it('should reject effective recipients queries on another doctype', () => {
68
+ expect(() => Q('io.cozy.sharings').effectiveRecipients('file-1')).toThrow(
69
+ 'effectiveRecipients must be called on io.cozy.sharings.recipients'
70
+ )
71
+ })
72
+
73
+ it('should reject effective recipients queries without a file id', () => {
74
+ expect(() =>
75
+ Q('io.cozy.sharings.recipients').effectiveRecipients(null)
76
+ ).toThrow('effectiveRecipients called with undefined fileId')
77
+ })
78
+
47
79
  it('paginates only one way', () => {
48
80
  const q = Q('io.cozy.files')
49
81
  const withSkip = q.offset(2)
package/dist/storage.js CHANGED
@@ -13,6 +13,8 @@ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/
13
13
 
14
14
  var _CozyClient = _interopRequireDefault(require("./CozyClient"));
15
15
 
16
+ var _const = require("./const");
17
+
16
18
  var _dsl = require("./queries/dsl");
17
19
 
18
20
  function _createForOfIteratorHelper(o, allowArrayLike) { var it; if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { 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 = o[Symbol.iterator](); }, 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; } } }; }
@@ -46,7 +48,7 @@ var shouldDocumentBePersisted = function shouldDocumentBePersisted(document) {
46
48
 
47
49
  var shouldEnforce = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
48
50
 
49
- if (!document || document.cozyLocalOnly) {
51
+ if (!document || document.cozyLocalOnly || document._type === _const.DOCTYPE_SHARING_RECIPIENTS) {
50
52
  return false;
51
53
  }
52
54
 
@@ -11,6 +11,15 @@ describe('shouldDocumentBePersisted', () => {
11
11
  expect(shouldDocumentBePersisted(doc)).toBe(false)
12
12
  })
13
13
 
14
+ it('should not persist effective recipients', () => {
15
+ const doc = {
16
+ ...BASE_DOC,
17
+ _type: 'io.cozy.sharings.recipients'
18
+ }
19
+
20
+ expect(shouldDocumentBePersisted(doc)).toBe(false)
21
+ })
22
+
14
23
  it('should not persist a doc with rev', () => {
15
24
  const doc1 = { ...BASE_DOC, _rev: '123' }
16
25
  expect(shouldDocumentBePersisted(doc1)).toBe(false)
@@ -49,6 +49,8 @@ var _helpers = require("./helpers");
49
49
 
50
50
  var _dsl = require("../queries/dsl");
51
51
 
52
+ var _const = require("../const");
53
+
52
54
  var _logger = _interopRequireDefault(require("../logger"));
53
55
 
54
56
  var _stateHelpers = require("./stateHelpers");
@@ -264,6 +266,21 @@ var query = function query(performanceApi) {
264
266
 
265
267
  var count = response.meta && response.meta.count ? response.meta.count : response.data.length;
266
268
 
269
+ if (state.definition.doctype === _const.DOCTYPE_SHARING_RECIPIENTS) {
270
+ performanceApi.measure({
271
+ markName: markName,
272
+ measureName: "".concat(markName, " with full snapshot"),
273
+ category: 'CozyClientStore'
274
+ });
275
+ return _objectSpread(_objectSpread(_objectSpread({}, state), common), {}, {
276
+ bookmark: null,
277
+ hasMore: false,
278
+ count: count,
279
+ fetchedPagesCount: 1,
280
+ data: response.data.map(_helpers.properId)
281
+ });
282
+ }
283
+
267
284
  if (action.backgroundFetching) {
268
285
  performanceApi.measure({
269
286
  markName: markName,
@@ -142,6 +142,36 @@ describe('queries reducer', () => {
142
142
 
143
143
  expect(state.a.data).toEqual([TODO_1._id])
144
144
  })
145
+
146
+ it('should replace the previous result for a full snapshot query', () => {
147
+ const recipient1 = {
148
+ _id: 'file-1/recipient-1',
149
+ _type: 'io.cozy.sharings.recipients'
150
+ }
151
+ const recipient2 = {
152
+ _id: 'file-1/recipient-2',
153
+ _type: 'io.cozy.sharings.recipients'
154
+ }
155
+ const queryDefinition = Q(
156
+ 'io.cozy.sharings.recipients'
157
+ ).effectiveRecipients('file-1')
158
+ applyAction(initQuery('recipients', queryDefinition))
159
+
160
+ applyAction(
161
+ receiveQueryResult('recipients', {
162
+ data: [recipient1, recipient2]
163
+ })
164
+ )
165
+ applyAction(
166
+ receiveQueryResult('recipients', {
167
+ data: [recipient1]
168
+ })
169
+ )
170
+
171
+ expect(state.recipients.data).toEqual([recipient1._id])
172
+ expect(state.recipients.count).toBe(1)
173
+ expect(state.recipients.hasMore).toBe(false)
174
+ })
145
175
  })
146
176
 
147
177
  describe('RECEIVE_QUERY_ERROR', () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-client",
3
- "version": "60.32.0",
3
+ "version": "60.33.0",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.node.js",
6
6
  "browser": "dist/index.js",
@@ -19,7 +19,7 @@
19
19
  "@types/jest": "^26.0.20",
20
20
  "@types/lodash": "^4.14.170",
21
21
  "btoa": "^1.2.1",
22
- "cozy-stack-client": "^60.28.0",
22
+ "cozy-stack-client": "^60.33.0",
23
23
  "date-fns": "^2.30.0",
24
24
  "fast-deep-equal": "^3.1.3",
25
25
  "json-stable-stringify": "^1.0.1",
@@ -74,5 +74,5 @@
74
74
  "react-native-ios11-devicecheck": "^0.0.3"
75
75
  },
76
76
  "sideEffects": false,
77
- "gitHead": "afc1cbe7a956b8abbb875e5436ed031f05207a24"
77
+ "gitHead": "a93abce8ac4c9f8538818dc48593dde32e774720"
78
78
  }
@@ -13,6 +13,14 @@ export class OAuthClient extends OAuthClient_base {
13
13
  updateAll: jest.Mock<Promise<void>, []>;
14
14
  destroy: jest.Mock<Promise<void>, []>;
15
15
  findReferencedBy: jest.Mock<Promise<void>, []>;
16
+ fetchEffectiveRecipients: jest.Mock<Promise<{
17
+ data: object[];
18
+ meta: {
19
+ file_id: string;
20
+ };
21
+ }>, [fileId: string, options?: {
22
+ driveId?: string | null;
23
+ }]>;
16
24
  }, []>;
17
25
  }
18
26
  export default MockedStackClient;
@@ -31,6 +39,14 @@ declare class MockedStackClient extends MockedStackClient_base {
31
39
  updateAll: jest.Mock<Promise<void>, []>;
32
40
  destroy: jest.Mock<Promise<void>, []>;
33
41
  findReferencedBy: jest.Mock<Promise<void>, []>;
42
+ fetchEffectiveRecipients: jest.Mock<Promise<{
43
+ data: object[];
44
+ meta: {
45
+ file_id: string;
46
+ };
47
+ }>, [fileId: string, options?: {
48
+ driveId?: string | null;
49
+ }]>;
34
50
  }, []>;
35
51
  }
36
52
  export const normalizeDoc: any;
package/types/const.d.ts CHANGED
@@ -2,3 +2,4 @@ export const REGISTRATION_ABORT: "REGISTRATION_ABORT";
2
2
  export const DOCTYPE_FILES: "io.cozy.files";
3
3
  export const DOCTYPE_PERMISSIONS: "io.cozy.permissions";
4
4
  export const DOCTYPE_FILES_ENCRYPTION: "io.cozy.files.encryption";
5
+ export const DOCTYPE_SHARING_RECIPIENTS: "io.cozy.sharings.recipients";
@@ -301,6 +301,17 @@ export class QueryDefinition {
301
301
  * @returns {QueryDefinition} The QueryDefinition object.
302
302
  */
303
303
  sharingById(id: string): QueryDefinition;
304
+ /**
305
+ * Query all recipients who can access a file or folder, either through a
306
+ * direct sharing on the target or through a sharing inherited from an ancestor.
307
+ *
308
+ * @param {string} fileId - The target file or folder id
309
+ * @param {{driveId?: string|null}} [options] - Query options; driveId scopes a shared drive and defaults to null
310
+ * @returns {QueryDefinition} The QueryDefinition object.
311
+ */
312
+ effectiveRecipients(fileId: string, options?: {
313
+ driveId?: string | null;
314
+ }): QueryDefinition;
304
315
  toDefinition(): {
305
316
  doctype: string;
306
317
  id: string;