managarten/packages/shared-pwa/src/config.ts
Till JS 8a991f7c39 feat(articles): M7 share-target + bookmarklet — save from anywhere
@mana/shared-pwa gains PWAShareTarget + PWAShareTargetParams types
plus ManifestConfig.share_target pass-through. createPWAConfig now
accepts an optional `shareTarget` and threads it into the generated
manifest. Other apps keep working unchanged — the field is omitted
unless set.

Web app wiring:
 - vite.config.ts passes shareTarget: { action: '/articles/add',
   method: 'GET', params: { title, text, url } } so the installed PWA
   shows up as a destination in the Android / Chromium share sheet.
 - AddUrlForm reads ?url / ?text / ?title in onMount; falls back to
   the first URL-shaped token in ?text because some senders (Chrome
   Android, WhatsApp) put the shared link there instead of ?url. When
   a URL is pre-filled the Readability preview auto-triggers, so the
   user just hits "In Leseliste speichern" to confirm.
 - New /articles/settings route hosts the bookmarklet (drag-to-
   bookmarks-bar button + copy-to-clipboard + expandable snippet
   viewer) and a short Share-Target explainer with an iOS-Safari
   caveat. Linked from the ListView via a new gear button next to
   "+ Neu speichern".

Bookmarklet form (origin-prefixed so it works across tenants):
  javascript:void(window.open('${origin}/articles/add?url='+…))

Not in scope (plan marked optional): _pendingUrls offline queue.
Share without internet shows the existing error + retry state today;
can slot in as M7b if users hit it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 19:03:33 +02:00

157 lines
3.9 KiB
TypeScript

/**
* PWA Configuration Factory
*
* Creates a complete @vite-pwa/sveltekit configuration with sensible defaults
* and preset-based caching strategies.
*
* @example
* ```ts
* import { createPWAConfig } from '@mana/shared-pwa';
* import { SvelteKitPWA } from '@vite-pwa/sveltekit';
*
* export default defineConfig({
* plugins: [
* sveltekit(),
* SvelteKitPWA(createPWAConfig({
* name: 'Calendar - Kalender',
* shortName: 'Calendar',
* description: 'Kalender mit Offline-Unterstützung',
* themeColor: '#3b82f6',
* preset: 'standard',
* })),
* ],
* });
* ```
*/
import type { PWAConfigOptions, PWAConfig, ManifestConfig, WorkboxConfig } from './types.js';
import {
DEFAULT_BACKGROUND_COLOR,
DEFAULT_CATEGORIES,
DEFAULT_INCLUDE_ASSETS,
DEFAULT_GLOB_PATTERNS,
DEFAULT_GLOB_IGNORES,
DEFAULT_NAVIGATE_FALLBACK_DENYLIST,
DEFAULT_ICONS,
} from './defaults.js';
import { getPresetRuntimeCaching } from './presets.js';
/**
* Create a complete PWA configuration for SvelteKit apps
*/
export function createPWAConfig(options: PWAConfigOptions): PWAConfig {
const {
name,
shortName,
description,
themeColor,
backgroundColor = DEFAULT_BACKGROUND_COLOR,
preset = 'standard',
shortcuts = [],
shareTarget,
categories = DEFAULT_CATEGORIES,
includeAssets = [],
globIgnores = [],
additionalRuntimeCaching = [],
navigateFallback = '/offline',
navigateFallbackDenylist = DEFAULT_NAVIGATE_FALLBACK_DENYLIST,
devEnabled = true,
registerType = 'autoUpdate',
lang = 'de',
startUrl = '/',
} = options;
// Build manifest
const manifest: ManifestConfig = {
// Pin the app identity to the start URL. Without `id`, Chrome derives
// one from start_url and warns in DevTools; it also refuses to
// re-prompt an install if start_url ever changes.
id: startUrl,
name,
short_name: shortName,
description,
theme_color: themeColor,
background_color: backgroundColor,
display: 'standalone',
orientation: 'any',
scope: '/',
start_url: startUrl,
lang,
categories,
icons: DEFAULT_ICONS,
};
// Add shortcuts if provided
if (shortcuts.length > 0) {
manifest.shortcuts = shortcuts.map((shortcut) => ({
name: shortcut.name,
short_name: shortcut.short_name,
description: shortcut.description,
url: shortcut.url,
icons: [{ src: 'pwa-192x192.png', sizes: '192x192' }],
}));
}
// Web Share Target — lets installed PWAs appear in the OS share
// sheet. Browsers that don't support the spec ignore the field.
if (shareTarget) {
manifest.share_target = {
action: shareTarget.action,
method: shareTarget.method ?? 'GET',
enctype: shareTarget.enctype,
params: shareTarget.params,
};
}
// Build workbox config
const workbox: WorkboxConfig = {
globPatterns: DEFAULT_GLOB_PATTERNS,
globIgnores: [...DEFAULT_GLOB_IGNORES, ...globIgnores],
cleanupOutdatedCaches: true,
clientsClaim: true,
skipWaiting: true,
navigateFallback,
navigateFallbackDenylist,
runtimeCaching: [...getPresetRuntimeCaching(preset), ...additionalRuntimeCaching],
maximumFileSizeToCacheInBytes: 8 * 1024 * 1024, // 8 MiB for large unified apps
};
// Return complete config
return {
registerType,
devOptions: {
enabled: devEnabled,
},
includeAssets: [...DEFAULT_INCLUDE_ASSETS, ...includeAssets],
manifest,
workbox,
};
}
/**
* Create PWA config with SQLite WASM support (for offline-first apps)
* Adds proper glob ignores and OPFS configuration
*/
export function createOfflineFirstPWAConfig(
options: PWAConfigOptions & {
/**
* Additional packages to exclude from precaching
*/
excludePackages?: string[];
}
): PWAConfig {
const { excludePackages = [], globIgnores = [], ...rest } = options;
// Add SQLite-specific ignores
const allGlobIgnores = [
'**/*sqlite*',
'**/*wasm*',
...excludePackages.map((pkg) => `**/${pkg}/**`),
...globIgnores,
];
return createPWAConfig({
...rest,
globIgnores: allGlobIgnores,
});
}