@ultimat3/core 22.3.2 → 22.3.4
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/package.json +2 -2
- package/src/config-pwa.ts +86 -0
- package/src/config.ts +1 -1
- package/src/index.ts +10 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultimat3/core",
|
|
3
|
-
"version": "22.3.
|
|
3
|
+
"version": "22.3.4",
|
|
4
4
|
"description": "Ultimate's foundation: errors, context, env, config, clock, ids, logging, telemetry, lifecycle",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -40,6 +40,6 @@
|
|
|
40
40
|
"test": "bun test"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@ultimat3/schema": "22.3.
|
|
43
|
+
"@ultimat3/schema": "22.3.4"
|
|
44
44
|
}
|
|
45
45
|
}
|
package/src/config-pwa.ts
CHANGED
|
@@ -37,6 +37,12 @@ export interface PwaOfflineConfig {
|
|
|
37
37
|
readonly image: string | null;
|
|
38
38
|
readonly font: string | null;
|
|
39
39
|
readonly neverCache: readonly string[];
|
|
40
|
+
/**
|
|
41
|
+
* A page rendered for someone (a policy, a `stream`, a `no-store`/`private` cache). `'never'`
|
|
42
|
+
* (default): not cached anywhere, its route `network-only`. `'last-member'`: kept offline for the
|
|
43
|
+
* most recent member only — declare it only with a sign-out that clears it (`clear-pages`).
|
|
44
|
+
*/
|
|
45
|
+
readonly personalPages: 'never' | 'last-member';
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
export interface PwaConfig {
|
|
@@ -58,6 +64,52 @@ export interface PwaConfig {
|
|
|
58
64
|
* worse than a boot that names the four values it needs.
|
|
59
65
|
*/
|
|
60
66
|
readonly colors: PwaColors | undefined;
|
|
67
|
+
/**
|
|
68
|
+
* The manifest `id`: one installed app across every locale's manifest. Absent, the scope (`/`),
|
|
69
|
+
* which is what a browser computes from the default locale's `start_url` anyway.
|
|
70
|
+
*/
|
|
71
|
+
readonly id?: string;
|
|
72
|
+
/** The install sheet's description. One string, or one per locale (`{ 'es-co': …, en: … }`). */
|
|
73
|
+
readonly description?: PwaText;
|
|
74
|
+
/** `categories`, e.g. `['business', 'productivity']` — W3C's list is a hint, not an enum. */
|
|
75
|
+
readonly categories?: readonly string[];
|
|
76
|
+
/** Home-screen shortcuts. `url` is the DEFAULT locale's path; each manifest spells it in its own. */
|
|
77
|
+
readonly shortcuts?: readonly PwaShortcut[];
|
|
78
|
+
/** Chrome's richer install sheet. `src` may differ per locale — a screenshot shows copy. */
|
|
79
|
+
readonly screenshots?: readonly PwaScreenshot[];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Manifest text, per locale where it is read: a string is every locale's, a record is keyed by
|
|
84
|
+
* locale tag and falls back to the default locale's entry. Each locale gets its own manifest
|
|
85
|
+
* (`/manifest.webmanifest`, `/en/manifest.webmanifest`), so each speaks its own language.
|
|
86
|
+
*/
|
|
87
|
+
export type PwaText = string | Readonly<Record<string, string>>;
|
|
88
|
+
|
|
89
|
+
export interface PwaImage {
|
|
90
|
+
/** Absolute path, e.g. `/assets/shortcut-panel.png`. */
|
|
91
|
+
readonly src: string;
|
|
92
|
+
readonly sizes: string;
|
|
93
|
+
readonly type: string;
|
|
94
|
+
readonly purpose?: 'any' | 'maskable' | 'monochrome';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface PwaShortcut {
|
|
98
|
+
readonly name: PwaText;
|
|
99
|
+
readonly shortName?: PwaText;
|
|
100
|
+
readonly description?: PwaText;
|
|
101
|
+
/** The default locale's absolute path (`/panel`); the `en` manifest names `/en/panel`. */
|
|
102
|
+
readonly url: string;
|
|
103
|
+
readonly icons?: readonly PwaImage[];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface PwaScreenshot {
|
|
107
|
+
/** Absolute path, or one per locale. */
|
|
108
|
+
readonly src: PwaText;
|
|
109
|
+
readonly sizes: string;
|
|
110
|
+
readonly type: string;
|
|
111
|
+
readonly formFactor?: 'narrow' | 'wide';
|
|
112
|
+
readonly label?: PwaText;
|
|
61
113
|
}
|
|
62
114
|
|
|
63
115
|
/**
|
|
@@ -127,6 +179,12 @@ export function pwaIssues(pwa: PwaConfig, issues: string[]): boolean {
|
|
|
127
179
|
`pwa.offline.fallback is required when pwa.enabled is true and must be an absolute route path like "/offline", and is ${describeValue(fallback)}`,
|
|
128
180
|
);
|
|
129
181
|
}
|
|
182
|
+
const personal: unknown = pwa.offline?.personalPages;
|
|
183
|
+
if (personal !== undefined && personal !== 'never' && personal !== 'last-member') {
|
|
184
|
+
issues.push(
|
|
185
|
+
`pwa.offline.personalPages must be 'never' or 'last-member', and is ${describeValue(personal)}`,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
130
188
|
// `typeof !== 'object' || null`, never `=== undefined`: an untyped `app.config.ts` writing
|
|
131
189
|
// `pwa.colors: null` reached `null[scheme]` one line down and took the boot out with a native
|
|
132
190
|
// `TypeError`, from the validator whose whole job is producing an instruction instead of one.
|
|
@@ -145,5 +203,33 @@ export function pwaIssues(pwa: PwaConfig, issues: string[]): boolean {
|
|
|
145
203
|
}
|
|
146
204
|
}
|
|
147
205
|
}
|
|
206
|
+
manifestIssues(pwa, issues);
|
|
148
207
|
return issues.length > before;
|
|
149
208
|
}
|
|
209
|
+
|
|
210
|
+
/** A path the manifest names must be absolute: a relative one resolves against the manifest's URL. */
|
|
211
|
+
const absolute = (value: unknown): boolean => typeof value === 'string' && value.startsWith('/');
|
|
212
|
+
|
|
213
|
+
const texts = (value: unknown): readonly unknown[] =>
|
|
214
|
+
value !== null && typeof value === 'object' ? Object.values(value) : [value];
|
|
215
|
+
|
|
216
|
+
/** The optional manifest members, screened for the one mistake a browser drops them for silently. */
|
|
217
|
+
function manifestIssues(pwa: PwaConfig, issues: string[]): void {
|
|
218
|
+
if (pwa.id !== undefined && !absolute(pwa.id)) {
|
|
219
|
+
issues.push(`pwa.id must be an absolute path like "/", and is ${describeValue(pwa.id)}`);
|
|
220
|
+
}
|
|
221
|
+
for (const [i, shortcut] of (pwa.shortcuts ?? []).entries()) {
|
|
222
|
+
if (!absolute(shortcut?.url)) {
|
|
223
|
+
issues.push(
|
|
224
|
+
`pwa.shortcuts[${i}].url must be an absolute path, and is ${describeValue(shortcut?.url)}`,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
for (const [i, screenshot] of (pwa.screenshots ?? []).entries()) {
|
|
229
|
+
if (!texts(screenshot?.src).every(absolute)) {
|
|
230
|
+
issues.push(
|
|
231
|
+
`pwa.screenshots[${i}].src must be an absolute path (or one per locale), and is ${describeValue(screenshot?.src)}`,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -285,7 +285,7 @@ function defaults(name: string): Omit<AppConfig, 'name' | 'site' | 'seo'> {
|
|
|
285
285
|
auth: { signInPath: null },
|
|
286
286
|
pwa: {
|
|
287
287
|
enabled: false,
|
|
288
|
-
offline: { fallback: null, image: null, font: null, neverCache: [] },
|
|
288
|
+
offline: { fallback: null, image: null, font: null, neverCache: [], personalPages: 'never' },
|
|
289
289
|
backgroundSync: false,
|
|
290
290
|
push: false,
|
|
291
291
|
name: '',
|
package/src/index.ts
CHANGED
|
@@ -109,7 +109,16 @@ export type {
|
|
|
109
109
|
ThemeMode,
|
|
110
110
|
} from './config';
|
|
111
111
|
export { defineConfig, INBOX_RETENTION_KEYS } from './config';
|
|
112
|
-
export type {
|
|
112
|
+
export type {
|
|
113
|
+
PwaColors,
|
|
114
|
+
PwaConfig,
|
|
115
|
+
PwaImage,
|
|
116
|
+
PwaOfflineConfig,
|
|
117
|
+
PwaSchemeColors,
|
|
118
|
+
PwaScreenshot,
|
|
119
|
+
PwaShortcut,
|
|
120
|
+
PwaText,
|
|
121
|
+
} from './config-pwa';
|
|
113
122
|
export { PWA_COLOR_KEYS, PWA_SCHEMES } from './config-pwa';
|
|
114
123
|
export type {
|
|
115
124
|
SeoConfig,
|