cozy-client 34.11.0 → 35.0.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.
- package/dist/CozyClient.js +2 -2
- package/dist/index.js +1 -1
- package/dist/index.node.js +1 -1
- package/dist/models/manifest.js +239 -0
- package/dist/models/manifest.spec.js +446 -0
- package/dist/types.js +16 -0
- package/package.json +3 -3
- package/types/CozyClient.d.ts +1 -1
- package/types/index.d.ts +1 -1
- package/types/index.node.d.ts +1 -1
- package/types/models/manifest.d.ts +23 -0
- package/types/types.d.ts +26 -0
- package/dist/manifest.js +0 -83
- package/dist/manifest.spec.js +0 -135
- package/types/manifest.d.ts +0 -12
package/dist/CozyClient.js
CHANGED
|
@@ -548,7 +548,7 @@ var CozyClient = /*#__PURE__*/function () {
|
|
|
548
548
|
* Emits
|
|
549
549
|
*
|
|
550
550
|
* - "beforeLogout" at the beginning, before links have been reset
|
|
551
|
-
* - "
|
|
551
|
+
* - "logout" when the client is fully logged out and links have been reset
|
|
552
552
|
*
|
|
553
553
|
* @returns {Promise} - Resolves when all links have been reset and client is fully logged out
|
|
554
554
|
*/
|
|
@@ -2669,7 +2669,7 @@ var CozyClient = /*#__PURE__*/function () {
|
|
|
2669
2669
|
CozyClient.hooks = CozyClient.hooks || {};
|
|
2670
2670
|
CozyClient.fetchPolicies = _policies.default; //COZY_CLIENT_VERSION_PACKAGE in replaced by babel. See babel config
|
|
2671
2671
|
|
|
2672
|
-
CozyClient.version = "
|
|
2672
|
+
CozyClient.version = "35.0.0";
|
|
2673
2673
|
|
|
2674
2674
|
_microee.default.mixin(CozyClient);
|
|
2675
2675
|
|
package/dist/index.js
CHANGED
|
@@ -323,7 +323,7 @@ var _registry = _interopRequireDefault(require("./registry"));
|
|
|
323
323
|
|
|
324
324
|
var _RealTimeQueries = _interopRequireDefault(require("./RealTimeQueries"));
|
|
325
325
|
|
|
326
|
-
var manifest = _interopRequireWildcard(require("./manifest"));
|
|
326
|
+
var manifest = _interopRequireWildcard(require("./models/manifest"));
|
|
327
327
|
|
|
328
328
|
exports.manifest = manifest;
|
|
329
329
|
|
package/dist/index.node.js
CHANGED
|
@@ -221,7 +221,7 @@ var _store = require("./store");
|
|
|
221
221
|
|
|
222
222
|
var _registry = _interopRequireDefault(require("./registry"));
|
|
223
223
|
|
|
224
|
-
var manifest = _interopRequireWildcard(require("./manifest"));
|
|
224
|
+
var manifest = _interopRequireWildcard(require("./models/manifest"));
|
|
225
225
|
|
|
226
226
|
exports.manifest = manifest;
|
|
227
227
|
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
|
|
5
|
+
Object.defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
exports.sanitizeCategories = sanitizeCategories;
|
|
9
|
+
exports.areTermsValid = areTermsValid;
|
|
10
|
+
exports.isPartnershipValid = isPartnershipValid;
|
|
11
|
+
exports.sanitize = sanitize;
|
|
12
|
+
exports.getIdentifier = exports.sanitizeIdentifier = exports.legacyLoginFields = exports.ROLE_IDENTIFIER = void 0;
|
|
13
|
+
|
|
14
|
+
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
15
|
+
|
|
16
|
+
var _flow2 = _interopRequireDefault(require("lodash/flow"));
|
|
17
|
+
|
|
18
|
+
var _cloneDeep2 = _interopRequireDefault(require("lodash/cloneDeep"));
|
|
19
|
+
|
|
20
|
+
var _findKey = _interopRequireDefault(require("lodash/findKey"));
|
|
21
|
+
|
|
22
|
+
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; } } }; }
|
|
23
|
+
|
|
24
|
+
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); }
|
|
25
|
+
|
|
26
|
+
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; }
|
|
27
|
+
|
|
28
|
+
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
|
+
|
|
30
|
+
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
|
|
31
|
+
|
|
32
|
+
var ROLE_IDENTIFIER = 'identifier';
|
|
33
|
+
/**
|
|
34
|
+
* Legacy login fields declared by some konnectors
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
exports.ROLE_IDENTIFIER = ROLE_IDENTIFIER;
|
|
38
|
+
var legacyLoginFields = ['login', 'identifier', 'new_identifier', 'email'];
|
|
39
|
+
exports.legacyLoginFields = legacyLoginFields;
|
|
40
|
+
var legacyEncryptedFields = ['secret', 'dob', 'code', 'answer', 'access_token', 'refresh_token', 'appSecret'];
|
|
41
|
+
var APP_CATEGORIES = ['banking', 'cozy', 'energy', 'health', 'host_provider', 'insurance', 'isp', 'mes_infos', 'online_services', 'others', 'partners', 'press', 'productivity', 'ptnb', 'public_service', 'shopping', 'social', 'telecom', 'transport'];
|
|
42
|
+
/**
|
|
43
|
+
* Filters unauthorized categories. Defaults to ['others'] if no suitable category.
|
|
44
|
+
*
|
|
45
|
+
* @param {Array<Object>} categories - Array of categories
|
|
46
|
+
* @returns {Array<Object>} sanitized categories
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
function sanitizeCategories(categories) {
|
|
50
|
+
if (!categories) return ['others'];
|
|
51
|
+
var filteredList = categories.filter(function (c) {
|
|
52
|
+
return APP_CATEGORIES.includes(c);
|
|
53
|
+
});
|
|
54
|
+
if (!filteredList.length) return ['others'];
|
|
55
|
+
return filteredList;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function areTermsValid(terms) {
|
|
59
|
+
return Boolean(terms && terms.id && terms.url && terms.version);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isPartnershipValid(partnership) {
|
|
63
|
+
return Boolean(partnership && partnership.description);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Normalize app manifest, retro-compatibility for old manifests
|
|
67
|
+
*
|
|
68
|
+
* @param {import('../types').Manifest} manifest - app manifest to normalize
|
|
69
|
+
* @returns {import('../types').SanitizedManifest}
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
function sanitize(manifest) {
|
|
74
|
+
var sanitized = _objectSpread({}, manifest); // Make categories an array and delete category attribute if it exists
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
if (!manifest.categories && manifest.category && typeof manifest.category === 'string') {
|
|
78
|
+
sanitized.categories = [manifest.category];
|
|
79
|
+
delete sanitized.category;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
sanitized.categories = sanitizeCategories(sanitized.categories); // manifest name is not an object
|
|
83
|
+
|
|
84
|
+
if (typeof manifest.name === 'object') sanitized.name = manifest.name.en; // Fix camelCase from cozy-stack
|
|
85
|
+
|
|
86
|
+
if (manifest.available_version) {
|
|
87
|
+
sanitized.availableVersion = manifest.available_version;
|
|
88
|
+
delete sanitized.available_version;
|
|
89
|
+
} // Fix camelCase from cozy-stack
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
if (manifest.latest_version) {
|
|
93
|
+
sanitized.latestVersion = manifest.latestVersion;
|
|
94
|
+
delete sanitized.latest_version;
|
|
95
|
+
} // Remove invalid terms
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
if (sanitized.terms && !areTermsValid(sanitized.terms)) {
|
|
99
|
+
delete sanitized.terms;
|
|
100
|
+
} // Remove invalid partnership
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
if (sanitized.partnership && !isPartnershipValid(sanitized.partnership)) {
|
|
104
|
+
delete sanitized.partnership;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (sanitized.fields) {
|
|
108
|
+
sanitized.fields = sanitizeFields(manifest.fields);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return sanitized;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Ensures that fields has at least one field with the role 'identifier'
|
|
115
|
+
*
|
|
116
|
+
* @param {import('../types').ManifestFields} fields - Manifest fields
|
|
117
|
+
* @returns {import('../types').ManifestFields} - Sanitized manifest fields
|
|
118
|
+
*/
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
var sanitizeIdentifier = function sanitizeIdentifier(fields) {
|
|
122
|
+
var sanitized = (0, _cloneDeep2.default)(fields);
|
|
123
|
+
var hasIdentifier = false;
|
|
124
|
+
|
|
125
|
+
for (var fieldName in sanitized) {
|
|
126
|
+
if (sanitized[fieldName].role === ROLE_IDENTIFIER) {
|
|
127
|
+
if (hasIdentifier) delete sanitized[fieldName].role;else hasIdentifier = true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (hasIdentifier) return sanitized;
|
|
132
|
+
|
|
133
|
+
var _iterator = _createForOfIteratorHelper(legacyLoginFields),
|
|
134
|
+
_step;
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
138
|
+
var name = _step.value;
|
|
139
|
+
|
|
140
|
+
if (sanitized[name]) {
|
|
141
|
+
sanitized[name].role = ROLE_IDENTIFIER;
|
|
142
|
+
return sanitized;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} catch (err) {
|
|
146
|
+
_iterator.e(err);
|
|
147
|
+
} finally {
|
|
148
|
+
_iterator.f();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
for (var _fieldName in sanitized) {
|
|
152
|
+
if (sanitized[_fieldName].type !== 'password') {
|
|
153
|
+
sanitized[_fieldName].role = ROLE_IDENTIFIER;
|
|
154
|
+
return sanitized;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return sanitized;
|
|
159
|
+
};
|
|
160
|
+
/**
|
|
161
|
+
* Returns the key for the field having the role=identifier attribute
|
|
162
|
+
*
|
|
163
|
+
* @param {import('../types').ManifestFields} fields Konnector fields
|
|
164
|
+
* @returns {String|null} The key for the identifier field, example 'login'
|
|
165
|
+
*/
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
exports.sanitizeIdentifier = sanitizeIdentifier;
|
|
169
|
+
|
|
170
|
+
var getIdentifier = function getIdentifier() {
|
|
171
|
+
var fields = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
172
|
+
return (0, _findKey.default)(sanitizeIdentifier(fields), function (field) {
|
|
173
|
+
return field.role === ROLE_IDENTIFIER;
|
|
174
|
+
}) || null;
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* Ensures old fields are removed
|
|
178
|
+
*
|
|
179
|
+
* @param {Object} fields Manifest fields
|
|
180
|
+
* @returns {Object} Sanitized manifest fields
|
|
181
|
+
*/
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
exports.getIdentifier = getIdentifier;
|
|
185
|
+
|
|
186
|
+
var removeOldFields = function removeOldFields(fields) {
|
|
187
|
+
var sanitized = (0, _cloneDeep2.default)(fields);
|
|
188
|
+
delete sanitized.advancedFields;
|
|
189
|
+
return sanitized;
|
|
190
|
+
};
|
|
191
|
+
/**
|
|
192
|
+
* Ensures every field not explicitely tagged as not required is required
|
|
193
|
+
*
|
|
194
|
+
* @param {import('../types').ManifestFields} [fields={}] Manifest fields
|
|
195
|
+
* @returns {import('../types').ManifestFields} Sanitized manifest fields
|
|
196
|
+
*/
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
var sanitizeRequired = function sanitizeRequired(fields) {
|
|
200
|
+
var sanitized = (0, _cloneDeep2.default)(fields);
|
|
201
|
+
|
|
202
|
+
for (var fieldName in sanitized) {
|
|
203
|
+
var field = sanitized[fieldName]; // Ensure legacy for field isRequired
|
|
204
|
+
|
|
205
|
+
var required = typeof field.required === 'undefined' ? field.isRequired : field.required;
|
|
206
|
+
sanitized[fieldName].required = typeof required === 'boolean' ? required : true;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return sanitized;
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* Ensures:
|
|
213
|
+
* * any field flagged as encrypted keeps its flag
|
|
214
|
+
* * any legacy encrypted field is tagged as encrypted
|
|
215
|
+
*
|
|
216
|
+
* @param {import('../types').ManifestFields} [fields={}] Manifest fields
|
|
217
|
+
* @returns {import('../types').ManifestFields} Sanitized Manifest fields
|
|
218
|
+
*/
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
var sanitizeEncrypted = function sanitizeEncrypted(fields) {
|
|
222
|
+
var sanitized = (0, _cloneDeep2.default)(fields);
|
|
223
|
+
|
|
224
|
+
for (var fieldName in sanitized) {
|
|
225
|
+
var field = sanitized[fieldName];
|
|
226
|
+
if (typeof field.encrypted !== 'boolean') field.encrypted = field.type === 'password' || legacyEncryptedFields.includes(fieldName);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return sanitized;
|
|
230
|
+
};
|
|
231
|
+
/**
|
|
232
|
+
* Sanitizes manifest fields with multiple rules
|
|
233
|
+
*
|
|
234
|
+
* @param {import('../types').ManifestFields} [fields={}] Manifest fields
|
|
235
|
+
* @returns {import('../types').ManifestFields} Sanitized manifest fields
|
|
236
|
+
*/
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
var sanitizeFields = (0, _flow2.default)([removeOldFields, sanitizeIdentifier, sanitizeRequired, sanitizeEncrypted]);
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
import {
|
|
2
|
+
sanitizeCategories,
|
|
3
|
+
sanitize as sanitizeManifest,
|
|
4
|
+
getIdentifier
|
|
5
|
+
} from './manifest'
|
|
6
|
+
|
|
7
|
+
describe('sanitizeCategories', () => {
|
|
8
|
+
it('should return the list of the provided expected categories correctly', () => {
|
|
9
|
+
// all of these categories is authorized by config/categories.json
|
|
10
|
+
// so it won't be filtered
|
|
11
|
+
const categories = [
|
|
12
|
+
'cozy',
|
|
13
|
+
'isp',
|
|
14
|
+
'partners',
|
|
15
|
+
'press',
|
|
16
|
+
'shopping',
|
|
17
|
+
'telecom'
|
|
18
|
+
]
|
|
19
|
+
expect(sanitizeCategories(categories)).toEqual(categories)
|
|
20
|
+
})
|
|
21
|
+
it('should filter unwanted categories', () => {
|
|
22
|
+
// pressingggg is not allowed as category
|
|
23
|
+
const categories = [
|
|
24
|
+
'cozy',
|
|
25
|
+
'isp',
|
|
26
|
+
'partners',
|
|
27
|
+
'pressingggg',
|
|
28
|
+
'shopping',
|
|
29
|
+
'telecom'
|
|
30
|
+
]
|
|
31
|
+
expect(sanitizeCategories(categories)).toEqual(
|
|
32
|
+
categories.filter(c => c !== 'pressingggg')
|
|
33
|
+
)
|
|
34
|
+
})
|
|
35
|
+
it('should return "others" as category if no categories provided', () => {
|
|
36
|
+
expect(sanitizeCategories([])).toEqual(['others'])
|
|
37
|
+
expect(sanitizeCategories(null)).toEqual(['others'])
|
|
38
|
+
})
|
|
39
|
+
it('should return "others" as category if categories after filtering is empty', () => {
|
|
40
|
+
// pressingggg is not allowed as category
|
|
41
|
+
expect(sanitizeCategories(['pressingggg'])).toEqual(['others'])
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
describe('sanitizeManifest', () => {
|
|
46
|
+
const baseMan = { slug: 'mock' }
|
|
47
|
+
|
|
48
|
+
it('should handle legacy category property', () => {
|
|
49
|
+
const testCategory = 'cozy'
|
|
50
|
+
expect(
|
|
51
|
+
sanitizeManifest({ slug: 'mock', category: testCategory })
|
|
52
|
+
).toStrictEqual({
|
|
53
|
+
slug: 'mock',
|
|
54
|
+
categories: [testCategory]
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
it('should handle legacy name property locales ("en" only)', () => {
|
|
58
|
+
const testName = 'Mock'
|
|
59
|
+
expect(
|
|
60
|
+
sanitizeManifest({
|
|
61
|
+
slug: 'mock',
|
|
62
|
+
name: {
|
|
63
|
+
en: testName
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
).toEqual(
|
|
67
|
+
expect.objectContaining({
|
|
68
|
+
slug: 'mock',
|
|
69
|
+
name: testName
|
|
70
|
+
})
|
|
71
|
+
)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('should transform available_version property to availableVersion', () => {
|
|
75
|
+
const testVersion = '3.0.0'
|
|
76
|
+
const man = sanitizeManifest({
|
|
77
|
+
slug: 'mock',
|
|
78
|
+
available_version: testVersion
|
|
79
|
+
})
|
|
80
|
+
expect(man.availableVersion).toBe('3.0.0')
|
|
81
|
+
expect(man).not.toHaveProperty('available_version')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('should remove "fields" if fields is null', () => {
|
|
85
|
+
const current = {
|
|
86
|
+
fields: null
|
|
87
|
+
}
|
|
88
|
+
const result = sanitizeManifest(current)
|
|
89
|
+
expect(result.fields).toBe(null)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it("shouldn't modify if fields is undefined", () => {
|
|
93
|
+
const current = {}
|
|
94
|
+
const result = sanitizeManifest(current)
|
|
95
|
+
expect(result.fields).toBe(undefined)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('should not mutate source manifest', () => {
|
|
99
|
+
const current = {
|
|
100
|
+
fields: {
|
|
101
|
+
username: {
|
|
102
|
+
type: 'text',
|
|
103
|
+
required: {
|
|
104
|
+
value: 'true'
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
passphrase: {
|
|
108
|
+
type: 'password'
|
|
109
|
+
},
|
|
110
|
+
birthdate: {
|
|
111
|
+
type: 'date'
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
sanitizeManifest(current)
|
|
117
|
+
expect(current).toEqual({
|
|
118
|
+
fields: {
|
|
119
|
+
username: {
|
|
120
|
+
type: 'text',
|
|
121
|
+
required: {
|
|
122
|
+
value: 'true'
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
passphrase: {
|
|
126
|
+
type: 'password'
|
|
127
|
+
},
|
|
128
|
+
birthdate: {
|
|
129
|
+
type: 'date'
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('should remove old property advancedFields in fields', () => {
|
|
136
|
+
const oldManifest = {
|
|
137
|
+
fields: {
|
|
138
|
+
login: {
|
|
139
|
+
type: 'text'
|
|
140
|
+
},
|
|
141
|
+
password: {
|
|
142
|
+
type: 'password'
|
|
143
|
+
},
|
|
144
|
+
advancedFields: {
|
|
145
|
+
folderPath: {
|
|
146
|
+
advanced: true,
|
|
147
|
+
isRequired: false
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const result = sanitizeManifest(oldManifest)
|
|
154
|
+
expect(result.fields.advancedFields).toBeUndefined()
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('should not add any identifier', () => {
|
|
158
|
+
const current = {
|
|
159
|
+
fields: {
|
|
160
|
+
passphrase: {
|
|
161
|
+
required: true,
|
|
162
|
+
type: 'password'
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const result = sanitizeManifest(current)
|
|
168
|
+
expect(result.fields.passphrase.role).not.toBe('identifier')
|
|
169
|
+
expect(Object.keys(result.fields).length).toBe(1)
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('should set role=identifier for login', () => {
|
|
173
|
+
const current = {
|
|
174
|
+
fields: {
|
|
175
|
+
login: { type: 'text' },
|
|
176
|
+
password: { type: 'password' }
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const result = sanitizeManifest(current)
|
|
180
|
+
expect(result.fields.login.role).toBe('identifier')
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('should set first non-password field as role=identifier', () => {
|
|
184
|
+
const current = {
|
|
185
|
+
fields: {
|
|
186
|
+
password: { type: 'password' },
|
|
187
|
+
plop: { type: 'text' },
|
|
188
|
+
foo: { type: 'date' }
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const result = sanitizeManifest(current)
|
|
192
|
+
expect(result.fields.plop.role).toBe('identifier')
|
|
193
|
+
expect(result.fields.foo.role).not.toBe('identifier')
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
const legacyLoginFieldsTest = [
|
|
197
|
+
'login',
|
|
198
|
+
'identifier',
|
|
199
|
+
'new_identifier',
|
|
200
|
+
'email'
|
|
201
|
+
]
|
|
202
|
+
for (let name of legacyLoginFieldsTest) {
|
|
203
|
+
let inputLegacy = {
|
|
204
|
+
fields: {
|
|
205
|
+
password: { type: 'password' },
|
|
206
|
+
plop: { type: 'text' }
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
inputLegacy.fields[name] = { type: 'text' }
|
|
210
|
+
it('should set role=identifier to ' + name, () => {
|
|
211
|
+
const result = sanitizeManifest(inputLegacy)
|
|
212
|
+
expect(result.fields[name].role).toBe('identifier')
|
|
213
|
+
expect(result.fields.plop.role).not.toBe('identifier')
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
it('should set only one identifier', () => {
|
|
218
|
+
const current = {
|
|
219
|
+
fields: {
|
|
220
|
+
identifier: { type: 'text' },
|
|
221
|
+
mail: { type: 'email' },
|
|
222
|
+
login: { type: 'text' },
|
|
223
|
+
new_identifier: { type: 'text' }
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const result = sanitizeManifest(current)
|
|
227
|
+
const identifiers = Object.keys(result.fields).filter(
|
|
228
|
+
name => result.fields[name].role === 'identifier'
|
|
229
|
+
)
|
|
230
|
+
expect(identifiers.length).toBe(1)
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
it('should set only one identifier even if there is many role=identifier in the manifest', () => {
|
|
234
|
+
const current = {
|
|
235
|
+
fields: {
|
|
236
|
+
identifier: { type: 'text' },
|
|
237
|
+
mail: { type: 'email', role: 'identifier' },
|
|
238
|
+
login: { type: 'text', role: 'identifier' },
|
|
239
|
+
new_identifier: { type: 'text', role: 'identifier' }
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const result = sanitizeManifest(current)
|
|
243
|
+
const identifiers = Object.keys(result.fields).filter(
|
|
244
|
+
name => result.fields[name].role === 'identifier'
|
|
245
|
+
)
|
|
246
|
+
expect(identifiers.length).toBe(1)
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
it('should set only one identifier even if there is no password fields', () => {
|
|
250
|
+
const current = {
|
|
251
|
+
fields: {
|
|
252
|
+
plop1: { type: 'text' },
|
|
253
|
+
plop2: { type: 'text' },
|
|
254
|
+
plop3: { type: 'text' },
|
|
255
|
+
plop4: { type: 'text' }
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
const result = sanitizeManifest(current)
|
|
259
|
+
const identifiers = Object.keys(result.fields).filter(
|
|
260
|
+
name => result.fields[name].role === 'identifier'
|
|
261
|
+
)
|
|
262
|
+
expect(identifiers.length).toBe(1)
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
it('should keep the identifier priority', () => {
|
|
266
|
+
const current = {
|
|
267
|
+
fields: {
|
|
268
|
+
identifier: { type: 'text' },
|
|
269
|
+
mail: { type: 'email' },
|
|
270
|
+
login: { type: 'text' },
|
|
271
|
+
new_identifier: { type: 'text' }
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const result = sanitizeManifest(current)
|
|
275
|
+
expect(result.fields.login.required).toBe(true)
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
it('should set required=true as default value in fields', () => {
|
|
279
|
+
const current = {
|
|
280
|
+
fields: {
|
|
281
|
+
login: { type: 'text' },
|
|
282
|
+
password: { type: 'password' },
|
|
283
|
+
gender: { type: 'text', required: false },
|
|
284
|
+
country: { type: 'text' }
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
const result = sanitizeManifest(current)
|
|
288
|
+
expect(result.fields.login.required).toBe(true)
|
|
289
|
+
expect(result.fields.password.required).toBe(true)
|
|
290
|
+
expect(result.fields.gender.required).toBe(false)
|
|
291
|
+
expect(result.fields.country.required).toBe(true)
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
it('should handle legacy property isRequired in fields', () => {
|
|
295
|
+
const current = {
|
|
296
|
+
fields: {
|
|
297
|
+
login: { type: 'text' },
|
|
298
|
+
password: { type: 'password' },
|
|
299
|
+
gender: { type: 'text', isRequired: false },
|
|
300
|
+
country: { type: 'text' }
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const result = sanitizeManifest(current)
|
|
304
|
+
expect(result.fields.login.required).toBe(true)
|
|
305
|
+
expect(result.fields.password.required).toBe(true)
|
|
306
|
+
expect(result.fields.gender.required).toBe(false)
|
|
307
|
+
expect(result.fields.country.required).toBe(true)
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
it('should set encrypted for type=password fields', () => {
|
|
311
|
+
const current = {
|
|
312
|
+
fields: {
|
|
313
|
+
username: { type: 'text' },
|
|
314
|
+
passphrase: { type: 'password' }
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const result = sanitizeManifest(current)
|
|
319
|
+
expect(result.fields.passphrase.encrypted).toBe(true)
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
const legacyEncryptedFieldsTest = [
|
|
323
|
+
'secret',
|
|
324
|
+
'dob',
|
|
325
|
+
'code',
|
|
326
|
+
'answer',
|
|
327
|
+
'access_token',
|
|
328
|
+
'refresh_token',
|
|
329
|
+
'appSecret'
|
|
330
|
+
]
|
|
331
|
+
|
|
332
|
+
for (let name of legacyEncryptedFieldsTest) {
|
|
333
|
+
let legacyManifest = {
|
|
334
|
+
fields: {
|
|
335
|
+
[name]: { type: 'text' },
|
|
336
|
+
password: { type: 'password' },
|
|
337
|
+
plop: { type: 'text' }
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
it('should set encrypted=true to ' + name, () => {
|
|
342
|
+
const result = sanitizeManifest(legacyManifest)
|
|
343
|
+
expect(result.fields[name].encrypted).toBe(true)
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
it('should keep encrypted value', () => {
|
|
347
|
+
const current = {
|
|
348
|
+
fields: {
|
|
349
|
+
[name]: { type: 'text', encrypted: false },
|
|
350
|
+
passphrase: { type: 'password', encrypted: true }
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
const result = sanitizeManifest(current)
|
|
354
|
+
expect(result.fields[name].encrypted).toBe(false)
|
|
355
|
+
expect(result.fields.passphrase.encrypted).toBe(true)
|
|
356
|
+
})
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
describe('terms', () => {
|
|
360
|
+
it('should remove incomplete terms', () => {
|
|
361
|
+
const incompleteTerms = {
|
|
362
|
+
id: 'mock-terms'
|
|
363
|
+
}
|
|
364
|
+
expect(
|
|
365
|
+
sanitizeManifest({ ...baseMan, terms: incompleteTerms })
|
|
366
|
+
).not.toHaveProperty('terms')
|
|
367
|
+
expect(sanitizeManifest({ ...baseMan, terms: {} })).not.toHaveProperty(
|
|
368
|
+
'terms'
|
|
369
|
+
)
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
it('should keep complete terms', () => {
|
|
373
|
+
const completeTerms = {
|
|
374
|
+
id: 'mock-terms',
|
|
375
|
+
url: 'mock://terms',
|
|
376
|
+
version: 'mock001'
|
|
377
|
+
}
|
|
378
|
+
const man = { ...baseMan, terms: completeTerms }
|
|
379
|
+
|
|
380
|
+
expect(sanitizeManifest(man)).toEqual(expect.objectContaining(man))
|
|
381
|
+
})
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
describe('partnership', () => {
|
|
385
|
+
it('should remove incomplete', () => {
|
|
386
|
+
const incompletePartnership = {
|
|
387
|
+
icon: 'icon.svg' // icon is optional
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
expect(
|
|
391
|
+
sanitizeManifest({
|
|
392
|
+
...baseMan,
|
|
393
|
+
partnership: {}
|
|
394
|
+
})
|
|
395
|
+
).not.toHaveProperty('partnership')
|
|
396
|
+
|
|
397
|
+
expect(
|
|
398
|
+
sanitizeManifest({
|
|
399
|
+
...baseMan,
|
|
400
|
+
partnership: incompletePartnership
|
|
401
|
+
})
|
|
402
|
+
).not.toHaveProperty('partnership')
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
it('should keep complete', () => {
|
|
406
|
+
const completePartnership = {
|
|
407
|
+
description: 'A partnership text here' // description is mandatory
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const man = { ...baseMan, partnership: completePartnership }
|
|
411
|
+
expect(sanitizeManifest(man)).toEqual(expect.objectContaining(man))
|
|
412
|
+
})
|
|
413
|
+
})
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
describe('getIdentifier', () => {
|
|
417
|
+
it('should return field having role=identifier', () => {
|
|
418
|
+
const fields = {
|
|
419
|
+
username: {
|
|
420
|
+
type: 'text'
|
|
421
|
+
},
|
|
422
|
+
id: {
|
|
423
|
+
type: 'text',
|
|
424
|
+
role: 'identifier'
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
expect(getIdentifier(fields)).toBe('id')
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
it('should return the first field', () => {
|
|
431
|
+
const fields = {
|
|
432
|
+
username: {
|
|
433
|
+
type: 'text'
|
|
434
|
+
},
|
|
435
|
+
id: {
|
|
436
|
+
type: 'text'
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
expect(getIdentifier(fields)).toBe('username')
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
it('should return null if there is no match', () => {
|
|
444
|
+
expect(getIdentifier({})).toBe(null)
|
|
445
|
+
})
|
|
446
|
+
})
|
package/dist/types.js
CHANGED
|
@@ -34,6 +34,22 @@ var _dsl = require("./queries/dsl");
|
|
|
34
34
|
* @typedef {object} Token
|
|
35
35
|
* @typedef {object} ClientResponse
|
|
36
36
|
* @typedef {object} Manifest
|
|
37
|
+
* @typedef {object} SanitizedManifest
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @typedef {Object} ManifestField
|
|
42
|
+
* @property {String} [type] - field type : can be "text" or "hidden" or "date" or "dropdown" or "password"
|
|
43
|
+
* @property {String} [role] - field role : with "identifier" value
|
|
44
|
+
* @property {Boolean} [required] - is the field required or not
|
|
45
|
+
* @property {Boolean} [isRequired] - is the field required or not (legacy)
|
|
46
|
+
* @property {Boolean} [encrypted] - encrypted value of the field (legacy)
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @typedef {{
|
|
51
|
+
* [key: string]: ManifestField;
|
|
52
|
+
* }} ManifestFields
|
|
37
53
|
*/
|
|
38
54
|
|
|
39
55
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cozy-client",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "35.0.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "dist/index.node.js",
|
|
6
6
|
"browser": "dist/index.js",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"@types/jest": "^26.0.20",
|
|
19
19
|
"@types/lodash": "^4.14.170",
|
|
20
20
|
"btoa": "^1.2.1",
|
|
21
|
-
"cozy-stack-client": "^
|
|
21
|
+
"cozy-stack-client": "^35.0.0",
|
|
22
22
|
"date-fns": "2.29.3",
|
|
23
23
|
"json-stable-stringify": "^1.0.1",
|
|
24
24
|
"lodash": "^4.17.13",
|
|
@@ -73,5 +73,5 @@
|
|
|
73
73
|
"react-native-ios11-devicecheck": "https://github.com/cozy/react-native-devicecheck#app-attest-v0.1"
|
|
74
74
|
},
|
|
75
75
|
"sideEffects": false,
|
|
76
|
-
"gitHead": "
|
|
76
|
+
"gitHead": "261c75d0ee603b66b2da046a9d04d6876a17eec6"
|
|
77
77
|
}
|
package/types/CozyClient.d.ts
CHANGED
|
@@ -284,7 +284,7 @@ declare class CozyClient {
|
|
|
284
284
|
* Emits
|
|
285
285
|
*
|
|
286
286
|
* - "beforeLogout" at the beginning, before links have been reset
|
|
287
|
-
* - "
|
|
287
|
+
* - "logout" when the client is fully logged out and links have been reset
|
|
288
288
|
*
|
|
289
289
|
* @returns {Promise} - Resolves when all links have been reset and client is fully logged out
|
|
290
290
|
*/
|
package/types/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export { default as fetchPolicies } from "./policies";
|
|
|
13
13
|
export * from "./mock";
|
|
14
14
|
export * from "./hooks";
|
|
15
15
|
export { BulkEditError } from "./errors";
|
|
16
|
-
import * as manifest from "./manifest";
|
|
16
|
+
import * as manifest from "./models/manifest";
|
|
17
17
|
import * as models from "./models";
|
|
18
18
|
export { manifest, models };
|
|
19
19
|
export { QueryDefinition, Q, Mutations, MutationTypes, getDoctypeFromOperation } from "./queries/dsl";
|
package/types/index.node.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export * from "./mock";
|
|
|
9
9
|
export * from "./cli";
|
|
10
10
|
export { BulkEditError } from "./errors";
|
|
11
11
|
export { default as fetchPolicies } from "./policies";
|
|
12
|
-
import * as manifest from "./manifest";
|
|
12
|
+
import * as manifest from "./models/manifest";
|
|
13
13
|
import * as models from "./models";
|
|
14
14
|
export { manifest, models };
|
|
15
15
|
export { QueryDefinition, Mutations, MutationTypes, getDoctypeFromOperation, Q } from "./queries/dsl";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filters unauthorized categories. Defaults to ['others'] if no suitable category.
|
|
3
|
+
*
|
|
4
|
+
* @param {Array<Object>} categories - Array of categories
|
|
5
|
+
* @returns {Array<Object>} sanitized categories
|
|
6
|
+
*/
|
|
7
|
+
export function sanitizeCategories(categories: Array<any>): Array<any>;
|
|
8
|
+
export function areTermsValid(terms: any): boolean;
|
|
9
|
+
export function isPartnershipValid(partnership: any): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Normalize app manifest, retro-compatibility for old manifests
|
|
12
|
+
*
|
|
13
|
+
* @param {import('../types').Manifest} manifest - app manifest to normalize
|
|
14
|
+
* @returns {import('../types').SanitizedManifest}
|
|
15
|
+
*/
|
|
16
|
+
export function sanitize(manifest: import('../types').Manifest): import('../types').SanitizedManifest;
|
|
17
|
+
export const ROLE_IDENTIFIER: "identifier";
|
|
18
|
+
/**
|
|
19
|
+
* Legacy login fields declared by some konnectors
|
|
20
|
+
*/
|
|
21
|
+
export const legacyLoginFields: string[];
|
|
22
|
+
export function sanitizeIdentifier(fields: import('../types').ManifestFields): import('../types').ManifestFields;
|
|
23
|
+
export function getIdentifier(fields?: import('../types').ManifestFields): string | null;
|
package/types/types.d.ts
CHANGED
|
@@ -20,6 +20,32 @@ export type ReduxStore = any;
|
|
|
20
20
|
export type Token = any;
|
|
21
21
|
export type ClientResponse = any;
|
|
22
22
|
export type Manifest = any;
|
|
23
|
+
export type SanitizedManifest = any;
|
|
24
|
+
export type ManifestField = {
|
|
25
|
+
/**
|
|
26
|
+
* - field type : can be "text" or "hidden" or "date" or "dropdown" or "password"
|
|
27
|
+
*/
|
|
28
|
+
type?: string;
|
|
29
|
+
/**
|
|
30
|
+
* - field role : with "identifier" value
|
|
31
|
+
*/
|
|
32
|
+
role?: string;
|
|
33
|
+
/**
|
|
34
|
+
* - is the field required or not
|
|
35
|
+
*/
|
|
36
|
+
required?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* - is the field required or not (legacy)
|
|
39
|
+
*/
|
|
40
|
+
isRequired?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* - encrypted value of the field (legacy)
|
|
43
|
+
*/
|
|
44
|
+
encrypted?: boolean;
|
|
45
|
+
};
|
|
46
|
+
export type ManifestFields = {
|
|
47
|
+
[key: string]: ManifestField;
|
|
48
|
+
};
|
|
23
49
|
export type OldCozyClient = any;
|
|
24
50
|
export type NodeEnvironment = any;
|
|
25
51
|
export type QueryFetchStatus = "failed" | "loading" | "pending" | "loaded";
|
package/dist/manifest.js
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
-
|
|
5
|
-
Object.defineProperty(exports, "__esModule", {
|
|
6
|
-
value: true
|
|
7
|
-
});
|
|
8
|
-
exports.sanitizeCategories = sanitizeCategories;
|
|
9
|
-
exports.areTermsValid = areTermsValid;
|
|
10
|
-
exports.isPartnershipValid = isPartnershipValid;
|
|
11
|
-
exports.sanitize = sanitize;
|
|
12
|
-
|
|
13
|
-
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
14
|
-
|
|
15
|
-
var _types = require("./types");
|
|
16
|
-
|
|
17
|
-
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; }
|
|
18
|
-
|
|
19
|
-
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
|
|
20
|
-
|
|
21
|
-
var APP_CATEGORIES = ['banking', 'cozy', 'energy', 'health', 'host_provider', 'insurance', 'isp', 'mes_infos', 'online_services', 'others', 'partners', 'press', 'productivity', 'ptnb', 'public_service', 'shopping', 'social', 'telecom', 'transport'];
|
|
22
|
-
/** Filters unauthorized categories. Defaults to ['others'] if no suitable category. */
|
|
23
|
-
|
|
24
|
-
function sanitizeCategories(categories) {
|
|
25
|
-
if (!categories) return ['others'];
|
|
26
|
-
var filteredList = categories.filter(function (c) {
|
|
27
|
-
return APP_CATEGORIES.includes(c);
|
|
28
|
-
});
|
|
29
|
-
if (!filteredList.length) return ['others'];
|
|
30
|
-
return filteredList;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function areTermsValid(terms) {
|
|
34
|
-
return Boolean(terms && terms.id && terms.url && terms.version);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function isPartnershipValid(partnership) {
|
|
38
|
-
return Boolean(partnership && partnership.description);
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Normalize app manifest, retro-compatibility for old manifests
|
|
42
|
-
*
|
|
43
|
-
* @param {Manifest} manifest - app manifest to normalize
|
|
44
|
-
* @returns {Manifest}
|
|
45
|
-
*/
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
function sanitize(manifest) {
|
|
49
|
-
var sanitized = _objectSpread({}, manifest); // Make categories an array and delete category attribute if it exists
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (!manifest.categories && manifest.category && typeof manifest.category === 'string') {
|
|
53
|
-
sanitized.categories = [manifest.category];
|
|
54
|
-
delete sanitized.category;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
sanitized.categories = sanitizeCategories(sanitized.categories); // manifest name is not an object
|
|
58
|
-
|
|
59
|
-
if (typeof manifest.name === 'object') sanitized.name = manifest.name.en; // Fix camelCase from cozy-stack
|
|
60
|
-
|
|
61
|
-
if (manifest.available_version) {
|
|
62
|
-
sanitized.availableVersion = manifest.available_version;
|
|
63
|
-
delete sanitized.available_version;
|
|
64
|
-
} // Fix camelCase from cozy-stack
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (manifest.latest_version) {
|
|
68
|
-
sanitized.latestVersion = manifest.latestVersion;
|
|
69
|
-
delete sanitized.latest_version;
|
|
70
|
-
} // Remove invalid terms
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (sanitized.terms && !areTermsValid(sanitized.terms)) {
|
|
74
|
-
delete sanitized.terms;
|
|
75
|
-
} // Remove invalid partnership
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (sanitized.partnership && !isPartnershipValid(sanitized.partnership)) {
|
|
79
|
-
delete sanitized.partnership;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return sanitized;
|
|
83
|
-
}
|
package/dist/manifest.spec.js
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { sanitizeCategories, sanitize as sanitizeManifest } from './manifest'
|
|
2
|
-
|
|
3
|
-
describe('sanitizeCategories', () => {
|
|
4
|
-
it('should return the list of the provided expected categories correctly', () => {
|
|
5
|
-
// all of these categories is authorized by config/categories.json
|
|
6
|
-
// so it won't be filtered
|
|
7
|
-
const categories = [
|
|
8
|
-
'cozy',
|
|
9
|
-
'isp',
|
|
10
|
-
'partners',
|
|
11
|
-
'press',
|
|
12
|
-
'shopping',
|
|
13
|
-
'telecom'
|
|
14
|
-
]
|
|
15
|
-
expect(sanitizeCategories(categories)).toEqual(categories)
|
|
16
|
-
})
|
|
17
|
-
it('should filter unwanted categories', () => {
|
|
18
|
-
// pressingggg is not allowed as category
|
|
19
|
-
const categories = [
|
|
20
|
-
'cozy',
|
|
21
|
-
'isp',
|
|
22
|
-
'partners',
|
|
23
|
-
'pressingggg',
|
|
24
|
-
'shopping',
|
|
25
|
-
'telecom'
|
|
26
|
-
]
|
|
27
|
-
expect(sanitizeCategories(categories)).toEqual(
|
|
28
|
-
categories.filter(c => c !== 'pressingggg')
|
|
29
|
-
)
|
|
30
|
-
})
|
|
31
|
-
it('should return "others" as category if no categories provided', () => {
|
|
32
|
-
expect(sanitizeCategories([])).toEqual(['others'])
|
|
33
|
-
expect(sanitizeCategories(null)).toEqual(['others'])
|
|
34
|
-
})
|
|
35
|
-
it('should return "others" as category if categories after filtering is empty', () => {
|
|
36
|
-
// pressingggg is not allowed as category
|
|
37
|
-
expect(sanitizeCategories(['pressingggg'])).toEqual(['others'])
|
|
38
|
-
})
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
describe('sanitizeManifest', () => {
|
|
42
|
-
const baseMan = { slug: 'mock' }
|
|
43
|
-
|
|
44
|
-
it('should handle legacy category property', () => {
|
|
45
|
-
const testCategory = 'cozy'
|
|
46
|
-
expect(
|
|
47
|
-
sanitizeManifest({ slug: 'mock', category: testCategory })
|
|
48
|
-
).toStrictEqual({
|
|
49
|
-
slug: 'mock',
|
|
50
|
-
categories: [testCategory]
|
|
51
|
-
})
|
|
52
|
-
})
|
|
53
|
-
it('should handle legacy name property locales ("en" only)', () => {
|
|
54
|
-
const testName = 'Mock'
|
|
55
|
-
expect(
|
|
56
|
-
sanitizeManifest({
|
|
57
|
-
slug: 'mock',
|
|
58
|
-
name: {
|
|
59
|
-
en: testName
|
|
60
|
-
}
|
|
61
|
-
})
|
|
62
|
-
).toEqual(
|
|
63
|
-
expect.objectContaining({
|
|
64
|
-
slug: 'mock',
|
|
65
|
-
name: testName
|
|
66
|
-
})
|
|
67
|
-
)
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it('should transform available_version property to availableVersion', () => {
|
|
71
|
-
const testVersion = '3.0.0'
|
|
72
|
-
const man = sanitizeManifest({
|
|
73
|
-
slug: 'mock',
|
|
74
|
-
available_version: testVersion
|
|
75
|
-
})
|
|
76
|
-
expect(man.availableVersion).toBe('3.0.0')
|
|
77
|
-
expect(man).not.toHaveProperty('available_version')
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
describe('terms', () => {
|
|
81
|
-
it('should remove incomplete terms', () => {
|
|
82
|
-
const incompleteTerms = {
|
|
83
|
-
id: 'mock-terms'
|
|
84
|
-
}
|
|
85
|
-
expect(
|
|
86
|
-
sanitizeManifest({ ...baseMan, terms: incompleteTerms })
|
|
87
|
-
).not.toHaveProperty('terms')
|
|
88
|
-
expect(sanitizeManifest({ ...baseMan, terms: {} })).not.toHaveProperty(
|
|
89
|
-
'terms'
|
|
90
|
-
)
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
it('should keep complete terms', () => {
|
|
94
|
-
const completeTerms = {
|
|
95
|
-
id: 'mock-terms',
|
|
96
|
-
url: 'mock://terms',
|
|
97
|
-
version: 'mock001'
|
|
98
|
-
}
|
|
99
|
-
const man = { ...baseMan, terms: completeTerms }
|
|
100
|
-
|
|
101
|
-
expect(sanitizeManifest(man)).toEqual(expect.objectContaining(man))
|
|
102
|
-
})
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
describe('partnership', () => {
|
|
106
|
-
it('should remove incomplete', () => {
|
|
107
|
-
const incompletePartnership = {
|
|
108
|
-
icon: 'icon.svg' // icon is optional
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
expect(
|
|
112
|
-
sanitizeManifest({
|
|
113
|
-
...baseMan,
|
|
114
|
-
partnership: {}
|
|
115
|
-
})
|
|
116
|
-
).not.toHaveProperty('partnership')
|
|
117
|
-
|
|
118
|
-
expect(
|
|
119
|
-
sanitizeManifest({
|
|
120
|
-
...baseMan,
|
|
121
|
-
partnership: incompletePartnership
|
|
122
|
-
})
|
|
123
|
-
).not.toHaveProperty('partnership')
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
it('should keep complete', () => {
|
|
127
|
-
const completePartnership = {
|
|
128
|
-
description: 'A partnership text here' // description is mandatory
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const man = { ...baseMan, partnership: completePartnership }
|
|
132
|
-
expect(sanitizeManifest(man)).toEqual(expect.objectContaining(man))
|
|
133
|
-
})
|
|
134
|
-
})
|
|
135
|
-
})
|
package/types/manifest.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/** Filters unauthorized categories. Defaults to ['others'] if no suitable category. */
|
|
2
|
-
export function sanitizeCategories(categories: any): any;
|
|
3
|
-
export function areTermsValid(terms: any): boolean;
|
|
4
|
-
export function isPartnershipValid(partnership: any): boolean;
|
|
5
|
-
/**
|
|
6
|
-
* Normalize app manifest, retro-compatibility for old manifests
|
|
7
|
-
*
|
|
8
|
-
* @param {Manifest} manifest - app manifest to normalize
|
|
9
|
-
* @returns {Manifest}
|
|
10
|
-
*/
|
|
11
|
-
export function sanitize(manifest: Manifest): Manifest;
|
|
12
|
-
import { Manifest } from "./types";
|