cozy-client 60.31.0 → 60.32.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 +1 -1
- package/dist/models/__fixtures__/io.cozy.banners.json +56 -401
- package/dist/models/banner.js +221 -800
- package/dist/models/banner.spec.js +137 -517
- package/package.json +2 -2
- package/types/models/banner.d.ts +31 -137
package/dist/models/banner.js
CHANGED
|
@@ -5,9 +5,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", {
|
|
6
6
|
value: true
|
|
7
7
|
});
|
|
8
|
-
exports.dismiss = exports.getActiveBanner = exports.getActiveBanners = exports.
|
|
9
|
-
|
|
10
|
-
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
8
|
+
exports.dismiss = exports.getActiveBanner = exports.getActiveBanners = exports.BANNERS_DOCTYPE = void 0;
|
|
11
9
|
|
|
12
10
|
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
|
|
13
11
|
|
|
@@ -15,619 +13,164 @@ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/
|
|
|
15
13
|
|
|
16
14
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
17
15
|
|
|
18
|
-
var _isValid = _interopRequireDefault(require("date-fns/isValid"));
|
|
19
|
-
|
|
20
|
-
var _parseISO = _interopRequireDefault(require("date-fns/parseISO"));
|
|
21
|
-
|
|
22
16
|
var _dsl = require("../queries/dsl");
|
|
23
17
|
|
|
24
|
-
var _utils = require("./utils");
|
|
25
|
-
|
|
26
18
|
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; }
|
|
27
19
|
|
|
28
20
|
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; }
|
|
29
21
|
|
|
30
|
-
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; } } }; }
|
|
31
|
-
|
|
32
|
-
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); }
|
|
33
|
-
|
|
34
|
-
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; }
|
|
35
|
-
|
|
36
22
|
var BANNERS_DOCTYPE = 'io.cozy.banners';
|
|
37
|
-
/**
|
|
38
|
-
* The version of the io.cozy.banners shape this code was written against.
|
|
39
|
-
* A document declaring a higher cozyMetadata.doctypeVersion is skipped.
|
|
40
|
-
*/
|
|
41
|
-
|
|
42
23
|
exports.BANNERS_DOCTYPE = BANNERS_DOCTYPE;
|
|
43
|
-
var SUPPORTED_DOCTYPE_VERSION = 1;
|
|
44
|
-
/**
|
|
45
|
-
* The severities the doctype defines. A value outside this list is rendered
|
|
46
|
-
* with DEFAULT_SEVERITY. Frozen because the fallbacks read it at call time, so
|
|
47
|
-
* a consumer mutating it would change what every other consumer renders.
|
|
48
|
-
*
|
|
49
|
-
* @type {readonly BannerSeverity[]}
|
|
50
|
-
*/
|
|
51
|
-
|
|
52
|
-
exports.SUPPORTED_DOCTYPE_VERSION = SUPPORTED_DOCTYPE_VERSION;
|
|
53
24
|
var KNOWN_SEVERITIES = ['info', 'warning', 'error'];
|
|
54
|
-
exports.KNOWN_SEVERITIES = KNOWN_SEVERITIES;
|
|
55
|
-
Object.freeze(KNOWN_SEVERITIES);
|
|
56
|
-
/**
|
|
57
|
-
* The surfaces the doctype defines. A value outside this list is rendered on
|
|
58
|
-
* DEFAULT_SURFACE. Frozen for the same reason as KNOWN_SEVERITIES.
|
|
59
|
-
*
|
|
60
|
-
* @type {readonly BannerSurface[]}
|
|
61
|
-
*/
|
|
62
|
-
|
|
63
25
|
var KNOWN_SURFACES = ['banner', 'modal'];
|
|
64
|
-
exports.KNOWN_SURFACES = KNOWN_SURFACES;
|
|
65
|
-
Object.freeze(KNOWN_SURFACES);
|
|
66
|
-
/** @type {BannerSeverity} */
|
|
67
|
-
|
|
68
|
-
var DEFAULT_SEVERITY = 'warning';
|
|
69
|
-
/** @type {BannerSurface} */
|
|
70
|
-
|
|
71
|
-
var DEFAULT_SURFACE = 'banner';
|
|
72
|
-
var STACK_APP = 'stack';
|
|
73
26
|
/**
|
|
74
27
|
* @typedef {object} BannerCta
|
|
75
|
-
* @property {string} label - The label, plain text
|
|
76
|
-
* @property {string} url -
|
|
77
|
-
*/
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* @typedef {'quota'|'billing'|'trial'|'account'|'system'} BannerCategory
|
|
81
|
-
*/
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* @typedef {'info'|'warning'|'error'} BannerSeverity
|
|
85
|
-
*/
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* @typedef {'banner'|'modal'} BannerSurface
|
|
89
|
-
*/
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* @typedef {object} VisibleBannersOptions
|
|
93
|
-
* @property {Date} [now] - The current time, defaults to now
|
|
94
|
-
* @property {number} [supportedDoctypeVersion] - The highest version this client handles
|
|
95
|
-
*/
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* @typedef {import("../CozyClient").default} BannerClient A CozyClient instance
|
|
99
|
-
*/
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* @typedef {object} DismissOptions
|
|
103
|
-
* @property {string} [dismissedAt] - The timestamp to record, defaults to now
|
|
28
|
+
* @property {string} label - The label, plain text
|
|
29
|
+
* @property {string} url - Where the label points
|
|
104
30
|
*/
|
|
105
31
|
|
|
106
32
|
/**
|
|
107
33
|
* @typedef {object} Banner An io.cozy.banners document
|
|
108
|
-
* @property {string} [_id] - Identifier of the document
|
|
109
|
-
* @property {string} [_rev] - Revision identifier of the document
|
|
34
|
+
* @property {string} [_id] - Identifier of the document
|
|
110
35
|
* @property {string} bannerId - Identifier of the banner itself
|
|
111
|
-
* @property {
|
|
112
|
-
* @property {
|
|
113
|
-
* @property {
|
|
114
|
-
* @property {string}
|
|
115
|
-
* @property {string}
|
|
116
|
-
* @property {
|
|
36
|
+
* @property {string} category - What the banner is about
|
|
37
|
+
* @property {'info'|'warning'|'error'} severity - How loudly to render it
|
|
38
|
+
* @property {'banner'|'modal'} surface - Where to render it
|
|
39
|
+
* @property {string} [title] - Optional heading, used on the modal surface
|
|
40
|
+
* @property {string} text - The message, already localized
|
|
41
|
+
* @property {string} lang - BCP 47 tag of the language `text` and `cta.label` are in
|
|
42
|
+
* @property {BannerCta} [cta] - Optional call to action
|
|
43
|
+
* @property {BannerCta} [secondaryCta] - Optional lesser action, needs a cta
|
|
117
44
|
* @property {boolean} dismissible - Whether the client offers a dismiss control
|
|
118
|
-
* @property {string
|
|
45
|
+
* @property {string} [dismissedAt] - When the user dismissed it
|
|
119
46
|
* @property {number} priority - Sort order, highest first
|
|
120
47
|
* @property {string} startsAt - Start of the validity window, inclusive
|
|
121
|
-
* @property {string
|
|
122
|
-
* @property {object} [source] - What produced the document
|
|
123
|
-
* @property {object} [cozyMetadata] - Document lifecycle metadata
|
|
48
|
+
* @property {string} [endsAt] - End of the validity window, exclusive
|
|
124
49
|
*/
|
|
125
50
|
|
|
126
51
|
/**
|
|
127
|
-
* The
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*/
|
|
133
|
-
|
|
134
|
-
var BANNERS_QUERY_NAME = "".concat(BANNERS_DOCTYPE, "/all");
|
|
135
|
-
/**
|
|
136
|
-
* Query for the banners of the current instance.
|
|
137
|
-
*
|
|
138
|
-
* The query is named so every consumer shares one store entry rather than
|
|
139
|
-
* minting a new one per call, and the limit is lifted because the whole
|
|
140
|
-
* collection is read in full.
|
|
141
|
-
*
|
|
142
|
-
* @returns {import('../types').Query} A named query
|
|
143
|
-
*/
|
|
144
|
-
|
|
145
|
-
exports.BANNERS_QUERY_NAME = BANNERS_QUERY_NAME;
|
|
146
|
-
|
|
147
|
-
var buildBannersQuery = function buildBannersQuery() {
|
|
148
|
-
return {
|
|
149
|
-
definition: (0, _dsl.Q)(BANNERS_DOCTYPE).UNSAFE_noLimit(),
|
|
150
|
-
options: {
|
|
151
|
-
as: BANNERS_QUERY_NAME
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
};
|
|
155
|
-
/**
|
|
156
|
-
* Documents are only trusted when the stack wrote them. Permissions scope by
|
|
157
|
-
* doctype and verb, never by field, so any application allowed to record a
|
|
158
|
-
* dismissal is also able to author a document that looks like a platform
|
|
159
|
-
* message.
|
|
160
|
-
*
|
|
161
|
-
* This is not a security boundary, and should not be read as one. The stack
|
|
162
|
-
* does not own this field: cozy-client keeps whatever `cozyMetadata` the caller
|
|
163
|
-
* passes (CozyClient.js, ensureCozyMetadata spreads it last), so an application
|
|
164
|
-
* can set `createdByApp: 'stack'` on a document it authored itself and pass
|
|
165
|
-
* this check. It catches an application writing under its own name, which is
|
|
166
|
-
* the ordinary mistake, not one deliberately impersonating the stack. Enforcing
|
|
167
|
-
* that needs the doctype to be stack write only on the server side.
|
|
168
|
-
*
|
|
169
|
-
* It also only speaks to who created the document. An application holding write
|
|
170
|
-
* permission can rewrite the fields of a stack authored one, which the platform
|
|
171
|
-
* cannot prevent, so a client escapes text and never interprets markup in it.
|
|
172
|
-
*
|
|
173
|
-
* @param {Banner} banner - The document to check
|
|
174
|
-
* @returns {boolean} True when the stack is the author
|
|
175
|
-
*/
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
exports.buildBannersQuery = buildBannersQuery;
|
|
179
|
-
|
|
180
|
-
var isTrusted = function isTrusted(banner) {
|
|
181
|
-
return (0, _utils.getCreatedByApp)(banner) === STACK_APP;
|
|
182
|
-
};
|
|
183
|
-
/**
|
|
184
|
-
* cozy-stack writes numbers as strings in places, `cozyMetadata` declares
|
|
185
|
-
* `doctypeVersion` that way platform wide, so a value is parsed rather than
|
|
186
|
-
* compared as it comes. `Number()` alone is not enough: it maps null, "", false
|
|
187
|
-
* and [] to a finite 0, which would read as a real version or a real priority
|
|
188
|
-
* rather than as an unusable value, so only a number or a non blank string is
|
|
189
|
-
* parsed at all.
|
|
190
|
-
*
|
|
191
|
-
* @param {any} value - The value to read
|
|
192
|
-
* @param {number} fallback - What an absent or unusable value means
|
|
193
|
-
* @returns {number} The parsed number, or the fallback
|
|
194
|
-
*/
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
exports.isTrusted = isTrusted;
|
|
198
|
-
|
|
199
|
-
var toNumber = function toNumber(value, fallback) {
|
|
200
|
-
var parsed = typeof value === 'number' || typeof value === 'string' && value.trim() !== '' ? Number(value) : NaN;
|
|
201
|
-
return Number.isFinite(parsed) ? parsed : fallback;
|
|
202
|
-
};
|
|
203
|
-
/**
|
|
204
|
-
* @param {Banner} banner - The document to read
|
|
205
|
-
* @returns {number} The version, defaulting to 1 for a missing or unusable one
|
|
206
|
-
*/
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
var doctypeVersion = function doctypeVersion(banner) {
|
|
210
|
-
var _banner$cozyMetadata;
|
|
211
|
-
|
|
212
|
-
return toNumber(banner === null || banner === void 0 ? void 0 : (_banner$cozyMetadata = banner.cozyMetadata) === null || _banner$cozyMetadata === void 0 ? void 0 : _banner$cozyMetadata.doctypeVersion, 1);
|
|
213
|
-
};
|
|
214
|
-
/**
|
|
215
|
-
* @param {Banner} banner - The document to check
|
|
216
|
-
* @param {number} [supportedVersion] - The highest version this client handles
|
|
217
|
-
* @returns {boolean} True when the shape is one this client can read
|
|
218
|
-
*/
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
var isSupported = function isSupported(banner) {
|
|
222
|
-
var supportedVersion = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : SUPPORTED_DOCTYPE_VERSION;
|
|
223
|
-
return doctypeVersion(banner) <= supportedVersion;
|
|
224
|
-
};
|
|
225
|
-
/**
|
|
226
|
-
* A null value and an absent key mean the same thing for dismissedAt and
|
|
227
|
-
* endsAt, and `new Date(null)` is the Unix epoch rather than "no date", so
|
|
228
|
-
* every date is null checked before being parsed.
|
|
229
|
-
*
|
|
230
|
-
* Returns a timestamp rather than a Date so the comparisons stay primitive,
|
|
231
|
-
* and NaN when the value is present but unparseable, which the callers reject
|
|
232
|
-
* rather than silently treating as "no bound".
|
|
233
|
-
*
|
|
234
|
-
* This is the only date parser in the file. `Date.parse` is deliberately not
|
|
235
|
-
* used anywhere: it accepts implementation defined formats that `parseISO`
|
|
236
|
-
* rejects, and the two disagree by the local UTC offset on a date only value,
|
|
237
|
-
* so mixing them would give one document two different instants.
|
|
238
|
-
*
|
|
239
|
-
* @param {string|null|undefined} value - An ISO 8601 timestamp
|
|
240
|
-
* @returns {number|null} The timestamp, NaN when malformed, null when absent
|
|
52
|
+
* The doctype writes an ISO 8601 timestamp with an explicit offset, and nothing
|
|
53
|
+
* else parses. `Date.parse` on its own falls back to the engine's legacy parser
|
|
54
|
+
* for anything it does not recognise, so a malformed bound would resolve to a
|
|
55
|
+
* real instant in one browser and to NaN in another, and the shared contract
|
|
56
|
+
* vectors could no longer say what every client displays.
|
|
241
57
|
*/
|
|
242
58
|
|
|
243
|
-
|
|
244
|
-
|
|
59
|
+
var ISO_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
|
|
60
|
+
/** A bound that is absent leaves that end of the window open, unlike an unparseable one. */
|
|
245
61
|
|
|
246
62
|
var toTime = function toTime(value) {
|
|
247
|
-
if (value
|
|
248
|
-
|
|
249
|
-
return
|
|
63
|
+
if (value == null) return null;
|
|
64
|
+
if (typeof value !== 'string' || !ISO_TIMESTAMP.test(value)) return NaN;
|
|
65
|
+
return Date.parse(value);
|
|
250
66
|
};
|
|
251
|
-
/**
|
|
252
|
-
* @param {Banner} banner - The document to check
|
|
253
|
-
* @returns {boolean} True when the user dismissed it
|
|
254
|
-
*/
|
|
255
|
-
|
|
256
67
|
|
|
257
68
|
var isDismissed = function isDismissed(banner) {
|
|
258
69
|
return (banner === null || banner === void 0 ? void 0 : banner.dismissedAt) != null;
|
|
259
70
|
};
|
|
260
|
-
/**
|
|
261
|
-
* The window is inclusive on startsAt and exclusive on endsAt, and an absent
|
|
262
|
-
* endsAt is open ended. A malformed bound hides the banner: a document whose
|
|
263
|
-
* window cannot be read is not one a client can decide to show.
|
|
264
|
-
*
|
|
265
|
-
* An absent startsAt hides it too. The doctype makes cta, dismissedAt and
|
|
266
|
-
* endsAt the only fields that may be missing, so a document with no lower bound
|
|
267
|
-
* is not open ended, it is unreadable. Treating it as "no bound" would make the
|
|
268
|
-
* missing field more permissive than a malformed one, and would let anything
|
|
269
|
-
* able to rewrite the document turn a scheduled banner into a permanent one by
|
|
270
|
-
* deleting both bounds.
|
|
271
|
-
*
|
|
272
|
-
* @param {Banner} banner - The document to check
|
|
273
|
-
* @param {Date} [now] - The current time
|
|
274
|
-
* @returns {boolean} True when the banner applies at that time
|
|
275
|
-
*/
|
|
276
|
-
|
|
71
|
+
/** The window is inclusive on startsAt, exclusive on endsAt, open when absent. */
|
|
277
72
|
|
|
278
|
-
exports.isDismissed = isDismissed;
|
|
279
73
|
|
|
280
|
-
var isInWindow = function isInWindow(banner) {
|
|
281
|
-
var now = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Date();
|
|
74
|
+
var isInWindow = function isInWindow(banner, now) {
|
|
282
75
|
var startsAt = toTime(banner === null || banner === void 0 ? void 0 : banner.startsAt);
|
|
283
76
|
var endsAt = toTime(banner === null || banner === void 0 ? void 0 : banner.endsAt);
|
|
284
|
-
if (startsAt === null || Number.isNaN(startsAt)) return false;
|
|
285
|
-
if (Number.isNaN(endsAt)) return false;
|
|
286
|
-
var at = now.getTime();
|
|
287
|
-
if (Number.isNaN(at)) return false;
|
|
288
|
-
if (at < startsAt) return false;
|
|
289
|
-
if (endsAt !== null && at >= endsAt) return false;
|
|
290
|
-
return true;
|
|
291
|
-
};
|
|
292
|
-
|
|
293
|
-
exports.isInWindow = isInWindow;
|
|
294
|
-
|
|
295
|
-
var priorityOf = function priorityOf(banner) {
|
|
296
|
-
return toNumber(banner === null || banner === void 0 ? void 0 : banner.priority, 0);
|
|
297
|
-
};
|
|
298
|
-
/**
|
|
299
|
-
* Highest priority first, then bannerId ascending so every client orders
|
|
300
|
-
* identically when priorities are equal.
|
|
301
|
-
*
|
|
302
|
-
* The tie break compares code units rather than calling localeCompare, whose
|
|
303
|
-
* result depends on the runtime's locale and ICU build. A missing or non
|
|
304
|
-
* numeric priority sorts as 0 instead of producing NaN, which would make the
|
|
305
|
-
* comparator intransitive and the sort order engine defined.
|
|
306
|
-
*
|
|
307
|
-
* Documents that lost their bannerId fall back on _id, as the deduplication
|
|
308
|
-
* does, so two of them do not compare equal and land in whatever order the
|
|
309
|
-
* database returned them in.
|
|
310
|
-
*
|
|
311
|
-
* @param {Banner} a - A banner
|
|
312
|
-
* @param {Banner} b - Another banner
|
|
313
|
-
* @returns {number} The comparison result
|
|
314
|
-
*/
|
|
315
77
|
|
|
316
|
-
|
|
317
|
-
var byPriority = function byPriority(a, b) {
|
|
318
|
-
var _ref, _a$bannerId, _ref2, _b$bannerId;
|
|
319
|
-
|
|
320
|
-
var byRank = priorityOf(b) - priorityOf(a);
|
|
321
|
-
if (byRank !== 0) return byRank;
|
|
322
|
-
var idA = String((_ref = (_a$bannerId = a === null || a === void 0 ? void 0 : a.bannerId) !== null && _a$bannerId !== void 0 ? _a$bannerId : a === null || a === void 0 ? void 0 : a._id) !== null && _ref !== void 0 ? _ref : '');
|
|
323
|
-
var idB = String((_ref2 = (_b$bannerId = b === null || b === void 0 ? void 0 : b.bannerId) !== null && _b$bannerId !== void 0 ? _b$bannerId : b === null || b === void 0 ? void 0 : b._id) !== null && _ref2 !== void 0 ? _ref2 : '');
|
|
324
|
-
if (idA < idB) return -1;
|
|
325
|
-
if (idA > idB) return 1;
|
|
326
|
-
return 0;
|
|
327
|
-
};
|
|
328
|
-
/**
|
|
329
|
-
* @param {Banner} banner - The document to read
|
|
330
|
-
* @returns {BannerSeverity} The severity to render, falling back when unknown
|
|
331
|
-
*/
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
exports.byPriority = byPriority;
|
|
335
|
-
|
|
336
|
-
var getSeverity = function getSeverity(banner) {
|
|
337
|
-
return KNOWN_SEVERITIES.includes(banner === null || banner === void 0 ? void 0 : banner.severity) ? banner.severity : DEFAULT_SEVERITY;
|
|
338
|
-
};
|
|
339
|
-
/**
|
|
340
|
-
* @param {Banner} banner - The document to read
|
|
341
|
-
* @returns {BannerSurface} The surface to render on, falling back when unknown
|
|
342
|
-
*/
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
exports.getSeverity = getSeverity;
|
|
346
|
-
|
|
347
|
-
var getSurface = function getSurface(banner) {
|
|
348
|
-
return KNOWN_SURFACES.includes(banner === null || banner === void 0 ? void 0 : banner.surface) ? banner.surface : DEFAULT_SURFACE;
|
|
349
|
-
};
|
|
350
|
-
/**
|
|
351
|
-
* Whitespace plus the zero width characters `trim` leaves behind. A label made
|
|
352
|
-
* only of these renders as a button with nothing on it. They are stripped to
|
|
353
|
-
* decide whether the label is empty, never from the label itself, so a script
|
|
354
|
-
* that uses a zero width joiner between glyphs keeps it.
|
|
355
|
-
*/
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
exports.getSurface = getSurface;
|
|
359
|
-
var BLANK = /[\s\u200B-\u200D\uFEFF]/g;
|
|
360
|
-
/**
|
|
361
|
-
* A call to action is only rendered when it carries a label and points at
|
|
362
|
-
* https, so a locally authored document cannot turn into a platform styled
|
|
363
|
-
* link on any scheme, and no banner renders a button the user cannot read.
|
|
364
|
-
* The scheme is matched case insensitively since URL schemes are.
|
|
365
|
-
*
|
|
366
|
-
* @param {Banner} banner - The document to read
|
|
367
|
-
* @returns {BannerCta|null} The call to action, or null when there is none to show
|
|
368
|
-
*/
|
|
369
|
-
|
|
370
|
-
var getCta = function getCta(banner) {
|
|
371
|
-
var cta = banner === null || banner === void 0 ? void 0 : banner.cta;
|
|
372
|
-
if (!cta || typeof cta.url !== 'string') return null;
|
|
373
|
-
|
|
374
|
-
if (typeof cta.label !== 'string' || cta.label.replace(BLANK, '') === '') {
|
|
375
|
-
return null;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
return isHttpsUrl(cta.url) ? cta : null;
|
|
379
|
-
};
|
|
380
|
-
/**
|
|
381
|
-
* A prefix check accepts "https://" and "https:example.org", which do not point
|
|
382
|
-
* where they look like they point, so the value is parsed instead. The parser
|
|
383
|
-
* rejects an https URL with no host outright, so reaching the scheme comparison
|
|
384
|
-
* already means there is one.
|
|
385
|
-
*
|
|
386
|
-
* @param {string} raw - The candidate URL
|
|
387
|
-
* @returns {boolean} True when it is an absolute https URL
|
|
388
|
-
*/
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
exports.getCta = getCta;
|
|
392
|
-
|
|
393
|
-
var isHttpsUrl = function isHttpsUrl(raw) {
|
|
394
|
-
try {
|
|
395
|
-
return new URL(raw).protocol === 'https:';
|
|
396
|
-
} catch (error) {
|
|
78
|
+
if (startsAt === null || Number.isNaN(startsAt) || Number.isNaN(endsAt)) {
|
|
397
79
|
return false;
|
|
398
80
|
}
|
|
399
|
-
};
|
|
400
|
-
|
|
401
|
-
var updatedAtOf = function updatedAtOf(banner) {
|
|
402
|
-
var _banner$cozyMetadata2;
|
|
403
|
-
|
|
404
|
-
var at = toTime(banner === null || banner === void 0 ? void 0 : (_banner$cozyMetadata2 = banner.cozyMetadata) === null || _banner$cozyMetadata2 === void 0 ? void 0 : _banner$cozyMetadata2.updatedAt);
|
|
405
|
-
return Number.isFinite(at) ? at : 0;
|
|
406
|
-
};
|
|
407
|
-
/**
|
|
408
|
-
* Orders two documents carrying the same bannerId, most relevant first. The
|
|
409
|
-
* highest supported doctypeVersion wins; equal versions fall back on the most
|
|
410
|
-
* recently written document and then on _id, so the winner never depends on
|
|
411
|
-
* the order the rows came back in.
|
|
412
|
-
*
|
|
413
|
-
* @param {Banner} candidate - The document being considered
|
|
414
|
-
* @param {Banner} current - The document currently held
|
|
415
|
-
* @returns {boolean} True when candidate should replace current
|
|
416
|
-
*/
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
var supersedes = function supersedes(candidate, current) {
|
|
420
|
-
var _candidate$_id, _current$_id;
|
|
421
81
|
|
|
422
|
-
var
|
|
423
|
-
if (
|
|
424
|
-
|
|
425
|
-
if (byUpdate !== 0) return byUpdate > 0;
|
|
426
|
-
return String((_candidate$_id = candidate === null || candidate === void 0 ? void 0 : candidate._id) !== null && _candidate$_id !== void 0 ? _candidate$_id : '') < String((_current$_id = current === null || current === void 0 ? void 0 : current._id) !== null && _current$_id !== void 0 ? _current$_id : '');
|
|
82
|
+
var at = now.getTime();
|
|
83
|
+
if (at < startsAt) return false;
|
|
84
|
+
return endsAt === null || at < endsAt;
|
|
427
85
|
};
|
|
428
86
|
/**
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
*
|
|
432
|
-
* document carrying neither keys on itself, so unrelated ones do not collapse
|
|
433
|
-
* onto a single `undefined` key.
|
|
434
|
-
*
|
|
435
|
-
* @param {Banner} banner - The document to key
|
|
436
|
-
* @returns {string|Banner} The grouping key
|
|
87
|
+
* Highest priority first, then bannerId, compared code unit by code unit so
|
|
88
|
+
* that every client orders equal priorities the same way. `localeCompare`
|
|
89
|
+
* would depend on the runtime locale.
|
|
437
90
|
*/
|
|
438
91
|
|
|
439
92
|
|
|
440
|
-
var
|
|
441
|
-
|
|
442
|
-
if ((banner === null || banner === void 0 ? void 0 : banner._id) != null) return "_id:".concat(banner._id);
|
|
443
|
-
return banner;
|
|
93
|
+
var rankOf = function rankOf(banner) {
|
|
94
|
+
return Number.isFinite(banner === null || banner === void 0 ? void 0 : banner.priority) ? banner.priority : 0;
|
|
444
95
|
};
|
|
445
|
-
/**
|
|
446
|
-
* Keeps the highest supported version of each bannerId. Without this the
|
|
447
|
-
* documents materialized on both sides of a version bump render twice.
|
|
448
|
-
*
|
|
449
|
-
* @param {Banner[]} banners - The documents to reduce
|
|
450
|
-
* @returns {Banner[]} One document per bannerId
|
|
451
|
-
*/
|
|
452
96
|
|
|
453
|
-
|
|
454
|
-
var
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
try {
|
|
461
|
-
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
462
|
-
var banner = _step.value;
|
|
463
|
-
var key = groupKey(banner);
|
|
464
|
-
var current = best.get(key);
|
|
465
|
-
|
|
466
|
-
if (current === undefined || supersedes(banner, current)) {
|
|
467
|
-
best.set(key, banner);
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
} catch (err) {
|
|
471
|
-
_iterator.e(err);
|
|
472
|
-
} finally {
|
|
473
|
-
_iterator.f();
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
return Array.from(best.values());
|
|
97
|
+
var byPriority = function byPriority(a, b) {
|
|
98
|
+
var byRank = rankOf(b) - rankOf(a);
|
|
99
|
+
if (byRank !== 0) return byRank;
|
|
100
|
+
if (a.bannerId < b.bannerId) return -1;
|
|
101
|
+
if (a.bannerId > b.bannerId) return 1;
|
|
102
|
+
return 0;
|
|
477
103
|
};
|
|
478
104
|
/**
|
|
479
|
-
* The
|
|
480
|
-
*
|
|
481
|
-
*
|
|
482
|
-
*
|
|
483
|
-
* client moves to another. Deduplication runs last, on the documents that are
|
|
484
|
-
* actually renderable, so a newer version sitting outside its validity window
|
|
485
|
-
* never suppresses the older one that is still live.
|
|
486
|
-
*
|
|
487
|
-
* @param {Banner[]} banners - The documents read from the doctype
|
|
488
|
-
* @param {VisibleBannersOptions} [options] - Options
|
|
489
|
-
* @returns {Banner[]} The visible banners, ordered
|
|
105
|
+
* The doctype specifies an absolute `https://` URL, so anything else is dropped
|
|
106
|
+
* rather than handed to a client that would put it in an `href`: the documents
|
|
107
|
+
* live in a database the applications can write, and a `javascript:` URL there
|
|
108
|
+
* would run in the origin of whichever application rendered the banner.
|
|
490
109
|
*/
|
|
491
110
|
|
|
492
111
|
|
|
493
|
-
var
|
|
494
|
-
|
|
495
|
-
var now = options.now instanceof Date && !Number.isNaN(options.now.getTime()) ? options.now : new Date(); // Validated rather than defaulted with ??, which only guards null and
|
|
496
|
-
// undefined: a NaN reaching isSupported makes every comparison false and
|
|
497
|
-
// hides every banner with no error.
|
|
498
|
-
|
|
499
|
-
var supportedVersion = toNumber(options.supportedDoctypeVersion, SUPPORTED_DOCTYPE_VERSION); // Only documents the stack wrote are read at all, dismissals included: a
|
|
500
|
-
// dismissal on a document an application authored must not hide a platform
|
|
501
|
-
// message.
|
|
502
|
-
|
|
503
|
-
var trusted = (banners !== null && banners !== void 0 ? banners : []).filter(isTrusted); // Collected before the version filter: a dismissal recorded by a client on a
|
|
504
|
-
// newer version of the document still applies to a client that only reads the
|
|
505
|
-
// older one, which would otherwise render a banner the user has closed.
|
|
506
|
-
|
|
507
|
-
var dismissed = new Set(trusted.filter(isDismissed).map(groupKey));
|
|
508
|
-
return keepHighestVersion(trusted.filter(function (banner) {
|
|
509
|
-
return isSupported(banner, supportedVersion) && !dismissed.has(groupKey(banner)) && isInWindow(banner, now);
|
|
510
|
-
})).sort(byPriority).map(resolve);
|
|
112
|
+
var isSafeCta = function isSafeCta(cta) {
|
|
113
|
+
return typeof (cta === null || cta === void 0 ? void 0 : cta.url) === 'string' && /^https:\/\//i.test(cta.url);
|
|
511
114
|
};
|
|
512
115
|
/**
|
|
513
|
-
*
|
|
514
|
-
*
|
|
515
|
-
* surface is already replaced, and a call to action that is not an absolute
|
|
516
|
-
* https URL is already dropped.
|
|
517
|
-
*
|
|
518
|
-
* The doctype guarantees that a non dismissible banner on the modal surface
|
|
519
|
-
* carries a call to action, so the user is never left without a way out.
|
|
520
|
-
* Dropping an unusable one would break that guarantee and paint a blocking
|
|
521
|
-
* dialog with no dismiss control and no button, so the banner falls back to the
|
|
522
|
-
* surface that is never blocking.
|
|
523
|
-
*
|
|
524
|
-
* The document is returned unchanged when no fallback applied. The store relies
|
|
525
|
-
* on referential equality, and copying every banner on every read would make a
|
|
526
|
-
* memoized renderer repaint on every poll tick. An absent cta is not left
|
|
527
|
-
* absent though: the resolved shape always carries the key, so a caller reads
|
|
528
|
-
* one value rather than checking for both null and undefined.
|
|
529
|
-
*
|
|
530
|
-
* @param {Banner} banner - A visible banner
|
|
531
|
-
* @returns {Banner} The same banner, ready to render
|
|
116
|
+
* The doctype allows a new severity or surface without a version bump, so a
|
|
117
|
+
* value this client does not know is rendered rather than dropped.
|
|
532
118
|
*/
|
|
533
119
|
|
|
534
120
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
var resolve = function resolve(banner) {
|
|
538
|
-
var severity = getSeverity(banner);
|
|
539
|
-
var cta = getCta(banner);
|
|
540
|
-
var surface = getSurface(banner) === 'modal' && cta === null && !(banner !== null && banner !== void 0 && banner.dismissible) ? DEFAULT_SURFACE : getSurface(banner);
|
|
541
|
-
|
|
542
|
-
if (severity === (banner === null || banner === void 0 ? void 0 : banner.severity) && surface === (banner === null || banner === void 0 ? void 0 : banner.surface) && cta === (banner === null || banner === void 0 ? void 0 : banner.cta)) {
|
|
543
|
-
return banner;
|
|
544
|
-
}
|
|
545
|
-
|
|
121
|
+
var withFallbacks = function withFallbacks(banner) {
|
|
122
|
+
var cta = isSafeCta(banner.cta) ? banner.cta : undefined;
|
|
546
123
|
return _objectSpread(_objectSpread({}, banner), {}, {
|
|
547
|
-
severity: severity,
|
|
548
|
-
surface: surface,
|
|
549
|
-
cta: cta
|
|
124
|
+
severity: KNOWN_SEVERITIES.includes(banner.severity) ? banner.severity : 'warning',
|
|
125
|
+
surface: KNOWN_SURFACES.includes(banner.surface) ? banner.surface : 'banner',
|
|
126
|
+
cta: cta,
|
|
127
|
+
// The secondary action is the lesser one, so it is never promoted to the
|
|
128
|
+
// only thing on offer: without a primary it is dropped rather than shown.
|
|
129
|
+
secondaryCta: cta && isSafeCta(banner.secondaryCta) ? banner.secondaryCta : undefined
|
|
550
130
|
});
|
|
551
131
|
};
|
|
552
132
|
/**
|
|
553
|
-
*
|
|
554
|
-
*
|
|
555
|
-
* rewraps into a plain Error that only keeps the message.
|
|
556
|
-
*
|
|
557
|
-
* @param {Error & {status?: number}} error - The error to classify
|
|
558
|
-
* @returns {boolean} True when the write lost a revision race
|
|
133
|
+
* `client.query` resolves undefined instead of rejecting when the client has an
|
|
134
|
+
* `onError` callback, which would otherwise read as "no banners apply".
|
|
559
135
|
*/
|
|
560
136
|
|
|
561
137
|
|
|
562
|
-
var
|
|
563
|
-
var _error$message;
|
|
564
|
-
|
|
565
|
-
return (error === null || error === void 0 ? void 0 : error.status) === 409 || /Document update conflict/.test((_error$message = error === null || error === void 0 ? void 0 : error.message) !== null && _error$message !== void 0 ? _error$message : '');
|
|
566
|
-
};
|
|
567
|
-
/**
|
|
568
|
-
* `client.query` does not always resolve a response. It resolves undefined when
|
|
569
|
-
* the client was built with an `onError` callback, which swallows the rejection,
|
|
570
|
-
* and when a query named the same as this one is already in flight with a
|
|
571
|
-
* different definition. Both are indistinguishable from an empty collection
|
|
572
|
-
* once the response is read, so they are rejected here instead: reporting a
|
|
573
|
-
* failed read as "there is nothing" hides a banner the user should see, and
|
|
574
|
-
* reports a dismissal that never happened as done.
|
|
575
|
-
*
|
|
576
|
-
* @param {object} response - The response of a query
|
|
577
|
-
* @returns {object} The same response
|
|
578
|
-
* @throws {Error} When the query resolved nothing
|
|
579
|
-
*/
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
var requireResponse = function requireResponse(response) {
|
|
138
|
+
var readData = function readData(response) {
|
|
583
139
|
if (response === null || response === undefined) {
|
|
584
|
-
throw new Error("The ".concat(BANNERS_DOCTYPE, " query resolved nothing
|
|
140
|
+
throw new Error("The ".concat(BANNERS_DOCTYPE, " query resolved nothing"));
|
|
585
141
|
}
|
|
586
142
|
|
|
587
|
-
return response;
|
|
143
|
+
return response.data;
|
|
588
144
|
};
|
|
589
|
-
/**
|
|
590
|
-
* The links disagree on how a missing document comes back: the stack link
|
|
591
|
-
* resolves `{data: null}` while the pouch link resolves `{data: []}`, and an
|
|
592
|
-
* empty array is truthy. Both mean there is nothing to write to.
|
|
593
|
-
*
|
|
594
|
-
* @param {object} response - The response of a getById query
|
|
595
|
-
* @returns {Banner|null} The document, or null when there is none
|
|
596
|
-
*/
|
|
145
|
+
/** The stack link answers a missing document with null, the pouch link with []. */
|
|
597
146
|
|
|
598
147
|
|
|
599
148
|
var readOne = function readOne(response) {
|
|
600
|
-
var
|
|
601
|
-
|
|
602
|
-
var data = (_requireResponse = requireResponse(response)) === null || _requireResponse === void 0 ? void 0 : _requireResponse.data;
|
|
149
|
+
var data = readData(response);
|
|
603
150
|
var doc = Array.isArray(data) ? data[0] : data;
|
|
604
151
|
return doc !== null && doc !== void 0 && doc._id ? doc : null;
|
|
605
152
|
};
|
|
153
|
+
|
|
154
|
+
var isConflict = function isConflict(error) {
|
|
155
|
+
var _error$message;
|
|
156
|
+
|
|
157
|
+
return (error === null || error === void 0 ? void 0 : error.status) === 409 || /Document update conflict/.test((_error$message = error === null || error === void 0 ? void 0 : error.message) !== null && _error$message !== void 0 ? _error$message : '');
|
|
158
|
+
};
|
|
606
159
|
/**
|
|
607
|
-
* The banners to display, in
|
|
608
|
-
* point: it queries the doctype and applies every rule the contract defines,
|
|
609
|
-
* so a caller does not compose the helpers itself.
|
|
160
|
+
* The banners to display, in order.
|
|
610
161
|
*
|
|
611
|
-
*
|
|
612
|
-
*
|
|
613
|
-
*
|
|
614
|
-
*
|
|
615
|
-
*
|
|
616
|
-
* @param {BannerClient} client - A CozyClient instance
|
|
617
|
-
* @param {VisibleBannersOptions} [options] - Options
|
|
618
|
-
* @returns {Promise<Banner[]>} The banners to display, ready to render
|
|
619
|
-
* @throws {Error} When the doctype cannot be read, a missing permission included
|
|
162
|
+
* @param {object} client - A CozyClient instance
|
|
163
|
+
* @param {object} [options] - Options
|
|
164
|
+
* @param {Date} [options.now] - The current time
|
|
165
|
+
* @returns {Promise<Banner[]>} The banners to render
|
|
620
166
|
*/
|
|
621
167
|
|
|
622
168
|
|
|
623
169
|
var getActiveBanners = /*#__PURE__*/function () {
|
|
624
|
-
var
|
|
625
|
-
var
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
_buildBannersQuery,
|
|
629
|
-
definition,
|
|
630
|
-
queryOptions,
|
|
170
|
+
var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(client) {
|
|
171
|
+
var _ref2,
|
|
172
|
+
_ref2$now,
|
|
173
|
+
now,
|
|
631
174
|
data,
|
|
632
175
|
_args = arguments;
|
|
633
176
|
|
|
@@ -635,42 +178,21 @@ var getActiveBanners = /*#__PURE__*/function () {
|
|
|
635
178
|
while (1) {
|
|
636
179
|
switch (_context.prev = _context.next) {
|
|
637
180
|
case 0:
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
_context.
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
case 5:
|
|
645
|
-
_context.t2 = _context.sent;
|
|
646
|
-
_context.t3 = _requireResponse2 = (0, _context.t1)(_context.t2);
|
|
647
|
-
_context.t0 = _context.t3 === null;
|
|
648
|
-
|
|
649
|
-
if (_context.t0) {
|
|
650
|
-
_context.next = 10;
|
|
651
|
-
break;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
_context.t0 = _requireResponse2 === void 0;
|
|
655
|
-
|
|
656
|
-
case 10:
|
|
657
|
-
if (!_context.t0) {
|
|
658
|
-
_context.next = 14;
|
|
659
|
-
break;
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
_context.t4 = void 0;
|
|
663
|
-
_context.next = 15;
|
|
664
|
-
break;
|
|
665
|
-
|
|
666
|
-
case 14:
|
|
667
|
-
_context.t4 = _requireResponse2.data;
|
|
181
|
+
_ref2 = _args.length > 1 && _args[1] !== undefined ? _args[1] : {}, _ref2$now = _ref2.now, now = _ref2$now === void 0 ? new Date() : _ref2$now;
|
|
182
|
+
_context.t0 = readData;
|
|
183
|
+
_context.next = 4;
|
|
184
|
+
return client.query((0, _dsl.Q)(BANNERS_DOCTYPE).UNSAFE_noLimit(), {
|
|
185
|
+
as: "".concat(BANNERS_DOCTYPE, "/all")
|
|
186
|
+
});
|
|
668
187
|
|
|
669
|
-
case
|
|
670
|
-
|
|
671
|
-
|
|
188
|
+
case 4:
|
|
189
|
+
_context.t1 = _context.sent;
|
|
190
|
+
data = (0, _context.t0)(_context.t1);
|
|
191
|
+
return _context.abrupt("return", (Array.isArray(data) ? data : []).filter(function (banner) {
|
|
192
|
+
return !isDismissed(banner) && isInWindow(banner, now);
|
|
193
|
+
}).sort(byPriority).map(withFallbacks));
|
|
672
194
|
|
|
673
|
-
case
|
|
195
|
+
case 7:
|
|
674
196
|
case "end":
|
|
675
197
|
return _context.stop();
|
|
676
198
|
}
|
|
@@ -679,300 +201,199 @@ var getActiveBanners = /*#__PURE__*/function () {
|
|
|
679
201
|
}));
|
|
680
202
|
|
|
681
203
|
return function getActiveBanners(_x) {
|
|
682
|
-
return
|
|
204
|
+
return _ref.apply(this, arguments);
|
|
683
205
|
};
|
|
684
206
|
}();
|
|
685
207
|
/**
|
|
686
208
|
* The single banner to display, for a surface that shows one at a time.
|
|
687
209
|
*
|
|
688
|
-
*
|
|
689
|
-
*
|
|
210
|
+
* A native client rendering one slot reads this rather than taking the head of
|
|
211
|
+
* the list itself, so the ordering rules stay in one place.
|
|
212
|
+
*
|
|
213
|
+
* @param {object} client - A CozyClient instance
|
|
214
|
+
* @param {object} [options] - Options
|
|
215
|
+
* @param {Date} [options.now] - The current time
|
|
690
216
|
* @returns {Promise<Banner|null>} The highest priority banner, or null
|
|
691
|
-
* @throws {Error} When the doctype cannot be read, a missing permission included
|
|
692
217
|
*/
|
|
693
218
|
|
|
694
219
|
|
|
695
220
|
exports.getActiveBanners = getActiveBanners;
|
|
696
221
|
|
|
697
222
|
var getActiveBanner = /*#__PURE__*/function () {
|
|
698
|
-
var
|
|
699
|
-
var
|
|
700
|
-
_yield$getActiveBanne,
|
|
701
|
-
_yield$getActiveBanne2,
|
|
702
|
-
first,
|
|
703
|
-
_args2 = arguments;
|
|
223
|
+
var _ref3 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(client, options) {
|
|
224
|
+
var _yield$getActiveBanne;
|
|
704
225
|
|
|
705
226
|
return _regenerator.default.wrap(function _callee2$(_context2) {
|
|
706
227
|
while (1) {
|
|
707
228
|
switch (_context2.prev = _context2.next) {
|
|
708
229
|
case 0:
|
|
709
|
-
|
|
710
|
-
_context2.next = 3;
|
|
230
|
+
_context2.next = 2;
|
|
711
231
|
return getActiveBanners(client, options);
|
|
712
232
|
|
|
713
|
-
case
|
|
714
|
-
_yield$getActiveBanne = _context2.sent;
|
|
715
|
-
|
|
716
|
-
first = _yield$getActiveBanne2[0];
|
|
717
|
-
return _context2.abrupt("return", first !== null && first !== void 0 ? first : null);
|
|
718
|
-
|
|
719
|
-
case 7:
|
|
720
|
-
case "end":
|
|
721
|
-
return _context2.stop();
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
}, _callee2);
|
|
725
|
-
}));
|
|
726
|
-
|
|
727
|
-
return function getActiveBanner(_x2) {
|
|
728
|
-
return _ref4.apply(this, arguments);
|
|
729
|
-
};
|
|
730
|
-
}();
|
|
731
|
-
/**
|
|
732
|
-
* Records a dismissal. The field is idempotent, so a conflict is resolved by
|
|
733
|
-
* reading the document again and writing the value on the fresh revision.
|
|
734
|
-
*
|
|
735
|
-
* The dismissal is written to the document it is given. During a doctypeVersion
|
|
736
|
-
* migration the stack materializes the same bannerId under several versions,
|
|
737
|
-
* and the sibling documents are not reached from here.
|
|
738
|
-
*
|
|
739
|
-
* @param {BannerClient} client - A CozyClient instance
|
|
740
|
-
* @param {Banner & {_id: string}} banner - The banner to dismiss, as stored
|
|
741
|
-
* @param {DismissOptions} [options] - Options
|
|
742
|
-
* @returns {Promise<{data: Banner|null}>} The dismissed document, null when it is gone
|
|
743
|
-
*/
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
exports.getActiveBanner = getActiveBanner;
|
|
747
|
-
|
|
748
|
-
var dismiss = /*#__PURE__*/function () {
|
|
749
|
-
var _ref5 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee3(client, banner) {
|
|
750
|
-
var _options$dismissedAt;
|
|
233
|
+
case 2:
|
|
234
|
+
_context2.t1 = _yield$getActiveBanne = _context2.sent[0];
|
|
235
|
+
_context2.t0 = _context2.t1 !== null;
|
|
751
236
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
id,
|
|
755
|
-
siblings,
|
|
756
|
-
result,
|
|
757
|
-
_iterator2,
|
|
758
|
-
_step2,
|
|
759
|
-
sibling,
|
|
760
|
-
written,
|
|
761
|
-
_args3 = arguments;
|
|
762
|
-
|
|
763
|
-
return _regenerator.default.wrap(function _callee3$(_context3) {
|
|
764
|
-
while (1) {
|
|
765
|
-
switch (_context3.prev = _context3.next) {
|
|
766
|
-
case 0:
|
|
767
|
-
options = _args3.length > 2 && _args3[2] !== undefined ? _args3[2] : {};
|
|
768
|
-
dismissedAt = (_options$dismissedAt = options.dismissedAt) !== null && _options$dismissedAt !== void 0 ? _options$dismissedAt : new Date().toISOString();
|
|
769
|
-
id = banner === null || banner === void 0 ? void 0 : banner._id;
|
|
770
|
-
|
|
771
|
-
if (id) {
|
|
772
|
-
_context3.next = 5;
|
|
237
|
+
if (!_context2.t0) {
|
|
238
|
+
_context2.next = 6;
|
|
773
239
|
break;
|
|
774
240
|
}
|
|
775
241
|
|
|
776
|
-
|
|
242
|
+
_context2.t0 = _yield$getActiveBanne !== void 0;
|
|
777
243
|
|
|
778
|
-
case
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
case 7:
|
|
783
|
-
siblings = _context3.sent;
|
|
784
|
-
result = {
|
|
785
|
-
data: null
|
|
786
|
-
};
|
|
787
|
-
_iterator2 = _createForOfIteratorHelper(siblings);
|
|
788
|
-
_context3.prev = 10;
|
|
789
|
-
|
|
790
|
-
_iterator2.s();
|
|
791
|
-
|
|
792
|
-
case 12:
|
|
793
|
-
if ((_step2 = _iterator2.n()).done) {
|
|
794
|
-
_context3.next = 20;
|
|
244
|
+
case 6:
|
|
245
|
+
if (!_context2.t0) {
|
|
246
|
+
_context2.next = 10;
|
|
795
247
|
break;
|
|
796
248
|
}
|
|
797
249
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
return writeDismissal(client, sibling._id, dismissedAt);
|
|
801
|
-
|
|
802
|
-
case 16:
|
|
803
|
-
written = _context3.sent;
|
|
804
|
-
// The caller is answered with the document it asked about.
|
|
805
|
-
if (sibling._id === id) result = written;
|
|
806
|
-
|
|
807
|
-
case 18:
|
|
808
|
-
_context3.next = 12;
|
|
250
|
+
_context2.t2 = _yield$getActiveBanne;
|
|
251
|
+
_context2.next = 11;
|
|
809
252
|
break;
|
|
810
253
|
|
|
811
|
-
case
|
|
812
|
-
|
|
813
|
-
break;
|
|
814
|
-
|
|
815
|
-
case 22:
|
|
816
|
-
_context3.prev = 22;
|
|
817
|
-
_context3.t0 = _context3["catch"](10);
|
|
818
|
-
|
|
819
|
-
_iterator2.e(_context3.t0);
|
|
820
|
-
|
|
821
|
-
case 25:
|
|
822
|
-
_context3.prev = 25;
|
|
823
|
-
|
|
824
|
-
_iterator2.f();
|
|
825
|
-
|
|
826
|
-
return _context3.finish(25);
|
|
254
|
+
case 10:
|
|
255
|
+
_context2.t2 = null;
|
|
827
256
|
|
|
828
|
-
case
|
|
829
|
-
return
|
|
257
|
+
case 11:
|
|
258
|
+
return _context2.abrupt("return", _context2.t2);
|
|
830
259
|
|
|
831
|
-
case
|
|
260
|
+
case 12:
|
|
832
261
|
case "end":
|
|
833
|
-
return
|
|
262
|
+
return _context2.stop();
|
|
834
263
|
}
|
|
835
264
|
}
|
|
836
|
-
},
|
|
265
|
+
}, _callee2);
|
|
837
266
|
}));
|
|
838
267
|
|
|
839
|
-
return function
|
|
840
|
-
return
|
|
268
|
+
return function getActiveBanner(_x2, _x3) {
|
|
269
|
+
return _ref3.apply(this, arguments);
|
|
841
270
|
};
|
|
842
271
|
}();
|
|
843
272
|
/**
|
|
844
|
-
*
|
|
845
|
-
*
|
|
846
|
-
*
|
|
273
|
+
* Records a dismissal on the stored document, retrying once on a conflict.
|
|
274
|
+
*
|
|
275
|
+
* The stored document is written back rather than the one the caller holds,
|
|
276
|
+
* whose severity and surface have had the fallbacks applied.
|
|
847
277
|
*
|
|
848
|
-
* @param {
|
|
849
|
-
* @param {Banner} banner - The banner
|
|
850
|
-
* @
|
|
278
|
+
* @param {object} client - A CozyClient instance
|
|
279
|
+
* @param {Banner} banner - The banner to dismiss
|
|
280
|
+
* @param {object} [options] - Options
|
|
281
|
+
* @param {string} [options.dismissedAt] - The timestamp to record
|
|
282
|
+
* @returns {Promise<{data: Banner|null}>} The dismissed document, null when gone
|
|
851
283
|
*/
|
|
852
284
|
|
|
853
285
|
|
|
854
|
-
exports.
|
|
286
|
+
exports.getActiveBanner = getActiveBanner;
|
|
855
287
|
|
|
856
|
-
var
|
|
857
|
-
var
|
|
858
|
-
var
|
|
288
|
+
var dismiss = /*#__PURE__*/function () {
|
|
289
|
+
var _ref4 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee4(client, banner) {
|
|
290
|
+
var _ref5,
|
|
291
|
+
dismissedAt,
|
|
292
|
+
at,
|
|
293
|
+
write,
|
|
294
|
+
_args4 = arguments;
|
|
859
295
|
|
|
860
296
|
return _regenerator.default.wrap(function _callee4$(_context4) {
|
|
861
297
|
while (1) {
|
|
862
298
|
switch (_context4.prev = _context4.next) {
|
|
863
299
|
case 0:
|
|
864
|
-
|
|
865
|
-
_context4.t0 = requireResponse;
|
|
866
|
-
_context4.next = 4;
|
|
867
|
-
return client.query(definition, queryOptions);
|
|
300
|
+
_ref5 = _args4.length > 2 && _args4[2] !== undefined ? _args4[2] : {}, dismissedAt = _ref5.dismissedAt;
|
|
868
301
|
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
response = (0, _context4.t0)(_context4.t1);
|
|
872
|
-
data = Array.isArray(response.data) ? response.data : [];
|
|
873
|
-
key = groupKey(banner);
|
|
874
|
-
siblings = data.filter(function (doc) {
|
|
875
|
-
return (doc === null || doc === void 0 ? void 0 : doc._id) && groupKey(doc) === key;
|
|
876
|
-
}); // The document may have been deleted between the read and the click, in which
|
|
877
|
-
// case there is nothing left to write to.
|
|
878
|
-
|
|
879
|
-
return _context4.abrupt("return", siblings.length > 0 ? siblings : []);
|
|
880
|
-
|
|
881
|
-
case 10:
|
|
882
|
-
case "end":
|
|
883
|
-
return _context4.stop();
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
}, _callee4);
|
|
887
|
-
}));
|
|
888
|
-
|
|
889
|
-
return function findSiblings(_x5, _x6) {
|
|
890
|
-
return _ref6.apply(this, arguments);
|
|
891
|
-
};
|
|
892
|
-
}();
|
|
893
|
-
/** How many times the write is attempted before a conflict reaches the caller. */
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
var DISMISS_ATTEMPTS = 2;
|
|
897
|
-
|
|
898
|
-
var writeDismissal = /*#__PURE__*/function () {
|
|
899
|
-
var _ref7 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee5(client, id, dismissedAt) {
|
|
900
|
-
var attempt, stored;
|
|
901
|
-
return _regenerator.default.wrap(function _callee5$(_context5) {
|
|
902
|
-
while (1) {
|
|
903
|
-
switch (_context5.prev = _context5.next) {
|
|
904
|
-
case 0:
|
|
905
|
-
attempt = 1;
|
|
906
|
-
|
|
907
|
-
case 1:
|
|
908
|
-
if (!(attempt <= DISMISS_ATTEMPTS)) {
|
|
909
|
-
_context5.next = 24;
|
|
302
|
+
if (banner !== null && banner !== void 0 && banner._id) {
|
|
303
|
+
_context4.next = 3;
|
|
910
304
|
break;
|
|
911
305
|
}
|
|
912
306
|
|
|
913
|
-
|
|
914
|
-
_context5.next = 5;
|
|
915
|
-
return client.query((0, _dsl.Q)(BANNERS_DOCTYPE).getById(id));
|
|
916
|
-
|
|
917
|
-
case 5:
|
|
918
|
-
_context5.t1 = _context5.sent;
|
|
919
|
-
stored = (0, _context5.t0)(_context5.t1);
|
|
920
|
-
|
|
921
|
-
if (stored) {
|
|
922
|
-
_context5.next = 9;
|
|
923
|
-
break;
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
return _context5.abrupt("return", {
|
|
927
|
-
data: null
|
|
928
|
-
});
|
|
929
|
-
|
|
930
|
-
case 9:
|
|
931
|
-
if (!isDismissed(stored)) {
|
|
932
|
-
_context5.next = 11;
|
|
933
|
-
break;
|
|
934
|
-
}
|
|
307
|
+
throw new Error('A banner needs an _id to be dismissed');
|
|
935
308
|
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
309
|
+
case 3:
|
|
310
|
+
at = dismissedAt !== null && dismissedAt !== void 0 ? dismissedAt : new Date().toISOString();
|
|
311
|
+
|
|
312
|
+
write = /*#__PURE__*/function () {
|
|
313
|
+
var _ref6 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee3() {
|
|
314
|
+
var stored;
|
|
315
|
+
return _regenerator.default.wrap(function _callee3$(_context3) {
|
|
316
|
+
while (1) {
|
|
317
|
+
switch (_context3.prev = _context3.next) {
|
|
318
|
+
case 0:
|
|
319
|
+
_context3.t0 = readOne;
|
|
320
|
+
_context3.next = 3;
|
|
321
|
+
return client.query((0, _dsl.Q)(BANNERS_DOCTYPE).getById(banner._id));
|
|
322
|
+
|
|
323
|
+
case 3:
|
|
324
|
+
_context3.t1 = _context3.sent;
|
|
325
|
+
stored = (0, _context3.t0)(_context3.t1);
|
|
326
|
+
|
|
327
|
+
if (stored) {
|
|
328
|
+
_context3.next = 7;
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return _context3.abrupt("return", {
|
|
333
|
+
data: null
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
case 7:
|
|
337
|
+
if (!isDismissed(stored)) {
|
|
338
|
+
_context3.next = 9;
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return _context3.abrupt("return", {
|
|
343
|
+
data: stored
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
case 9:
|
|
347
|
+
return _context3.abrupt("return", client.save(_objectSpread(_objectSpread({}, stored), {}, {
|
|
348
|
+
_type: BANNERS_DOCTYPE,
|
|
349
|
+
dismissedAt: at
|
|
350
|
+
})));
|
|
351
|
+
|
|
352
|
+
case 10:
|
|
353
|
+
case "end":
|
|
354
|
+
return _context3.stop();
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}, _callee3);
|
|
358
|
+
}));
|
|
359
|
+
|
|
360
|
+
return function write() {
|
|
361
|
+
return _ref6.apply(this, arguments);
|
|
362
|
+
};
|
|
363
|
+
}();
|
|
364
|
+
|
|
365
|
+
_context4.prev = 5;
|
|
366
|
+
_context4.next = 8;
|
|
367
|
+
return write();
|
|
368
|
+
|
|
369
|
+
case 8:
|
|
370
|
+
return _context4.abrupt("return", _context4.sent);
|
|
939
371
|
|
|
940
372
|
case 11:
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
}));
|
|
947
|
-
|
|
948
|
-
case 14:
|
|
949
|
-
return _context5.abrupt("return", _context5.sent);
|
|
950
|
-
|
|
951
|
-
case 17:
|
|
952
|
-
_context5.prev = 17;
|
|
953
|
-
_context5.t2 = _context5["catch"](11);
|
|
954
|
-
|
|
955
|
-
if (!(!isConflict(_context5.t2) || attempt === DISMISS_ATTEMPTS)) {
|
|
956
|
-
_context5.next = 21;
|
|
373
|
+
_context4.prev = 11;
|
|
374
|
+
_context4.t0 = _context4["catch"](5);
|
|
375
|
+
|
|
376
|
+
if (isConflict(_context4.t0)) {
|
|
377
|
+
_context4.next = 15;
|
|
957
378
|
break;
|
|
958
379
|
}
|
|
959
380
|
|
|
960
|
-
throw
|
|
381
|
+
throw _context4.t0;
|
|
961
382
|
|
|
962
|
-
case
|
|
963
|
-
|
|
964
|
-
_context5.next = 1;
|
|
965
|
-
break;
|
|
383
|
+
case 15:
|
|
384
|
+
return _context4.abrupt("return", write());
|
|
966
385
|
|
|
967
|
-
case
|
|
386
|
+
case 16:
|
|
968
387
|
case "end":
|
|
969
|
-
return
|
|
388
|
+
return _context4.stop();
|
|
970
389
|
}
|
|
971
390
|
}
|
|
972
|
-
},
|
|
391
|
+
}, _callee4, null, [[5, 11]]);
|
|
973
392
|
}));
|
|
974
393
|
|
|
975
|
-
return function
|
|
976
|
-
return
|
|
394
|
+
return function dismiss(_x4, _x5) {
|
|
395
|
+
return _ref4.apply(this, arguments);
|
|
977
396
|
};
|
|
978
|
-
}();
|
|
397
|
+
}();
|
|
398
|
+
|
|
399
|
+
exports.dismiss = dismiss;
|