@blamejs/blamejs-shop 0.0.72 → 0.0.75

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/lib/announcement-bar.js +753 -0
  3. package/lib/banner-ab-tests.js +806 -0
  4. package/lib/bin-locations.js +791 -0
  5. package/lib/blog-articles.js +1173 -0
  6. package/lib/carrier-accounts.js +805 -0
  7. package/lib/cart-recovery.js +1133 -0
  8. package/lib/category-navigation.js +934 -0
  9. package/lib/consent-ledger.js +539 -0
  10. package/lib/customer-impersonation.js +743 -0
  11. package/lib/customer-merge.js +879 -0
  12. package/lib/demand-forecast.js +1121 -0
  13. package/lib/dispute-resolution.js +886 -0
  14. package/lib/email-ab-tests.js +918 -0
  15. package/lib/email-engagement-score.js +649 -0
  16. package/lib/event-log.js +713 -0
  17. package/lib/fulfillment-sla.js +791 -0
  18. package/lib/index.js +41 -0
  19. package/lib/inventory-audits.js +852 -0
  20. package/lib/line-gift-wrap.js +430 -0
  21. package/lib/marketing-budget.js +792 -0
  22. package/lib/operator-activity-feed.js +977 -0
  23. package/lib/operator-approvals.js +942 -0
  24. package/lib/operator-help-center.js +1020 -0
  25. package/lib/operator-inbox.js +889 -0
  26. package/lib/operator-sessions.js +701 -0
  27. package/lib/order-exchanges.js +602 -0
  28. package/lib/product-compare.js +804 -0
  29. package/lib/pwa-manifest.js +1005 -0
  30. package/lib/referral-leaderboard.js +612 -0
  31. package/lib/sales-tax-filings.js +807 -0
  32. package/lib/search-ranking.js +859 -0
  33. package/lib/shipping-insurance.js +757 -0
  34. package/lib/shrinkage-report.js +1182 -0
  35. package/lib/sidebar-widgets.js +952 -0
  36. package/lib/smart-restocking.js +1048 -0
  37. package/lib/stock-receipts.js +834 -0
  38. package/lib/subscription-analytics.js +1032 -0
  39. package/lib/suggestion-box.js +921 -0
  40. package/lib/tax-remittance.js +625 -0
  41. package/lib/vendor-invoices.js +1021 -0
  42. package/lib/winback-campaigns.js +1350 -0
  43. package/lib/wishlist-digest.js +1133 -0
  44. package/package.json +1 -1
@@ -0,0 +1,1005 @@
1
+ "use strict";
2
+ /**
3
+ * @module shop.pwaManifest
4
+ * @title PWA manifest — operator-configurable webmanifest + service worker
5
+ *
6
+ * @intro
7
+ * Owns the bytes the storefront serves at
8
+ * `/manifest.webmanifest` and `/sw.js` — the two artifacts a
9
+ * browser fetches to treat the site as a Progressive Web App
10
+ * (install prompt, standalone display, offline shell). Operators
11
+ * author both surfaces through this primitive; the worker
12
+ * renders the active versions on demand.
13
+ *
14
+ * Manifest surface:
15
+ * - `defineManifest({ name, short_name, description, start_url,
16
+ * scope, display, orientation, theme_color,
17
+ * background_color, lang?, dir?, icons })`
18
+ * — append a new version row. `display` and `orientation` are
19
+ * closed enums (display: standalone / fullscreen / minimal-ui
20
+ * / browser; orientation: any / natural / portrait /
21
+ * landscape). Colors must be lowercase `#rrggbb` 6-digit hex —
22
+ * any other shape (3-digit hex, named colors, `rgb(...)`) is
23
+ * refused so the byte-level output is predictable across
24
+ * browsers. `start_url` + `scope` are validated through
25
+ * `b.safeUrl.parse` (https-only) when absolute or pass the
26
+ * `/`-rooted absolute-path allow-list (the common case — the
27
+ * manifest references the site's own routes). `icons` is an
28
+ * array of `{ src, sizes, type, purpose? }` descriptors that
29
+ * passes through `validateIcons` before storage.
30
+ *
31
+ * - `getActive()` — the currently-active manifest row (or null
32
+ * when no version has been activated yet).
33
+ *
34
+ * - `setActive(versionNumber)` — flip the active flag to the
35
+ * named version inside a single sweep. The prior active row
36
+ * drops to `is_active=0`. Archived versions can not become
37
+ * active.
38
+ *
39
+ * - `listVersions({ cursor?, limit? })` — paginate the version
40
+ * history newest-first. Cursor is HMAC-tagged via
41
+ * `b.pagination.encodeCursor` so a tampered cursor refuses.
42
+ *
43
+ * - `archiveVersion(versionNumber)` — soft-delete a version.
44
+ * Archiving the active version clears the active flag (no
45
+ * archived version can stay active).
46
+ *
47
+ * - `renderManifestJson()` — emit the JSON bytes for
48
+ * `/manifest.webmanifest`. The output is a deterministic
49
+ * JSON.stringify (sorted keys, two-space indent) so a byte-
50
+ * diff between releases reflects an operator edit rather
51
+ * than serializer noise.
52
+ *
53
+ * - `validateIcons(icons)` — exposed icon validation entry
54
+ * point. Operators previewing icon arrays before
55
+ * defineManifest call this directly.
56
+ *
57
+ * Service-worker surface:
58
+ * - `defineServiceWorkerConfig({ cache_name, precache_urls,
59
+ * runtime_rules, offline_fallback?,
60
+ * navigation_fallback? })` —
61
+ * append a new SW config version. `cache_name` namespaces
62
+ * the caches so two parallel SW versions don't collide
63
+ * during a rollout. `precache_urls` is the URL list cached
64
+ * on `install`. `runtime_rules` is an array of
65
+ * `{ url_pattern, strategy }` entries (strategy: cache-first
66
+ * / network-first / stale-while-revalidate / network-only).
67
+ *
68
+ * - Companion `getActive('sw')` / `setActive('sw', version)` /
69
+ * `listVersions('sw', ...)` / `archiveVersion('sw', version)`
70
+ * overloads route the same lifecycle to the SW table. The
71
+ * default scope when omitted is `'manifest'`.
72
+ *
73
+ * - `renderServiceWorkerJs()` — emit the JS bytes for `/sw.js`.
74
+ * The body is a self-contained `addEventListener('install', …)`
75
+ * / `addEventListener('fetch', …)` script that reads the
76
+ * active SW config row. Operator-sourced URL fragments are
77
+ * JSON.stringify'd into the body so a hostile URL can't
78
+ * break out of the string literal.
79
+ *
80
+ * Composes ONLY blamejs:
81
+ * - `b.framework.safeUrl.parse` — start_url / scope / icon src
82
+ * validation (https-only absolute).
83
+ * - `b.framework.uuid.v7` — row ids.
84
+ * - `b.framework.pagination` — listVersions cursor (HMAC-tagged).
85
+ *
86
+ * Monotonic per-process clock — two writes in the same millisecond
87
+ * would tie on `updated_at` and make a sort-by-timestamp read
88
+ * ambiguous. `_now` bumps to `prior + 1` on collision so the
89
+ * timeline stays strictly increasing.
90
+ *
91
+ * Storage:
92
+ * - `pwa_manifests` — versioned manifest rows.
93
+ * - `pwa_sw_configs` — versioned SW config rows.
94
+ * (migration `0168_pwa_manifest.sql`)
95
+ *
96
+ * @primitive pwaManifest
97
+ * @related b.safeUrl.parse, b.uuid.v7, b.pagination
98
+ */
99
+
100
+ // ---- enums -------------------------------------------------------------
101
+
102
+ var ALLOWED_DISPLAYS = Object.freeze([
103
+ "standalone",
104
+ "fullscreen",
105
+ "minimal-ui",
106
+ "browser",
107
+ ]);
108
+
109
+ var ALLOWED_ORIENTATIONS = Object.freeze([
110
+ "any",
111
+ "natural",
112
+ "portrait",
113
+ "landscape",
114
+ ]);
115
+
116
+ var ALLOWED_DIRS = Object.freeze(["ltr", "rtl", "auto"]);
117
+
118
+ var ALLOWED_STRATEGIES = Object.freeze([
119
+ "cache-first",
120
+ "network-first",
121
+ "stale-while-revalidate",
122
+ "network-only",
123
+ ]);
124
+
125
+ var ALLOWED_ICON_PURPOSES = Object.freeze(["any", "maskable", "monochrome"]);
126
+
127
+ var ALLOWED_SCOPES = Object.freeze(["manifest", "sw"]);
128
+
129
+ // ---- bounds ------------------------------------------------------------
130
+
131
+ var MAX_NAME_LEN = 200;
132
+ var MAX_SHORT_NAME_LEN = 60;
133
+ var MAX_DESCRIPTION_LEN = 1024;
134
+ var MAX_URL_LEN = 2048;
135
+ var MAX_LANG_LEN = 35;
136
+ var MAX_CACHE_NAME_LEN = 120;
137
+ var MAX_ICON_COUNT = 32;
138
+ var MAX_ICON_SIZES_LEN = 200;
139
+ var MAX_ICON_TYPE_LEN = 80;
140
+ var MAX_PRECACHE_URLS = 256;
141
+ var MAX_RUNTIME_RULES = 64;
142
+ var MAX_URL_PATTERN_LEN = 256;
143
+ var MAX_LIST_LIMIT = 100;
144
+ var DEFAULT_LIST_LIMIT = 25;
145
+
146
+ // ---- regexes -----------------------------------------------------------
147
+
148
+ // Lowercase 6-digit hex only. 3-digit shorthand, named colors,
149
+ // rgb()/hsl()/hwb(), and uppercase are refused so the rendered
150
+ // bytes are predictable across user agents (some Android Chrome
151
+ // flavors render `#fff` differently from `#FFFFFF`).
152
+ var HEX_COLOR_RE = /^#[0-9a-f]{6}$/;
153
+
154
+ // BCP-47 tag — same shape as knowledgeBase / emailTemplates.
155
+ var LANG_RE = /^[a-z]{2,3}(?:-[a-z0-9]{2,8})*$/;
156
+
157
+ // Cache name — alnum + dot + hyphen + underscore. Reaches the
158
+ // rendered SW body inside a JSON.stringify, but a narrow cache
159
+ // name keeps the operator-visible value grep-friendly.
160
+ var CACHE_NAME_RE = /^[a-z0-9][a-z0-9._-]{0,118}[a-z0-9]$|^[a-z0-9]$/;
161
+
162
+ // Icon sizes — one or more space-separated `WxH` or `any` tokens.
163
+ // e.g. `192x192`, `512x512`, `192x192 512x512`, `any`.
164
+ var ICON_SIZES_RE = /^(?:any|\d+x\d+)(?:\s+(?:any|\d+x\d+))*$/;
165
+
166
+ // MIME type — narrow shape covering the icon types the manifest
167
+ // spec recommends (image/png / image/svg+xml / image/webp).
168
+ var ICON_TYPE_RE = /^[a-z]+\/[a-z0-9.+-]+$/;
169
+
170
+ var CONTROL_BYTE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
171
+ var ZERO_WIDTH_RE = new RegExp(
172
+ "[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
173
+ );
174
+
175
+ var bShop;
176
+ function _b() {
177
+ if (!bShop) bShop = require("./index");
178
+ return bShop.framework;
179
+ }
180
+
181
+ // ---- monotonic clock ---------------------------------------------------
182
+ //
183
+ // Two writes in the same millisecond would tie on `updated_at`.
184
+ // Bump by 1 on collision so the listVersions sort is strictly
185
+ // increasing.
186
+
187
+ var _lastTs = 0;
188
+ function _now() {
189
+ var t = Date.now();
190
+ if (t <= _lastTs) t = _lastTs + 1;
191
+ _lastTs = t;
192
+ return t;
193
+ }
194
+
195
+ // ---- validators --------------------------------------------------------
196
+
197
+ function _string(s, label, max) {
198
+ if (typeof s !== "string") {
199
+ throw new TypeError("pwaManifest: " + label + " must be a string");
200
+ }
201
+ var trimmed = s.trim();
202
+ if (!trimmed.length) {
203
+ throw new TypeError("pwaManifest: " + label + " must be non-empty after trim");
204
+ }
205
+ if (s.length > max) {
206
+ throw new TypeError("pwaManifest: " + label + " must be <= " + max + " characters");
207
+ }
208
+ if (CONTROL_BYTE_RE.test(s)) {
209
+ throw new TypeError("pwaManifest: " + label + " contains control bytes");
210
+ }
211
+ if (ZERO_WIDTH_RE.test(s)) {
212
+ throw new TypeError("pwaManifest: " + label + " contains zero-width / direction-override characters");
213
+ }
214
+ return s;
215
+ }
216
+
217
+ function _hexColor(s, label) {
218
+ if (typeof s !== "string") {
219
+ throw new TypeError("pwaManifest: " + label + " must be a string");
220
+ }
221
+ if (!HEX_COLOR_RE.test(s)) {
222
+ throw new TypeError(
223
+ "pwaManifest: " + label + " must be a lowercase 6-digit hex color like '#1a2b3c' " +
224
+ "(3-digit shorthand, uppercase, rgb()/hsl(), and named colors are refused)"
225
+ );
226
+ }
227
+ return s;
228
+ }
229
+
230
+ function _enum(s, allowed, label) {
231
+ if (typeof s !== "string" || allowed.indexOf(s) === -1) {
232
+ throw new TypeError("pwaManifest: " + label + " must be one of " + JSON.stringify(allowed));
233
+ }
234
+ return s;
235
+ }
236
+
237
+ function _lang(s) {
238
+ if (typeof s !== "string" || !s.length) {
239
+ throw new TypeError("pwaManifest: lang must be a non-empty string");
240
+ }
241
+ if (s.length > MAX_LANG_LEN) {
242
+ throw new TypeError("pwaManifest: lang must be <= " + MAX_LANG_LEN + " characters");
243
+ }
244
+ var lower = s.toLowerCase();
245
+ if (!LANG_RE.test(lower)) {
246
+ throw new TypeError("pwaManifest: lang must be a BCP-47 tag (e.g. 'en', 'fr-ca')");
247
+ }
248
+ return lower;
249
+ }
250
+
251
+ function _cacheName(s) {
252
+ if (typeof s !== "string" || !s.length) {
253
+ throw new TypeError("pwaManifest: cache_name must be a non-empty string");
254
+ }
255
+ if (s.length > MAX_CACHE_NAME_LEN) {
256
+ throw new TypeError("pwaManifest: cache_name must be <= " + MAX_CACHE_NAME_LEN + " characters");
257
+ }
258
+ if (!CACHE_NAME_RE.test(s)) {
259
+ throw new TypeError("pwaManifest: cache_name must match /^[a-z0-9][a-z0-9._-]*[a-z0-9]$/");
260
+ }
261
+ return s;
262
+ }
263
+
264
+ // URL validation. A manifest's start_url / scope / icon src is
265
+ // either an absolute https:// URL (validated through b.safeUrl) OR
266
+ // a `/`-rooted absolute path on the same origin. Protocol-relative
267
+ // `//host` and `..`-bearing paths are refused so a hostile value
268
+ // can't smuggle a navigation off-origin.
269
+ function _url(s, label) {
270
+ if (typeof s !== "string" || !s.length) {
271
+ throw new TypeError("pwaManifest: " + label + " must be a non-empty string");
272
+ }
273
+ if (s.length > MAX_URL_LEN) {
274
+ throw new TypeError("pwaManifest: " + label + " must be <= " + MAX_URL_LEN + " characters");
275
+ }
276
+ if (CONTROL_BYTE_RE.test(s) || ZERO_WIDTH_RE.test(s)) {
277
+ throw new TypeError("pwaManifest: " + label + " contains control / zero-width bytes");
278
+ }
279
+ if (s.charCodeAt(0) === 47 /* "/" */) {
280
+ if (s.length > 1 && s.charCodeAt(1) === 47) {
281
+ throw new TypeError("pwaManifest: " + label + " must not start with '//' (protocol-relative)");
282
+ }
283
+ if (s.indexOf("..") !== -1) {
284
+ throw new TypeError("pwaManifest: " + label + " must not contain '..'");
285
+ }
286
+ return s;
287
+ }
288
+ try {
289
+ _b().safeUrl.parse(s, { allowedProtocols: ["https:"] });
290
+ } catch (e) {
291
+ throw new TypeError(
292
+ "pwaManifest: " + label + " — " + (e && e.message || "must be a /-rooted path or https:// URL")
293
+ );
294
+ }
295
+ return s;
296
+ }
297
+
298
+ function _scopeKind(s) {
299
+ if (s == null) return "manifest";
300
+ if (typeof s !== "string" || ALLOWED_SCOPES.indexOf(s) === -1) {
301
+ throw new TypeError("pwaManifest: scope kind must be one of " + JSON.stringify(ALLOWED_SCOPES));
302
+ }
303
+ return s;
304
+ }
305
+
306
+ function _versionNumber(n, label) {
307
+ if (!Number.isInteger(n) || n < 1) {
308
+ throw new TypeError("pwaManifest: " + label + " must be a positive integer");
309
+ }
310
+ return n;
311
+ }
312
+
313
+ function _limit(n) {
314
+ if (n == null) return DEFAULT_LIST_LIMIT;
315
+ if (!Number.isInteger(n) || n <= 0 || n > MAX_LIST_LIMIT) {
316
+ throw new TypeError("pwaManifest: limit must be an integer 1..." + MAX_LIST_LIMIT);
317
+ }
318
+ return n;
319
+ }
320
+
321
+ // ---- icon validation ---------------------------------------------------
322
+ //
323
+ // Each icon descriptor: { src, sizes, type, purpose? }. The src is
324
+ // gated by `_url`; sizes match the manifest spec's `WxH` shape or
325
+ // `any`; type is a narrow MIME shape; purpose (when present) is an
326
+ // enum. Duplicates are refused so an operator can't ship two
327
+ // conflicting descriptors for the same (sizes, purpose) tuple.
328
+
329
+ function _validateIcons(icons) {
330
+ if (!Array.isArray(icons)) {
331
+ throw new TypeError("pwaManifest: icons must be an array of { src, sizes, type, purpose? } descriptors");
332
+ }
333
+ if (!icons.length) {
334
+ throw new TypeError("pwaManifest: icons must contain at least one descriptor");
335
+ }
336
+ if (icons.length > MAX_ICON_COUNT) {
337
+ throw new TypeError("pwaManifest: icons must contain <= " + MAX_ICON_COUNT + " entries");
338
+ }
339
+ var seen = {};
340
+ var out = [];
341
+ for (var i = 0; i < icons.length; i += 1) {
342
+ var ic = icons[i];
343
+ if (!ic || typeof ic !== "object") {
344
+ throw new TypeError("pwaManifest: icons[" + i + "] must be an object");
345
+ }
346
+ var src = _url(ic.src, "icons[" + i + "].src");
347
+ if (typeof ic.sizes !== "string" || !ic.sizes.length) {
348
+ throw new TypeError("pwaManifest: icons[" + i + "].sizes must be a non-empty string");
349
+ }
350
+ if (ic.sizes.length > MAX_ICON_SIZES_LEN) {
351
+ throw new TypeError("pwaManifest: icons[" + i + "].sizes must be <= " + MAX_ICON_SIZES_LEN + " characters");
352
+ }
353
+ if (!ICON_SIZES_RE.test(ic.sizes)) {
354
+ throw new TypeError(
355
+ "pwaManifest: icons[" + i + "].sizes must be space-separated 'WxH' tokens or 'any' " +
356
+ "(e.g. '192x192 512x512')"
357
+ );
358
+ }
359
+ if (typeof ic.type !== "string" || !ic.type.length) {
360
+ throw new TypeError("pwaManifest: icons[" + i + "].type must be a non-empty string");
361
+ }
362
+ if (ic.type.length > MAX_ICON_TYPE_LEN) {
363
+ throw new TypeError("pwaManifest: icons[" + i + "].type must be <= " + MAX_ICON_TYPE_LEN + " characters");
364
+ }
365
+ if (!ICON_TYPE_RE.test(ic.type)) {
366
+ throw new TypeError("pwaManifest: icons[" + i + "].type must be a MIME type (e.g. 'image/png')");
367
+ }
368
+ var purpose;
369
+ if (ic.purpose == null) {
370
+ purpose = "any";
371
+ } else if (typeof ic.purpose !== "string" || ALLOWED_ICON_PURPOSES.indexOf(ic.purpose) === -1) {
372
+ throw new TypeError(
373
+ "pwaManifest: icons[" + i + "].purpose must be one of " + JSON.stringify(ALLOWED_ICON_PURPOSES)
374
+ );
375
+ } else {
376
+ purpose = ic.purpose;
377
+ }
378
+ var key = ic.sizes + "|" + purpose;
379
+ if (seen[key]) {
380
+ throw new TypeError(
381
+ "pwaManifest: icons[" + i + "] duplicates an earlier (sizes='" + ic.sizes +
382
+ "', purpose='" + purpose + "') descriptor"
383
+ );
384
+ }
385
+ seen[key] = 1;
386
+ out.push({ src: src, sizes: ic.sizes, type: ic.type, purpose: purpose });
387
+ }
388
+ return out;
389
+ }
390
+
391
+ // ---- runtime-rule validation ------------------------------------------
392
+ //
393
+ // Each runtime rule: { url_pattern, strategy }. `url_pattern` is a
394
+ // narrow string (no control bytes / no zero-width). `strategy` is
395
+ // a closed enum so the rendered SW body has a known finite branch
396
+ // surface. Duplicate url_patterns are refused so the operator's
397
+ // rule table is internally consistent.
398
+
399
+ function _validateRuntimeRules(rules) {
400
+ if (!Array.isArray(rules)) {
401
+ throw new TypeError("pwaManifest: runtime_rules must be an array");
402
+ }
403
+ if (rules.length > MAX_RUNTIME_RULES) {
404
+ throw new TypeError("pwaManifest: runtime_rules must contain <= " + MAX_RUNTIME_RULES + " entries");
405
+ }
406
+ var seen = {};
407
+ var out = [];
408
+ for (var i = 0; i < rules.length; i += 1) {
409
+ var r = rules[i];
410
+ if (!r || typeof r !== "object") {
411
+ throw new TypeError("pwaManifest: runtime_rules[" + i + "] must be an object");
412
+ }
413
+ if (typeof r.url_pattern !== "string" || !r.url_pattern.length) {
414
+ throw new TypeError("pwaManifest: runtime_rules[" + i + "].url_pattern must be a non-empty string");
415
+ }
416
+ if (r.url_pattern.length > MAX_URL_PATTERN_LEN) {
417
+ throw new TypeError(
418
+ "pwaManifest: runtime_rules[" + i + "].url_pattern must be <= " +
419
+ MAX_URL_PATTERN_LEN + " characters"
420
+ );
421
+ }
422
+ if (CONTROL_BYTE_RE.test(r.url_pattern) || ZERO_WIDTH_RE.test(r.url_pattern)) {
423
+ throw new TypeError(
424
+ "pwaManifest: runtime_rules[" + i + "].url_pattern contains control / zero-width bytes"
425
+ );
426
+ }
427
+ _enum(r.strategy, ALLOWED_STRATEGIES, "runtime_rules[" + i + "].strategy");
428
+ if (seen[r.url_pattern]) {
429
+ throw new TypeError(
430
+ "pwaManifest: runtime_rules[" + i + "] duplicates url_pattern " + JSON.stringify(r.url_pattern)
431
+ );
432
+ }
433
+ seen[r.url_pattern] = 1;
434
+ out.push({ url_pattern: r.url_pattern, strategy: r.strategy });
435
+ }
436
+ return out;
437
+ }
438
+
439
+ function _validatePrecacheUrls(urls) {
440
+ if (!Array.isArray(urls)) {
441
+ throw new TypeError("pwaManifest: precache_urls must be an array");
442
+ }
443
+ if (urls.length > MAX_PRECACHE_URLS) {
444
+ throw new TypeError("pwaManifest: precache_urls must contain <= " + MAX_PRECACHE_URLS + " entries");
445
+ }
446
+ var seen = {};
447
+ var out = [];
448
+ for (var i = 0; i < urls.length; i += 1) {
449
+ var u = _url(urls[i], "precache_urls[" + i + "]");
450
+ if (seen[u]) {
451
+ throw new TypeError("pwaManifest: precache_urls[" + i + "] duplicates an earlier entry");
452
+ }
453
+ seen[u] = 1;
454
+ out.push(u);
455
+ }
456
+ return out;
457
+ }
458
+
459
+ // ---- hydration ---------------------------------------------------------
460
+
461
+ function _hydrateManifest(row) {
462
+ if (!row) return null;
463
+ var icons;
464
+ try { icons = JSON.parse(row.icons_json || "[]"); }
465
+ catch (_e) { icons = []; }
466
+ return {
467
+ id: row.id,
468
+ version_number: Number(row.version_number),
469
+ name: row.name,
470
+ short_name: row.short_name,
471
+ description: row.description,
472
+ start_url: row.start_url,
473
+ scope: row.scope,
474
+ display: row.display,
475
+ orientation: row.orientation,
476
+ theme_color: row.theme_color,
477
+ background_color: row.background_color,
478
+ lang: row.lang,
479
+ dir: row.dir,
480
+ icons: icons,
481
+ is_active: row.is_active === 1 || row.is_active === true,
482
+ archived_at: row.archived_at == null ? null : Number(row.archived_at),
483
+ created_at: Number(row.created_at),
484
+ updated_at: Number(row.updated_at),
485
+ };
486
+ }
487
+
488
+ function _hydrateSwConfig(row) {
489
+ if (!row) return null;
490
+ var precache;
491
+ try { precache = JSON.parse(row.precache_urls_json || "[]"); }
492
+ catch (_e) { precache = []; }
493
+ var runtime;
494
+ try { runtime = JSON.parse(row.runtime_rules_json || "[]"); }
495
+ catch (_e) { runtime = []; }
496
+ return {
497
+ id: row.id,
498
+ version_number: Number(row.version_number),
499
+ cache_name: row.cache_name,
500
+ precache_urls: precache,
501
+ runtime_rules: runtime,
502
+ offline_fallback: row.offline_fallback == null ? null : row.offline_fallback,
503
+ navigation_fallback: row.navigation_fallback == null ? null : row.navigation_fallback,
504
+ is_active: row.is_active === 1 || row.is_active === true,
505
+ archived_at: row.archived_at == null ? null : Number(row.archived_at),
506
+ created_at: Number(row.created_at),
507
+ updated_at: Number(row.updated_at),
508
+ };
509
+ }
510
+
511
+ // ---- deterministic JSON serialization ---------------------------------
512
+ //
513
+ // `JSON.stringify(obj, replacer, 2)` orders keys by insertion. A
514
+ // byte-stable manifest output needs a sorted-key serializer so a
515
+ // `git diff` of two releases reflects an operator edit rather than
516
+ // JavaScript's object-property iteration order.
517
+
518
+ function _stableStringify(value, indent) {
519
+ function _walk(v) {
520
+ if (v === null || typeof v !== "object") return v;
521
+ if (Array.isArray(v)) {
522
+ var arr = [];
523
+ for (var i = 0; i < v.length; i += 1) arr.push(_walk(v[i]));
524
+ return arr;
525
+ }
526
+ var keys = Object.keys(v).sort();
527
+ var obj = {};
528
+ for (var k = 0; k < keys.length; k += 1) {
529
+ obj[keys[k]] = _walk(v[keys[k]]);
530
+ }
531
+ return obj;
532
+ }
533
+ return JSON.stringify(_walk(value), null, indent);
534
+ }
535
+
536
+ // ---- factory -----------------------------------------------------------
537
+
538
+ function create(opts) {
539
+ opts = opts || {};
540
+ var query = opts.query;
541
+ if (!query) {
542
+ query = function (sql, params) { return _b().externalDb.query(sql, params); };
543
+ }
544
+
545
+ if (typeof opts.cursorSecret !== "string" || !opts.cursorSecret.length) {
546
+ if (process.env.NODE_ENV === "production") {
547
+ throw new Error("pwaManifest.create: opts.cursorSecret is required in production");
548
+ }
549
+ opts.cursorSecret = "pwa-manifest-cursor-secret-dev-only";
550
+ }
551
+ var cursorSecret = opts.cursorSecret;
552
+
553
+ var MANIFEST_ORDER_KEY = ["version_number:desc"];
554
+ var SW_ORDER_KEY = ["version_number:desc"];
555
+
556
+ function _decodeCursor(cursor, label, orderKey) {
557
+ if (cursor == null) return null;
558
+ if (typeof cursor !== "string") {
559
+ throw new TypeError("pwaManifest." + label + ": cursor must be an opaque string or null");
560
+ }
561
+ try {
562
+ var state = _b().pagination.decodeCursor(cursor, cursorSecret);
563
+ if (JSON.stringify(state.orderKey) !== JSON.stringify(orderKey)) {
564
+ throw new TypeError("pwaManifest." + label + ": cursor orderKey mismatch");
565
+ }
566
+ return state.vals;
567
+ } catch (e) {
568
+ if (e instanceof TypeError) throw e;
569
+ throw new TypeError("pwaManifest." + label + ": cursor — " + (e && e.message || "malformed"));
570
+ }
571
+ }
572
+
573
+ function _encodeNext(rows, limit, orderKey) {
574
+ var last = rows[rows.length - 1];
575
+ if (!last || rows.length < limit) return null;
576
+ return _b().pagination.encodeCursor({
577
+ orderKey: orderKey,
578
+ vals: [Number(last.version_number)],
579
+ forward: true,
580
+ }, cursorSecret);
581
+ }
582
+
583
+ // Compute next version_number for the given table. Reading the
584
+ // MAX is fine — the table's UNIQUE(version_number) catches a
585
+ // hypothetical concurrent insert and the lib layer is single-
586
+ // writer in the worker by construction.
587
+ async function _nextVersion(table) {
588
+ var r = await query(
589
+ "SELECT MAX(version_number) AS mx FROM " + table,
590
+ [],
591
+ );
592
+ var mx = r.rows[0] && r.rows[0].mx;
593
+ return (mx == null ? 0 : Number(mx)) + 1;
594
+ }
595
+
596
+ // ---- defineManifest ----------------------------------------------------
597
+
598
+ async function defineManifest(input) {
599
+ if (!input || typeof input !== "object") {
600
+ throw new TypeError("pwaManifest.defineManifest: input object required");
601
+ }
602
+ var name = _string(input.name, "name", MAX_NAME_LEN);
603
+ var shortName = _string(input.short_name, "short_name", MAX_SHORT_NAME_LEN);
604
+ var description = _string(input.description, "description", MAX_DESCRIPTION_LEN);
605
+ var startUrl = _url(input.start_url, "start_url");
606
+ var scopeUrl = _url(input.scope, "scope");
607
+ var display = _enum(input.display, ALLOWED_DISPLAYS, "display");
608
+ var orientation = _enum(input.orientation, ALLOWED_ORIENTATIONS, "orientation");
609
+ var themeColor = _hexColor(input.theme_color, "theme_color");
610
+ var backgroundColor = _hexColor(input.background_color, "background_color");
611
+ var lang = input.lang == null ? "en" : _lang(input.lang);
612
+ var dir = input.dir == null ? "auto" : _enum(input.dir, ALLOWED_DIRS, "dir");
613
+ var icons = _validateIcons(input.icons);
614
+
615
+ var versionNumber = await _nextVersion("pwa_manifests");
616
+ var ts = _now();
617
+ var id = _b().uuid.v7();
618
+
619
+ await query(
620
+ "INSERT INTO pwa_manifests " +
621
+ "(id, version_number, name, short_name, description, start_url, scope, " +
622
+ " display, orientation, theme_color, background_color, lang, dir, icons_json, " +
623
+ " is_active, archived_at, created_at, updated_at) " +
624
+ "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, 0, NULL, ?15, ?15)",
625
+ [
626
+ id, versionNumber, name, shortName, description, startUrl, scopeUrl,
627
+ display, orientation, themeColor, backgroundColor, lang, dir,
628
+ JSON.stringify(icons), ts,
629
+ ],
630
+ );
631
+
632
+ var r = await query(
633
+ "SELECT * FROM pwa_manifests WHERE version_number = ?1",
634
+ [versionNumber],
635
+ );
636
+ return _hydrateManifest(r.rows[0]);
637
+ }
638
+
639
+ // ---- defineServiceWorkerConfig ----------------------------------------
640
+
641
+ async function defineServiceWorkerConfig(input) {
642
+ if (!input || typeof input !== "object") {
643
+ throw new TypeError("pwaManifest.defineServiceWorkerConfig: input object required");
644
+ }
645
+ var cacheName = _cacheName(input.cache_name);
646
+ var precacheUrls = _validatePrecacheUrls(input.precache_urls == null ? [] : input.precache_urls);
647
+ var runtimeRules = _validateRuntimeRules(input.runtime_rules == null ? [] : input.runtime_rules);
648
+ var offlineFallback = null;
649
+ var navigationFallback = null;
650
+ if (input.offline_fallback != null) {
651
+ offlineFallback = _url(input.offline_fallback, "offline_fallback");
652
+ }
653
+ if (input.navigation_fallback != null) {
654
+ navigationFallback = _url(input.navigation_fallback, "navigation_fallback");
655
+ }
656
+
657
+ var versionNumber = await _nextVersion("pwa_sw_configs");
658
+ var ts = _now();
659
+ var id = _b().uuid.v7();
660
+
661
+ await query(
662
+ "INSERT INTO pwa_sw_configs " +
663
+ "(id, version_number, cache_name, precache_urls_json, runtime_rules_json, " +
664
+ " offline_fallback, navigation_fallback, is_active, archived_at, created_at, updated_at) " +
665
+ "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, 0, NULL, ?8, ?8)",
666
+ [
667
+ id, versionNumber, cacheName, JSON.stringify(precacheUrls), JSON.stringify(runtimeRules),
668
+ offlineFallback, navigationFallback, ts,
669
+ ],
670
+ );
671
+
672
+ var r = await query(
673
+ "SELECT * FROM pwa_sw_configs WHERE version_number = ?1",
674
+ [versionNumber],
675
+ );
676
+ return _hydrateSwConfig(r.rows[0]);
677
+ }
678
+
679
+ // ---- getActive ---------------------------------------------------------
680
+
681
+ async function getActive(kind) {
682
+ var k = _scopeKind(kind);
683
+ if (k === "manifest") {
684
+ var r = await query(
685
+ "SELECT * FROM pwa_manifests WHERE is_active = 1 AND archived_at IS NULL LIMIT 1",
686
+ [],
687
+ );
688
+ return _hydrateManifest(r.rows[0] || null);
689
+ }
690
+ var rs = await query(
691
+ "SELECT * FROM pwa_sw_configs WHERE is_active = 1 AND archived_at IS NULL LIMIT 1",
692
+ [],
693
+ );
694
+ return _hydrateSwConfig(rs.rows[0] || null);
695
+ }
696
+
697
+ // ---- setActive ---------------------------------------------------------
698
+
699
+ async function setActive(arg1, arg2) {
700
+ // Two call shapes:
701
+ // setActive(versionNumber) → manifest scope
702
+ // setActive(kind, versionNumber) → explicit scope
703
+ var kind, versionNumber;
704
+ if (arg2 === undefined) {
705
+ kind = "manifest";
706
+ versionNumber = arg1;
707
+ } else {
708
+ kind = _scopeKind(arg1);
709
+ versionNumber = arg2;
710
+ }
711
+ versionNumber = _versionNumber(versionNumber, "version_number");
712
+
713
+ var table = kind === "manifest" ? "pwa_manifests" : "pwa_sw_configs";
714
+
715
+ var r = await query(
716
+ "SELECT * FROM " + table + " WHERE version_number = ?1",
717
+ [versionNumber],
718
+ );
719
+ var row = r.rows[0];
720
+ if (!row) {
721
+ var err = new Error(
722
+ "pwaManifest.setActive: " + kind + " version " + versionNumber + " not found"
723
+ );
724
+ err.code = "PWA_VERSION_NOT_FOUND";
725
+ throw err;
726
+ }
727
+ if (row.archived_at != null) {
728
+ var aErr = new Error(
729
+ "pwaManifest.setActive: " + kind + " version " + versionNumber + " is archived"
730
+ );
731
+ aErr.code = "PWA_VERSION_ARCHIVED";
732
+ throw aErr;
733
+ }
734
+ var ts = _now();
735
+ await query(
736
+ "UPDATE " + table + " SET is_active = 0, updated_at = ?1 " +
737
+ "WHERE is_active = 1 AND version_number != ?2",
738
+ [ts, versionNumber],
739
+ );
740
+ await query(
741
+ "UPDATE " + table + " SET is_active = 1, updated_at = ?1 " +
742
+ "WHERE version_number = ?2",
743
+ [ts, versionNumber],
744
+ );
745
+ var fresh = await query(
746
+ "SELECT * FROM " + table + " WHERE version_number = ?1",
747
+ [versionNumber],
748
+ );
749
+ return kind === "manifest"
750
+ ? _hydrateManifest(fresh.rows[0])
751
+ : _hydrateSwConfig(fresh.rows[0]);
752
+ }
753
+
754
+ // ---- listVersions ------------------------------------------------------
755
+
756
+ async function listVersions(arg1, arg2) {
757
+ // Two call shapes:
758
+ // listVersions(opts) → manifest scope
759
+ // listVersions(kind, opts) → explicit scope
760
+ var kind, listOpts;
761
+ if (arg2 === undefined) {
762
+ if (typeof arg1 === "string") {
763
+ kind = _scopeKind(arg1);
764
+ listOpts = {};
765
+ } else {
766
+ kind = "manifest";
767
+ listOpts = arg1 || {};
768
+ }
769
+ } else {
770
+ kind = _scopeKind(arg1);
771
+ listOpts = arg2 || {};
772
+ }
773
+
774
+ var table = kind === "manifest" ? "pwa_manifests" : "pwa_sw_configs";
775
+ var orderKey = kind === "manifest" ? MANIFEST_ORDER_KEY : SW_ORDER_KEY;
776
+ var limit = _limit(listOpts.limit);
777
+ var cursorVals = _decodeCursor(listOpts.cursor, "listVersions", orderKey);
778
+
779
+ var where = ["1 = 1"];
780
+ var params = [];
781
+ var idx = 1;
782
+ if (cursorVals) {
783
+ where.push("version_number < ?" + idx);
784
+ params.push(cursorVals[0]);
785
+ idx += 1;
786
+ }
787
+ params.push(limit);
788
+ var sql = "SELECT * FROM " + table + " WHERE " + where.join(" AND ") +
789
+ " ORDER BY version_number DESC LIMIT ?" + idx;
790
+ var r = await query(sql, params);
791
+ var rows = r.rows.map(kind === "manifest" ? _hydrateManifest : _hydrateSwConfig);
792
+ return { rows: rows, next_cursor: _encodeNext(r.rows, limit, orderKey) };
793
+ }
794
+
795
+ // ---- archiveVersion ----------------------------------------------------
796
+
797
+ async function archiveVersion(arg1, arg2) {
798
+ var kind, versionNumber;
799
+ if (arg2 === undefined) {
800
+ kind = "manifest";
801
+ versionNumber = arg1;
802
+ } else {
803
+ kind = _scopeKind(arg1);
804
+ versionNumber = arg2;
805
+ }
806
+ versionNumber = _versionNumber(versionNumber, "version_number");
807
+
808
+ var table = kind === "manifest" ? "pwa_manifests" : "pwa_sw_configs";
809
+
810
+ var r = await query(
811
+ "SELECT * FROM " + table + " WHERE version_number = ?1",
812
+ [versionNumber],
813
+ );
814
+ var row = r.rows[0];
815
+ if (!row) {
816
+ var err = new Error(
817
+ "pwaManifest.archiveVersion: " + kind + " version " + versionNumber + " not found"
818
+ );
819
+ err.code = "PWA_VERSION_NOT_FOUND";
820
+ throw err;
821
+ }
822
+ if (row.archived_at != null) {
823
+ return kind === "manifest" ? _hydrateManifest(row) : _hydrateSwConfig(row);
824
+ }
825
+ var ts = _now();
826
+ await query(
827
+ "UPDATE " + table + " SET archived_at = ?1, is_active = 0, updated_at = ?1 " +
828
+ "WHERE version_number = ?2",
829
+ [ts, versionNumber],
830
+ );
831
+ var fresh = await query(
832
+ "SELECT * FROM " + table + " WHERE version_number = ?1",
833
+ [versionNumber],
834
+ );
835
+ return kind === "manifest"
836
+ ? _hydrateManifest(fresh.rows[0])
837
+ : _hydrateSwConfig(fresh.rows[0]);
838
+ }
839
+
840
+ // ---- renderManifestJson ------------------------------------------------
841
+
842
+ async function renderManifestJson() {
843
+ var active = await getActive("manifest");
844
+ if (!active) {
845
+ var err = new Error("pwaManifest.renderManifestJson: no active manifest");
846
+ err.code = "PWA_NO_ACTIVE_MANIFEST";
847
+ throw err;
848
+ }
849
+ var manifestObj = {
850
+ name: active.name,
851
+ short_name: active.short_name,
852
+ description: active.description,
853
+ start_url: active.start_url,
854
+ scope: active.scope,
855
+ display: active.display,
856
+ orientation: active.orientation,
857
+ theme_color: active.theme_color,
858
+ background_color: active.background_color,
859
+ lang: active.lang,
860
+ dir: active.dir,
861
+ icons: active.icons,
862
+ };
863
+ return _stableStringify(manifestObj, 2);
864
+ }
865
+
866
+ // ---- renderServiceWorkerJs ---------------------------------------------
867
+ //
868
+ // Emit the JS bytes for `/sw.js`. The body is a self-contained
869
+ // script — install handler precaches the operator's URL list,
870
+ // fetch handler dispatches per-rule. Operator-sourced URLs and
871
+ // patterns are JSON.stringify'd into the body so a hostile URL
872
+ // can't break out of the string literal.
873
+
874
+ async function renderServiceWorkerJs() {
875
+ var active = await getActive("sw");
876
+ if (!active) {
877
+ var err = new Error("pwaManifest.renderServiceWorkerJs: no active service-worker config");
878
+ err.code = "PWA_NO_ACTIVE_SW";
879
+ throw err;
880
+ }
881
+ var lines = [];
882
+ lines.push("// service worker generated by shop.pwaManifest");
883
+ lines.push("// cache_name=" + JSON.stringify(active.cache_name) +
884
+ " version=" + active.version_number);
885
+ lines.push('"use strict";');
886
+ lines.push("var CACHE_NAME = " + JSON.stringify(active.cache_name) + ";");
887
+ lines.push("var PRECACHE_URLS = " + JSON.stringify(active.precache_urls) + ";");
888
+ lines.push("var RUNTIME_RULES = " + JSON.stringify(active.runtime_rules) + ";");
889
+ lines.push("var OFFLINE_FALLBACK = " +
890
+ (active.offline_fallback == null ? "null" : JSON.stringify(active.offline_fallback)) + ";");
891
+ lines.push("var NAVIGATION_FALLBACK = " +
892
+ (active.navigation_fallback == null ? "null" : JSON.stringify(active.navigation_fallback)) + ";");
893
+ lines.push("self.addEventListener('install', function (e) {");
894
+ lines.push(" e.waitUntil(caches.open(CACHE_NAME).then(function (c) {");
895
+ lines.push(" return c.addAll(PRECACHE_URLS);");
896
+ lines.push(" }).then(function () { return self.skipWaiting(); }));");
897
+ lines.push("});");
898
+ lines.push("self.addEventListener('activate', function (e) {");
899
+ lines.push(" e.waitUntil(caches.keys().then(function (keys) {");
900
+ lines.push(" return Promise.all(keys.map(function (k) {");
901
+ lines.push(" if (k !== CACHE_NAME) return caches.delete(k);");
902
+ lines.push(" }));");
903
+ lines.push(" }).then(function () { return self.clients.claim(); }));");
904
+ lines.push("});");
905
+ lines.push("function _matchRule(url) {");
906
+ lines.push(" for (var i = 0; i < RUNTIME_RULES.length; i += 1) {");
907
+ lines.push(" if (url.indexOf(RUNTIME_RULES[i].url_pattern) !== -1) return RUNTIME_RULES[i];");
908
+ lines.push(" }");
909
+ lines.push(" return null;");
910
+ lines.push("}");
911
+ lines.push("function _cacheFirst(req) {");
912
+ lines.push(" return caches.match(req).then(function (hit) {");
913
+ lines.push(" return hit || fetch(req).then(function (res) {");
914
+ lines.push(" var copy = res.clone();");
915
+ lines.push(" caches.open(CACHE_NAME).then(function (c) { c.put(req, copy); });");
916
+ lines.push(" return res;");
917
+ lines.push(" });");
918
+ lines.push(" });");
919
+ lines.push("}");
920
+ lines.push("function _networkFirst(req) {");
921
+ lines.push(" return fetch(req).then(function (res) {");
922
+ lines.push(" var copy = res.clone();");
923
+ lines.push(" caches.open(CACHE_NAME).then(function (c) { c.put(req, copy); });");
924
+ lines.push(" return res;");
925
+ lines.push(" }).catch(function () { return caches.match(req); });");
926
+ lines.push("}");
927
+ lines.push("function _staleWhileRevalidate(req) {");
928
+ lines.push(" var hit = caches.match(req);");
929
+ lines.push(" var net = fetch(req).then(function (res) {");
930
+ lines.push(" var copy = res.clone();");
931
+ lines.push(" caches.open(CACHE_NAME).then(function (c) { c.put(req, copy); });");
932
+ lines.push(" return res;");
933
+ lines.push(" }).catch(function () { return null; });");
934
+ lines.push(" return hit.then(function (h) { return h || net; });");
935
+ lines.push("}");
936
+ lines.push("self.addEventListener('fetch', function (e) {");
937
+ lines.push(" if (e.request.method !== 'GET') return;");
938
+ lines.push(" var url = e.request.url;");
939
+ lines.push(" var rule = _matchRule(url);");
940
+ lines.push(" if (rule) {");
941
+ lines.push(" if (rule.strategy === 'cache-first') { e.respondWith(_cacheFirst(e.request)); return; }");
942
+ lines.push(" if (rule.strategy === 'network-first') { e.respondWith(_networkFirst(e.request)); return; }");
943
+ lines.push(" if (rule.strategy === 'stale-while-revalidate') { e.respondWith(_staleWhileRevalidate(e.request)); return; }");
944
+ lines.push(" /* network-only: fall through to default fetch */");
945
+ lines.push(" }");
946
+ lines.push(" if (e.request.mode === 'navigate' && NAVIGATION_FALLBACK) {");
947
+ lines.push(" e.respondWith(fetch(e.request).catch(function () {");
948
+ lines.push(" return caches.match(NAVIGATION_FALLBACK);");
949
+ lines.push(" }));");
950
+ lines.push(" return;");
951
+ lines.push(" }");
952
+ lines.push(" if (OFFLINE_FALLBACK) {");
953
+ lines.push(" e.respondWith(fetch(e.request).catch(function () {");
954
+ lines.push(" return caches.match(OFFLINE_FALLBACK);");
955
+ lines.push(" }));");
956
+ lines.push(" }");
957
+ lines.push("});");
958
+ return lines.join("\n") + "\n";
959
+ }
960
+
961
+ return {
962
+ ALLOWED_DISPLAYS: ALLOWED_DISPLAYS,
963
+ ALLOWED_ORIENTATIONS: ALLOWED_ORIENTATIONS,
964
+ ALLOWED_DIRS: ALLOWED_DIRS,
965
+ ALLOWED_STRATEGIES: ALLOWED_STRATEGIES,
966
+ ALLOWED_ICON_PURPOSES: ALLOWED_ICON_PURPOSES,
967
+ ALLOWED_SCOPES: ALLOWED_SCOPES,
968
+ MAX_NAME_LEN: MAX_NAME_LEN,
969
+ MAX_SHORT_NAME_LEN: MAX_SHORT_NAME_LEN,
970
+ MAX_DESCRIPTION_LEN: MAX_DESCRIPTION_LEN,
971
+ MAX_URL_LEN: MAX_URL_LEN,
972
+ MAX_ICON_COUNT: MAX_ICON_COUNT,
973
+ MAX_PRECACHE_URLS: MAX_PRECACHE_URLS,
974
+ MAX_RUNTIME_RULES: MAX_RUNTIME_RULES,
975
+ MAX_LIST_LIMIT: MAX_LIST_LIMIT,
976
+
977
+ defineManifest: defineManifest,
978
+ defineServiceWorkerConfig: defineServiceWorkerConfig,
979
+ getActive: getActive,
980
+ setActive: setActive,
981
+ renderManifestJson: renderManifestJson,
982
+ renderServiceWorkerJs: renderServiceWorkerJs,
983
+ listVersions: listVersions,
984
+ archiveVersion: archiveVersion,
985
+ validateIcons: function (icons) { return _validateIcons(icons); },
986
+ };
987
+ }
988
+
989
+ module.exports = {
990
+ create: create,
991
+ ALLOWED_DISPLAYS: ALLOWED_DISPLAYS,
992
+ ALLOWED_ORIENTATIONS: ALLOWED_ORIENTATIONS,
993
+ ALLOWED_DIRS: ALLOWED_DIRS,
994
+ ALLOWED_STRATEGIES: ALLOWED_STRATEGIES,
995
+ ALLOWED_ICON_PURPOSES: ALLOWED_ICON_PURPOSES,
996
+ ALLOWED_SCOPES: ALLOWED_SCOPES,
997
+ MAX_NAME_LEN: MAX_NAME_LEN,
998
+ MAX_SHORT_NAME_LEN: MAX_SHORT_NAME_LEN,
999
+ MAX_DESCRIPTION_LEN: MAX_DESCRIPTION_LEN,
1000
+ MAX_URL_LEN: MAX_URL_LEN,
1001
+ MAX_ICON_COUNT: MAX_ICON_COUNT,
1002
+ MAX_PRECACHE_URLS: MAX_PRECACHE_URLS,
1003
+ MAX_RUNTIME_RULES: MAX_RUNTIME_RULES,
1004
+ MAX_LIST_LIMIT: MAX_LIST_LIMIT,
1005
+ };