cozy-client 60.37.0 → 60.38.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.
@@ -1833,9 +1833,10 @@ var CozyClient = /*#__PURE__*/function () {
1833
1833
  while (1) {
1834
1834
  switch (_context15.prev = _context15.next) {
1835
1835
  case 0:
1836
- isSingleDoc = !Array.isArray(response.data);
1836
+ isSingleDoc = !Array.isArray(response.data); // A query matching no document resolves with a null `data` or an empty
1837
+ // `data` array, depending on whether it is a single or multiple docs query
1837
1838
 
1838
- if (!(!isSingleDoc && response.data.length === 0)) {
1839
+ if (!(isSingleDoc && !response.data || !isSingleDoc && response.data.length === 0)) {
1839
1840
  _context15.next = 3;
1840
1841
  break;
1841
1842
  }
@@ -3126,7 +3127,7 @@ var CozyClient = /*#__PURE__*/function () {
3126
3127
  CozyClient.hooks = CozyClient.hooks || {};
3127
3128
  CozyClient.fetchPolicies = _policies.default; //COZY_CLIENT_VERSION_PACKAGE in replaced by babel. See babel config
3128
3129
 
3129
- CozyClient.version = "60.37.0";
3130
+ CozyClient.version = "60.38.0";
3130
3131
 
3131
3132
  _microee.default.mixin(CozyClient);
3132
3133
 
@@ -1296,6 +1296,19 @@ describe('CozyClient', () => {
1296
1296
  expect(requestHandler.mock.calls[0][0]).toBe(query)
1297
1297
  })
1298
1298
 
1299
+ it('should resolve to a null document when a getById with includes misses', async () => {
1300
+ requestHandler.mockResolvedValueOnce({ data: null })
1301
+
1302
+ const resp = await client.query(
1303
+ Q('io.cozy.todos')
1304
+ .getById('missing')
1305
+ .include(['attachments'])
1306
+ )
1307
+
1308
+ expect(requestHandler).toHaveBeenCalledTimes(1)
1309
+ expect(resp.data).toBeNull()
1310
+ })
1311
+
1299
1312
  it('should handle queries with includes', async () => {
1300
1313
  requestHandler.mockReturnValueOnce(
1301
1314
  Promise.resolve({
@@ -66,6 +66,7 @@ var attachRelationships = function attachRelationships(response, relationshipsBy
66
66
  });
67
67
  } else {
68
68
  var doc = response.data;
69
+ if (!doc) return response;
69
70
  return _objectSpread(_objectSpread({}, response), {}, {
70
71
  data: attachRelationship(doc, relationshipsByDocId[doc._id])
71
72
  });
@@ -68,6 +68,11 @@ describe('Associations helpers', () => {
68
68
  })
69
69
  })
70
70
 
71
+ it('should leave a missing document untouched', () => {
72
+ const response = { data: null }
73
+ expect(attachRelationships(response, {})).toBe(response)
74
+ })
75
+
71
76
  it('should add missing relationships', () => {
72
77
  const doc = {
73
78
  _id: 1,
@@ -27,6 +27,12 @@ Object.defineProperty(exports, "ensureFirstSlash", {
27
27
  return _urlHelper.ensureFirstSlash;
28
28
  }
29
29
  });
30
+ Object.defineProperty(exports, "isHttpUrl", {
31
+ enumerable: true,
32
+ get: function get() {
33
+ return _urlHelper.isHttpUrl;
34
+ }
35
+ });
30
36
  Object.defineProperty(exports, "fetchRegistrationDetails", {
31
37
  enumerable: true,
32
38
  get: function get() {
@@ -5,7 +5,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
5
5
  Object.defineProperty(exports, "__esModule", {
6
6
  value: true
7
7
  });
8
- exports.fetchRegistrationDetails = exports.rootCozyUrl = exports.InvalidCozyUrlError = exports.BlockedCozyError = exports.InvalidProtocolError = exports.InvalidRedirectLinkError = exports.deconstructRedirectLink = exports.deconstructCozyWebLinkWithSlug = exports.generateWebLink = exports.ensureFirstSlash = void 0;
8
+ exports.fetchRegistrationDetails = exports.isHttpUrl = exports.rootCozyUrl = exports.InvalidCozyUrlError = exports.BlockedCozyError = exports.InvalidProtocolError = exports.InvalidRedirectLinkError = exports.deconstructRedirectLink = exports.deconstructCozyWebLinkWithSlug = exports.generateWebLink = exports.ensureFirstSlash = void 0;
9
9
 
10
10
  var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
11
11
 
@@ -509,6 +509,23 @@ var isResponseAboutBlockedCozy = /*#__PURE__*/function () {
509
509
  return _ref5.apply(this, arguments);
510
510
  };
511
511
  }();
512
+ /**
513
+ * Check if the given value is an absolute URL using the http or https protocol
514
+ *
515
+ * @param {any} value - Value to check
516
+ * @returns {boolean} true if the value is an absolute http(s) URL
517
+ */
518
+
519
+
520
+ var isHttpUrl = function isHttpUrl(value) {
521
+ if (typeof value !== 'string') return false;
522
+
523
+ try {
524
+ return ['http:', 'https:'].includes(new URL(value).protocol);
525
+ } catch (e) {
526
+ return false;
527
+ }
528
+ };
512
529
  /**
513
530
  * Check if the given url uses the expected protocols (http or https). Throws otherwise
514
531
  *
@@ -516,6 +533,8 @@ var isResponseAboutBlockedCozy = /*#__PURE__*/function () {
516
533
  */
517
534
 
518
535
 
536
+ exports.isHttpUrl = isHttpUrl;
537
+
519
538
  var throwIfInvalidProtocol = function throwIfInvalidProtocol(url) {
520
539
  if (!['http:', 'https:'].includes(url.protocol)) {
521
540
  throw new InvalidProtocolError(url);
@@ -5,6 +5,7 @@ import {
5
5
  deconstructRedirectLink,
6
6
  generateWebLink,
7
7
  fetchRegistrationDetails,
8
+ isHttpUrl,
8
9
  rootCozyUrl,
9
10
  InvalidRedirectLinkError,
10
11
  InvalidCozyUrlError,
@@ -133,6 +134,28 @@ describe('deconstructWebLink', () => {
133
134
  )
134
135
  })
135
136
 
137
+ describe('isHttpUrl', () => {
138
+ it.each([
139
+ 'http://alice.cozy.localhost:8080',
140
+ 'https://calendar.example.com/'
141
+ ])('accepts %p', value => {
142
+ expect(isHttpUrl(value)).toBe(true)
143
+ })
144
+
145
+ it.each([
146
+ undefined,
147
+ null,
148
+ true,
149
+ '',
150
+ 'calendar.example.com',
151
+ 'not a url',
152
+ 'javascript:alert(1)',
153
+ 'ftp://example.com'
154
+ ])('rejects %p', value => {
155
+ expect(isHttpUrl(value)).toBe(false)
156
+ })
157
+ })
158
+
136
159
  describe('deconstructRedirectLink', () => {
137
160
  it.each([
138
161
  [
@@ -13,6 +13,8 @@ var _cozyFlags = _interopRequireDefault(require("cozy-flags"));
13
13
 
14
14
  var _get = _interopRequireDefault(require("lodash/get"));
15
15
 
16
+ var _helpers = require("../helpers");
17
+
16
18
  var STORE_SLUG = 'store';
17
19
  /**
18
20
  * Returns the store URL of an app/konnector
@@ -79,7 +81,6 @@ var isInstalled = function isInstalled() {
79
81
  });
80
82
  };
81
83
  /**
82
- *
83
84
  * @param {object} app io.cozy.apps document
84
85
  * @returns {string} url to the app
85
86
  */
@@ -88,6 +89,11 @@ var isInstalled = function isInstalled() {
88
89
  exports.isInstalled = isInstalled;
89
90
 
90
91
  var getUrl = function getUrl(app) {
92
+ if (app.standalone === true && app.client_url_flag) {
93
+ var clientUrl = (0, _cozyFlags.default)(app.client_url_flag);
94
+ if ((0, _helpers.isHttpUrl)(clientUrl)) return clientUrl;
95
+ }
96
+
91
97
  return app.links && app.links.related;
92
98
  };
93
99
  /**
@@ -124,6 +124,55 @@ describe('applications model', () => {
124
124
  })
125
125
  })
126
126
 
127
+ describe('getUrl', () => {
128
+ const related = 'http://calendar.cozy.tools:8080/'
129
+
130
+ beforeEach(() => {
131
+ flag.mockReset()
132
+ })
133
+
134
+ it('returns links.related when the app has no client_url_flag', () => {
135
+ expect(applications.getUrl({ links: { related } })).toBe(related)
136
+ })
137
+
138
+ it('returns the flag value of a standalone app when it is a valid URL', () => {
139
+ flag.mockReturnValue('https://calendar.example.com/')
140
+ const app = {
141
+ standalone: true,
142
+ client_url_flag: 'calendar.embedded-app-url',
143
+ links: { related }
144
+ }
145
+ expect(applications.getUrl(app)).toBe('https://calendar.example.com/')
146
+ expect(flag).toHaveBeenCalledWith('calendar.embedded-app-url')
147
+ })
148
+
149
+ it.each([null, true, 'not a url', 'javascript:alert(1)'])(
150
+ 'returns links.related when the flag value is %p',
151
+ value => {
152
+ flag.mockReturnValue(value)
153
+ const app = {
154
+ standalone: true,
155
+ client_url_flag: 'calendar.embedded-app-url',
156
+ links: { related }
157
+ }
158
+ expect(applications.getUrl(app)).toBe(related)
159
+ }
160
+ )
161
+
162
+ it.each([undefined, false])(
163
+ 'returns links.related when standalone is %p',
164
+ standalone => {
165
+ flag.mockReturnValue('https://mail.example.com/')
166
+ const app = {
167
+ standalone,
168
+ client_url_flag: 'mail.embedded-app-url',
169
+ links: { related }
170
+ }
171
+ expect(applications.getUrl(app)).toBe(related)
172
+ }
173
+ )
174
+ })
175
+
127
176
  describe('get store installation url', () => {
128
177
  const contactsApp = { slug: 'contacts' }
129
178
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-client",
3
- "version": "60.37.0",
3
+ "version": "60.38.0",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.node.js",
6
6
  "browser": "dist/index.js",
@@ -74,5 +74,5 @@
74
74
  "react-native-ios11-devicecheck": "^0.0.3"
75
75
  },
76
76
  "sideEffects": false,
77
- "gitHead": "55c11be9fd2fc31e6aa047945c28e1395981a289"
77
+ "gitHead": "7531ce634e310b15dbbe32958a71600a57e8ecad"
78
78
  }
@@ -1,3 +1,3 @@
1
1
  export { dehydrate } from "./dehydrateHelper";
2
- export { deconstructCozyWebLinkWithSlug, deconstructRedirectLink, generateWebLink, ensureFirstSlash, fetchRegistrationDetails, rootCozyUrl, InvalidRedirectLinkError, InvalidCozyUrlError, InvalidProtocolError, BlockedCozyError } from "./urlHelper";
2
+ export { deconstructCozyWebLinkWithSlug, deconstructRedirectLink, generateWebLink, ensureFirstSlash, isHttpUrl, fetchRegistrationDetails, rootCozyUrl, InvalidRedirectLinkError, InvalidCozyUrlError, InvalidProtocolError, BlockedCozyError } from "./urlHelper";
3
3
  export { editSettings, getQuery, getSettings, normalizeSettings, saveAfterFetchSettings } from "./settings";
@@ -26,6 +26,7 @@ export class InvalidCozyUrlError extends Error {
26
26
  url: any;
27
27
  }
28
28
  export function rootCozyUrl(url: URL): Promise<URL>;
29
+ export function isHttpUrl(value: any): boolean;
29
30
  export function fetchRegistrationDetails(url: URL): Promise<RegistrationDetails>;
30
31
  export type RegistrationDetails = {
31
32
  /**