cozy-client 60.45.0 → 61.0.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.
@@ -189,21 +189,23 @@ exports.isEncryptedFileOrFolder = isEncryptedFileOrFolder;
189
189
 
190
190
  var isOnlyOfficeFile = function isOnlyOfficeFile(file) {
191
191
  return isFile(file) && !isNote(file) && (file.class === 'text' || file.class === 'spreadsheet' || file.class === 'slide');
192
- };
192
+ }; // text/* types OnlyOffice can edit, from https://github.com/ONLYOFFICE/document-formats
193
+
194
+
195
+ exports.isOnlyOfficeFile = isOnlyOfficeFile;
196
+ var ONLY_OFFICE_TEXT_MIME_TYPES = ['text/rtf', 'text/csv', 'text/x-csv', 'text/x-comma-separated-values', 'text/tab-separated-values', 'text/fb2+xml'];
193
197
  /**
194
198
  * Whether the file should be opened by only office
195
199
  * We want to be consistent with the stack so we check the class attributes
196
- * But we want to exclude .txt and .md because the CozyUI Viewer can already show them
200
+ * But we want to exclude .txt, .md and other plain text files not supported
201
+ * by only office (.vcf, .ics...) because the CozyUI Viewer can already show them
197
202
  *
198
203
  * @param {import("../types").IOCozyFile} file - io.cozy.file document
199
204
  * @returns {boolean}
200
205
  */
201
206
 
202
-
203
- exports.isOnlyOfficeFile = isOnlyOfficeFile;
204
-
205
207
  var shouldBeOpenedByOnlyOffice = function shouldBeOpenedByOnlyOffice(file) {
206
- return isOnlyOfficeFile(file) && !file.name.endsWith('.txt') && !file.name.endsWith('.md');
208
+ return isOnlyOfficeFile(file) && !file.name.endsWith('.txt') && !file.name.endsWith('.md') && (!isPlainText(file.mime, file.name) || ONLY_OFFICE_TEXT_MIME_TYPES.includes(file.mime));
207
209
  };
208
210
  /**
209
211
  *
@@ -163,6 +163,28 @@ describe('File Model', () => {
163
163
  expect(fileModel.shouldBeOpenedByOnlyOffice(mdFile)).toBe(false)
164
164
  })
165
165
 
166
+ it.each`
167
+ name | mime | result
168
+ ${'test.docx'} | ${'application/vnd.openxmlformats-officedocument.wordprocessingml.document'} | ${true}
169
+ ${'test.odt'} | ${'application/vnd.oasis.opendocument.text'} | ${true}
170
+ ${'test.rtf'} | ${'text/rtf'} | ${true}
171
+ ${'test.csv'} | ${'text/csv'} | ${true}
172
+ ${'test.csv'} | ${'text/x-csv'} | ${true}
173
+ ${'test.tsv'} | ${'text/tab-separated-values'} | ${true}
174
+ ${'test.txt'} | ${'text/plain'} | ${false}
175
+ ${'test.md'} | ${'text/markdown'} | ${false}
176
+ ${'contact.vcf'} | ${'text/vcard'} | ${false}
177
+ ${'contact.vcf'} | ${'text/x-vcard'} | ${false}
178
+ ${'event.ics'} | ${'text/calendar'} | ${false}
179
+ ${'server.log'} | ${'text/plain'} | ${false}
180
+ `(
181
+ 'should return $result for $name ($mime) with only office',
182
+ ({ name, mime, result }) => {
183
+ const file = { type: 'file', class: 'text', name, mime }
184
+ expect(fileModel.shouldBeOpenedByOnlyOffice(file)).toBe(result)
185
+ }
186
+ )
187
+
166
188
  it('should test if a file has a specific metadata', () => {
167
189
  const doc = {
168
190
  metadata: {
@@ -554,11 +554,12 @@ exports.Q = Q;
554
554
  var isAGetByIdQuery = function isAGetByIdQuery(queryDefinition) {
555
555
  if (!queryDefinition) return false;
556
556
  var attributes = Object.values(queryDefinition);
557
- if (attributes.length === 0) return false; // 2 attrs because we check if id and doctype are not undefined
557
+ if (attributes.length === 0) return false; // File drive scope does not turn an ID lookup into a selector query.
558
558
 
559
+ var expectedAttributes = queryDefinition.doctype === _const.DOCTYPE_FILES && queryDefinition.sharingId !== undefined ? 3 : 2;
559
560
  return attributes.filter(function (attr) {
560
561
  return attr !== undefined;
561
- }).length === 2 && queryDefinition.id !== undefined;
562
+ }).length === expectedAttributes && queryDefinition.id !== undefined;
562
563
  }; // Mutations
563
564
 
564
565
 
@@ -422,9 +422,11 @@ var executeQueryFromState = function executeQueryFromState(state, queryDefinitio
422
422
 
423
423
  if (queryDefinition.id) {
424
424
  var doc = (0, _stateHelpers.getDocumentFromState)(state, queryDefinition.doctype, queryDefinition.id);
425
- res = doc ? [doc] : [];
425
+ res = doc && isDocumentInQueryScope(queryDefinition, doc) ? [doc] : [];
426
426
  } else if (queryDefinition.ids) {
427
- res = (0, _stateHelpers.getDocumentsFromState)(state, queryDefinition.doctype, queryDefinition.ids);
427
+ res = (0, _stateHelpers.getDocumentsFromState)(state, queryDefinition.doctype, queryDefinition.ids).filter(function (doc) {
428
+ return isDocumentInQueryScope(queryDefinition, doc);
429
+ });
428
430
  } else {
429
431
  var documents = (0, _stateHelpers.getCollectionFromState)(state, queryDefinition.doctype);
430
432
  res = documents === null || documents === void 0 ? void 0 : documents.filter(makeFilterDocumentFn(queryDefinition));
@@ -448,24 +450,39 @@ var executeQueryFromState = function executeQueryFromState(state, queryDefinitio
448
450
  data: sortedDocuments
449
451
  };
450
452
  };
453
+
454
+ exports.executeQueryFromState = executeQueryFromState;
455
+
456
+ function isDocumentInQueryScope(definition, document) {
457
+ var _definition$selector, _definition$partialFi;
458
+
459
+ if (definition.doctype !== _const.DOCTYPE_FILES) return true;
460
+ if (definition.sharingId) return document.driveId === definition.sharingId; // Offline folder queries can select a drive explicitly without sharingById().
461
+
462
+ if (((_definition$selector = definition.selector) === null || _definition$selector === void 0 ? void 0 : _definition$selector.driveId) !== undefined || ((_definition$partialFi = definition.partialFilter) === null || _definition$partialFi === void 0 ? void 0 : _definition$partialFi.driveId) !== undefined) {
463
+ return true;
464
+ }
465
+
466
+ return !document.driveId;
467
+ }
451
468
  /**
452
469
  *
453
470
  * Returns a predicate function that checks if a document should be
454
471
  * included in the result of the query.
455
472
  *
456
473
  * @param {QueryDefinition} queryDefinition - Definition of the query
457
- * @returns {function(import("../types").CozyClientDocument): boolean} Predicate function
474
+ * @returns {function(import("../types").CozyClientDocument): boolean|null} Predicate function; null means another drive
458
475
  */
459
476
 
460
477
 
461
- exports.executeQueryFromState = executeQueryFromState;
462
-
463
478
  var makeFilterDocumentFn = function makeFilterDocumentFn(queryDefinition) {
464
479
  var qdoctype = queryDefinition.doctype;
465
480
  var selectorFilterFn = getSelectorFilterFn(queryDefinition);
466
481
  return function (datum) {
467
482
  var ddoctype = datum._type;
468
- if (ddoctype !== qdoctype) return false;
483
+ if (ddoctype !== qdoctype) return false; // Another drive is ignored, not treated as a removal from this query.
484
+
485
+ if (!isDocumentInQueryScope(queryDefinition, datum)) return null;
469
486
  if (datum._deleted) return false;
470
487
  if (!selectorFilterFn) return true; // no selector: query all the docs
471
488
 
@@ -527,7 +544,7 @@ var dedupCache = null;
527
544
  var dedupIdIndex = null;
528
545
 
529
546
  var makeDefinitionKey = function makeDefinitionKey(def) {
530
- return JSON.stringify([def.doctype, def.selector, def.partialFilter, def.id, def.ids]);
547
+ return JSON.stringify([def.doctype, def.sharingId, def.selector, def.partialFilter, def.id, def.ids]);
531
548
  };
532
549
 
533
550
  var getDedupIdIndex = function getDedupIdIndex(newData) {
@@ -587,6 +604,10 @@ var computeMatchPartition = function computeMatchPartition(definition, newData)
587
604
  var doc = idx.get(id);
588
605
 
589
606
  if (doc) {
607
+ if (doc._type === definition.doctype && !isDocumentInQueryScope(definition, doc)) {
608
+ continue;
609
+ }
610
+
590
611
  if (!doc._deleted && doc._type === definition.doctype) {
591
612
  matchedIds.push((0, _helpers.properId)(doc));
592
613
  } else {
@@ -937,8 +958,9 @@ var QueryIDGenerator = /*#__PURE__*/function () {
937
958
  return this.generateRandomId();
938
959
  } else {
939
960
  var id = queryDefinition.id,
940
- doctype = queryDefinition.doctype;
941
- return "".concat(doctype, "/").concat(id);
961
+ doctype = queryDefinition.doctype,
962
+ sharingId = queryDefinition.sharingId;
963
+ return doctype === _const.DOCTYPE_FILES && sharingId ? "".concat(doctype, "/drive/").concat(sharingId, "/").concat(id) : "".concat(doctype, "/").concat(id);
942
964
  }
943
965
  }
944
966
  }]);
@@ -707,6 +707,57 @@ describe('updateData', () => {
707
707
  })
708
708
 
709
709
  describe('computeMatchPartition', () => {
710
+ it('ignores other drives instead of removing their documents from the query', () => {
711
+ const makeFile = (_id, driveId, name = 'Wanted') => ({
712
+ _id,
713
+ _type: 'io.cozy.files',
714
+ driveId,
715
+ name
716
+ })
717
+ const data = [
718
+ makeFile('personal', null),
719
+ makeFile('a-match', 'drive-a'),
720
+ makeFile('a-renamed', 'drive-a', 'Renamed'),
721
+ makeFile('b-match', 'drive-b')
722
+ ]
723
+ const definition = Q('io.cozy.files').where({ name: 'Wanted' })
724
+
725
+ expect(
726
+ computeMatchPartition(definition.sharingById('drive-a'), data)
727
+ ).toEqual({
728
+ matchedIds: ['a-match'],
729
+ unmatchedIds: ['a-renamed']
730
+ })
731
+ expect(
732
+ computeMatchPartition(definition.sharingById('drive-b'), data)
733
+ ).toEqual({
734
+ matchedIds: ['b-match'],
735
+ unmatchedIds: []
736
+ })
737
+ expect(computeMatchPartition(definition, data)).toEqual({
738
+ matchedIds: ['personal'],
739
+ unmatchedIds: []
740
+ })
741
+ })
742
+
743
+ it.each([false, true])(
744
+ 'ignores another drive for an id query (multiple: %s)',
745
+ multiple => {
746
+ const definition = Q('io.cozy.files').sharingById('drive-b')
747
+ const query = multiple
748
+ ? definition.getByIds(['same-id'])
749
+ : definition.getById('same-id')
750
+ const data = [
751
+ { _id: 'same-id', _type: 'io.cozy.files', driveId: 'drive-a' }
752
+ ]
753
+
754
+ expect(computeMatchPartition(query, data)).toEqual({
755
+ matchedIds: [],
756
+ unmatchedIds: []
757
+ })
758
+ }
759
+ )
760
+
710
761
  it('should resolve a getById query without scanning the whole newData', () => {
711
762
  const newData = [TODO_1, TODO_2, TODO_3]
712
763
  const definition = Q('io.cozy.todos').getById(TODO_2._id)
@@ -15,6 +15,8 @@ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/de
15
15
 
16
16
  var _dsl = require("../queries/dsl");
17
17
 
18
+ var _const = require("../const");
19
+
18
20
  var _mutations = require("./mutations");
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; }
@@ -29,13 +31,16 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
29
31
  * @public
30
32
  * @param {import("../types").CouchDBDocument} couchDBDoc - object representing the document
31
33
  * @param {string} doctype - Doctype of the document
34
+ * @param {string} [driveId] - Shared drive that emitted this event
32
35
  * @returns {import("../types").CozyClientDocument} full normalized document
33
36
  */
34
- var normalizeDoc = function normalizeDoc(couchDBDoc, doctype) {
35
- return _objectSpread({
37
+ var normalizeDoc = function normalizeDoc(couchDBDoc, doctype, driveId) {
38
+ return _objectSpread(_objectSpread({
36
39
  id: couchDBDoc._id,
37
40
  _type: doctype
38
- }, couchDBDoc);
41
+ }, doctype === _const.DOCTYPE_FILES && driveId ? {
42
+ driveId: driveId
43
+ } : {}), couchDBDoc);
39
44
  };
40
45
  /**
41
46
  * Enhances a document with additional attributes
@@ -103,6 +108,7 @@ var dispatchChange = function dispatchChange(client, document, mutationDefinitio
103
108
  /**
104
109
  * @typedef {Object} DispatchOptions
105
110
  * @property {function} [enhanceDocFn] Optional function to enhance the document attributes before dispatch
111
+ * @property {string} [driveId] Shared drive that emitted this event
106
112
  */
107
113
 
108
114
  /**
@@ -126,7 +132,7 @@ var dispatchCreate = /*#__PURE__*/function () {
126
132
  switch (_context2.prev = _context2.next) {
127
133
  case 0:
128
134
  options = _args2.length > 3 && _args2[3] !== undefined ? _args2[3] : {};
129
- normalizedDoc = normalizeDoc(couchDBDoc, doctype);
135
+ normalizedDoc = normalizeDoc(couchDBDoc, doctype, options.driveId);
130
136
  _context2.next = 4;
131
137
  return enhanceDoc(normalizedDoc, {
132
138
  client: client,
@@ -172,7 +178,7 @@ var dispatchUpdate = /*#__PURE__*/function () {
172
178
  switch (_context3.prev = _context3.next) {
173
179
  case 0:
174
180
  options = _args3.length > 3 && _args3[3] !== undefined ? _args3[3] : {};
175
- normalizedDoc = normalizeDoc(couchDBDoc, doctype);
181
+ normalizedDoc = normalizeDoc(couchDBDoc, doctype, options.driveId);
176
182
  _context3.next = 4;
177
183
  return enhanceDoc(normalizedDoc, {
178
184
  client: client,
@@ -220,7 +226,7 @@ var dispatchDelete = /*#__PURE__*/function () {
220
226
  options = _args4.length > 3 && _args4[3] !== undefined ? _args4[3] : {};
221
227
  normalizedDoc = normalizeDoc(_objectSpread(_objectSpread({}, couchDBDoc), {}, {
222
228
  _deleted: true
223
- }), doctype);
229
+ }), doctype, options.driveId);
224
230
  _context4.next = 4;
225
231
  return enhanceDoc(normalizedDoc, {
226
232
  client: client,
package/dist/types.js CHANGED
@@ -304,6 +304,7 @@ var _dsl = require("./queries/dsl");
304
304
  /**
305
305
  * @typedef {object} QueryOptions
306
306
  * @property {string} [as] - Name of the query
307
+ * @property {string} [driveId] - Shared-drive scope for io.cozy.files queries; equivalent to sharingById()
307
308
  * @property {Function} [fetchPolicy] - Fetch policy to bypass fetching based on what's already inside the state. See "Fetch policies"
308
309
  * @property {AutoUpdateOptions} [autoUpdate] - Options for the query auto update
309
310
  * @property {string} [update] - Does not seem to be used
@@ -408,9 +409,12 @@ var _dsl = require("./queries/dsl");
408
409
 
409
410
  /**
410
411
  * @typedef {object} MutationOptions
412
+ * @property {string} [driveId] - Shared drive scope for io.cozy.files creation through StackLink only
413
+ * @property {string} [forceLink] - Use 'stack' for online-only file creation when an offline link is configured
414
+ * @property {boolean} [forceStack] - Deprecated: use forceLink:'stack' instead
411
415
  * @property {string} as - Mutation id
412
416
  @property {Function} update - Function to update the document
413
- * @property {Function} updateQueries - Function to update queries
417
+ * @property {Object.<string, Function>} updateQueries - Query update functions indexed by query id
414
418
  */
415
419
 
416
420
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-client",
3
- "version": "60.45.0",
3
+ "version": "61.0.1",
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.45.0",
22
+ "cozy-stack-client": "^61.0.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": "0b14a9be7b03613a8e041f3f99110204e0942963"
77
+ "gitHead": "0619509d0618d695c92265c1f0cfecba3225aeb6"
78
78
  }
@@ -382,7 +382,7 @@ declare class CozyClient {
382
382
  * @param {import("./types").ReferenceMap} [references] - References are a special kind of relationship
383
383
  * that is not stored inside the referencer document, they are used for example between a photo
384
384
  * and its album. You should not need to use it normally.
385
- * @param {object} options - Mutation options
385
+ * @param {Partial<import('./types').MutationOptions>} [options] - Mutation options
386
386
  *
387
387
  * @example
388
388
  * ```js
@@ -398,7 +398,7 @@ declare class CozyClient {
398
398
  *
399
399
  * @returns {Promise}
400
400
  */
401
- create(type: string, doc: object, references?: import("./types").ReferenceMap, options?: object): Promise<any>;
401
+ create(type: string, doc: object, references?: import("./types").ReferenceMap, options?: Partial<import('./types').MutationOptions>): Promise<any>;
402
402
  /**
403
403
  * Create multiple documents in one batch.
404
404
  * WARNING: this method is currently not supported by the stack, but
@@ -543,18 +543,11 @@ declare class CozyClient {
543
543
  * Mutate a document
544
544
  *
545
545
  * @param {object} mutationDefinition - Describe the mutation
546
- * @param {object} [params] The mutation params
547
- * @param {function} [params.update] - update function from MutationOptions
548
- * @param {function} [params.updateQueries] - updateQueries function from MutationOptions
549
- * @param {string} [params.as] - Mutation id
550
- * @param {Object} [params.options={}] - Additional options
546
+ * @param {Partial<import('./types').MutationOptions> & { options?: object }} [params] - Mutation options
551
547
  * @returns {Promise}
552
548
  */
553
- mutate(mutationDefinition: object, { update, updateQueries, ...options }?: {
554
- update?: Function;
555
- updateQueries?: Function;
556
- as?: string;
557
- options?: any;
549
+ mutate(mutationDefinition: object, { update, updateQueries, ...options }?: Partial<import("./types").MutationOptions> & {
550
+ options?: object;
558
551
  }): Promise<any>;
559
552
  /**
560
553
  * Executes a query through links and fetches relationships
@@ -6,4 +6,8 @@ export type DispatchOptions = {
6
6
  * Optional function to enhance the document attributes before dispatch
7
7
  */
8
8
  enhanceDocFn?: Function;
9
+ /**
10
+ * Shared drive that emitted this event
11
+ */
12
+ driveId?: string;
9
13
  };
package/types/types.d.ts CHANGED
@@ -528,6 +528,10 @@ export type QueryOptions = {
528
528
  * - Name of the query
529
529
  */
530
530
  as?: string;
531
+ /**
532
+ * - Shared-drive scope for io.cozy.files queries; equivalent to sharingById()
533
+ */
534
+ driveId?: string;
531
535
  /**
532
536
  * - Fetch policy to bypass fetching based on what's already inside the state. See "Fetch policies"
533
537
  */
@@ -666,6 +670,18 @@ export type ReferenceMap = {
666
670
  [x: string]: Reference[];
667
671
  };
668
672
  export type MutationOptions = {
673
+ /**
674
+ * - Shared drive scope for io.cozy.files creation through StackLink only
675
+ */
676
+ driveId?: string;
677
+ /**
678
+ * - Use 'stack' for online-only file creation when an offline link is configured
679
+ */
680
+ forceLink?: string;
681
+ /**
682
+ * - Deprecated: use forceLink:'stack' instead
683
+ */
684
+ forceStack?: boolean;
669
685
  /**
670
686
  * - Mutation id
671
687
  */
@@ -675,9 +691,11 @@ export type MutationOptions = {
675
691
  */
676
692
  update: Function;
677
693
  /**
678
- * - Function to update queries
694
+ * - Query update functions indexed by query id
679
695
  */
680
- updateQueries: Function;
696
+ updateQueries: {
697
+ [x: string]: Function;
698
+ };
681
699
  };
682
700
  export type UpdatedByApp = {
683
701
  /**