@mywallpaper/addon-sdk 2.7.0 → 2.8.1
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/README.md +1 -1
- package/dist/index.d.mts +147 -2
- package/dist/index.d.ts +147 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/manifest.d.mts +147 -2
- package/dist/manifest.d.ts +147 -2
- package/dist/manifest.js.map +1 -1
- package/dist/manifest.mjs.map +1 -1
- package/package.json +1 -1
- package/src/runtime/addon-client.js +160 -1
package/dist/manifest.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/manifest.ts"],"names":[],"mappings":"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","file":"manifest.js","sourcesContent":["/**\n * @mywallpaper/addon-sdk - Manifest Schema & Validation\n *\n * Defines the addon manifest format (manifest.json) and provides\n * Zod-based validation for type safety at runtime.\n *\n * @example\n * ```typescript\n * import { validateManifest, type AddonManifest } from '@mywallpaper/addon-sdk/manifest'\n *\n * const manifest: AddonManifest = {\n * name: 'My Addon',\n * version: '1.0.0',\n * sdkVersion: '2.3.0',\n * description: 'A cool addon',\n * settings: {\n * primaryColor: { type: 'color', default: '#ffffff', label: 'Primary Color' }\n * }\n * }\n *\n * // Validate at runtime\n * const result = validateManifest(manifest)\n * if (!result.success) {\n * console.error('Invalid manifest:', result.errors)\n * }\n * ```\n */\n\nimport type { SystemEventType, PermissionType, OAuthProvider } from './types'\n\n// =============================================================================\n// SETTING TYPES\n// =============================================================================\n\n/**\n * Available setting types for addon configuration.\n */\nexport type SettingType =\n | 'string' // Text input\n | 'number' // Numeric input\n | 'boolean' // Checkbox/toggle\n | 'color' // Color picker\n | 'select' // Dropdown selection\n | 'range' // Slider\n | 'image' // Image upload\n | 'video' // Video upload\n | 'audio' // Audio upload (mp3, wav, ogg, etc.)\n | 'file' // Generic file upload (images + videos + audio)\n | 'section' // Visual grouping (no value)\n | 'textarea' // Multi-line text\n | 'radio' // Radio buttons\n | 'gradient' // Gradient picker\n | 'vector2' // 2D vector (x, y)\n | 'button' // Action button (no value)\n | 'multi-select' // Multiple selection\n\n/**\n * Definition for a single addon setting.\n *\n * @example\n * ```typescript\n * const colorSetting: SettingDefinition = {\n * type: 'color',\n * label: 'Background Color',\n * description: 'Choose a background color',\n * default: '#000000'\n * }\n *\n * const speedSetting: SettingDefinition = {\n * type: 'range',\n * label: 'Animation Speed',\n * min: 0.1,\n * max: 10,\n * step: 0.1,\n * default: 1\n * }\n * ```\n */\nexport interface SettingDefinition {\n /** Setting type determines the UI control */\n type: SettingType\n\n /** Human-readable label */\n label?: string\n\n /** Help text shown below the control */\n description?: string\n\n /** Default value (type depends on setting type) */\n default?: unknown\n\n // Select/Radio/Multi-select options\n /** Options for select, radio, multi-select types */\n options?: Array<{ value: string; label: string }>\n\n // Range-specific\n /** Minimum value for range type */\n min?: number\n /** Maximum value for range type */\n max?: number\n /** Step increment for range type */\n step?: number\n\n // Button-specific\n /** Label text on button */\n buttonLabel?: string\n\n // Vector-specific\n /** Axis labels for vector types, e.g., ['X', 'Y'] */\n axisLabels?: [string, string] | [string, string, string]\n\n // Multi-select specific\n /** Maximum number of selections */\n maxItems?: number\n\n // Conditional visibility\n /** Only show if another setting has a specific value */\n showIf?: {\n setting: string\n equals: unknown\n }\n}\n\n// =============================================================================\n// MANIFEST SCHEMA\n// =============================================================================\n\n/**\n * Default layout configuration for addon positioning.\n */\nexport interface AddonDefaultLayout {\n /** X position as percentage of viewport width (0-100) */\n xPercent?: number\n /** Y position as percentage of viewport height (0-100) */\n yPercent?: number\n /** Width as percentage of viewport width (0-100) */\n widthPercent?: number\n /** Height as percentage of viewport height (0-100) */\n heightPercent?: number\n /** Rotation in degrees */\n rotation?: number\n}\n\n/**\n * The complete addon manifest schema.\n * This defines your addon's identity, settings, and capabilities.\n *\n * @example\n * ```json\n * {\n * \"name\": \"Weather Widget\",\n * \"version\": \"1.2.0\",\n * \"description\": \"Displays current weather conditions\",\n * \"author\": \"Jane Developer\",\n * \"categories\": [\"utilities\", \"weather\"],\n * \"capabilities\": {\n * \"hotReload\": true,\n * \"systemEvents\": [\"viewport:resize\", \"theme:change\"]\n * },\n * \"permissions\": {\n * \"storage\": { \"quota\": 512 },\n * \"network\": { \"domains\": [\"api.openweathermap.org\"] }\n * },\n * \"settings\": {\n * \"apiKey\": {\n * \"type\": \"string\",\n * \"label\": \"API Key\",\n * \"description\": \"Get your key at openweathermap.org\"\n * },\n * \"unit\": {\n * \"type\": \"select\",\n * \"label\": \"Temperature Unit\",\n * \"default\": \"celsius\",\n * \"options\": [\n * { \"value\": \"celsius\", \"label\": \"Celsius\" },\n * { \"value\": \"fahrenheit\", \"label\": \"Fahrenheit\" }\n * ]\n * }\n * }\n * }\n * ```\n */\nexport interface AddonManifest {\n // ---------------------------------------------------------------------------\n // REQUIRED FIELDS\n // ---------------------------------------------------------------------------\n\n /** Addon name (displayed in UI) */\n name: string\n\n /** Semantic version (e.g., \"1.0.0\") */\n version: string\n\n /**\n * SDK version this addon was built for.\n * Must be a valid semver (e.g., \"2.3.0\").\n * Host uses this to inject the correct client-side SDK.\n */\n sdkVersion: string\n\n // ---------------------------------------------------------------------------\n // OPTIONAL METADATA\n // ---------------------------------------------------------------------------\n\n /** Short description of the addon */\n description?: string\n\n /** Author name or organization */\n author?: string\n\n /** Addon type/category for filtering */\n type?: string\n\n /** Categories for discoverability */\n categories?: string[]\n\n /** License (e.g., \"MIT\", \"Apache-2.0\") */\n license?: string\n\n /** Homepage or documentation URL */\n homepage?: string\n\n /** Repository URL */\n repository?: string\n\n // ---------------------------------------------------------------------------\n // SETTINGS\n // ---------------------------------------------------------------------------\n\n /**\n * User-configurable settings.\n * Key is the setting ID, value is the definition.\n */\n settings?: Record<string, SettingDefinition>\n\n // ---------------------------------------------------------------------------\n // CAPABILITIES\n // ---------------------------------------------------------------------------\n\n /**\n * Addon capabilities and event subscriptions.\n */\n capabilities?: {\n /** Supports settings changes without full reload */\n hotReload?: boolean\n /** System events the addon wants to receive */\n systemEvents?: SystemEventType[]\n }\n\n // ---------------------------------------------------------------------------\n // PERMISSIONS\n // ---------------------------------------------------------------------------\n\n /**\n * Permissions the addon will request.\n * Declaring them here improves UX by showing upfront.\n */\n permissions?: {\n /** Storage permission with quota in KB */\n storage?: { quota: number }\n /** CPU usage limit */\n cpu?: { limit: 'low' | 'medium' | 'high' }\n /** Network access with allowed domains */\n network?: { domains: string[] }\n /** Audio playback permission */\n audio?: boolean\n /** Notification permission */\n notifications?: boolean\n /**\n * File access permission for uploaded media files.\n * When enabled, the addon can receive uploaded files (video, audio, images)\n * via secure blob transfer for playback.\n *\n * This is required for addons that play user-uploaded media content.\n */\n fileAccess?: {\n /** User-friendly reason why this permission is needed */\n reason: string\n }\n /**\n * Same-origin permission for blob URL support.\n * Required for addons that handle large local files (videos, audio).\n *\n * ⚠️ SECURITY WARNING: This allows the addon to access:\n * - localStorage/sessionStorage of the parent origin\n * - Cookies of the parent domain\n * - IndexedDB of the parent origin\n *\n * Only grant to trusted addons!\n */\n sameOrigin?: {\n /** User-friendly reason why this permission is needed */\n reason: string\n }\n }\n\n // ---------------------------------------------------------------------------\n // LAYOUT\n // ---------------------------------------------------------------------------\n\n /**\n * Default layout for automatic positioning.\n * Used when addon is first added to a wallpaper.\n */\n defaultLayout?: AddonDefaultLayout\n\n // ---------------------------------------------------------------------------\n // FUTURE FIELDS\n // ---------------------------------------------------------------------------\n\n /**\n * OAuth providers and scopes for authenticated addons.\n *\n * Declare the OAuth providers your addon needs and the scopes it requires.\n * Scopes are requested incrementally - users can connect with basic scopes\n * first, and your addon can request additional scopes when needed.\n *\n * @example\n * ```json\n * {\n * \"permissions\": {\n * \"oauth\": {\n * \"required\": [\n * { \"provider\": \"github\", \"scopes\": [\"read:user\"] }\n * ],\n * \"optional\": [\n * { \"provider\": \"github\", \"scopes\": [\"repo\"] }\n * ]\n * }\n * }\n * }\n * ```\n */\n oauth?: {\n /** OAuth providers required for the addon to function */\n required?: Array<{\n provider: OAuthProvider\n scopes: string[]\n /** Reason shown to user when requesting this permission */\n reason?: string\n }>\n /** OAuth providers that enhance functionality but aren't required */\n optional?: Array<{\n provider: OAuthProvider\n scopes: string[]\n /** Reason shown to user when requesting this permission */\n reason?: string\n }>\n }\n}\n\n// =============================================================================\n// VALIDATION\n// =============================================================================\n\n/**\n * Validation result type.\n */\nexport interface ValidationResult {\n success: boolean\n errors?: string[]\n manifest?: AddonManifest\n}\n\n/**\n * Validates an addon manifest object.\n *\n * @param input - The manifest object to validate\n * @returns ValidationResult with success status and any errors\n *\n * @example\n * ```typescript\n * const result = validateManifest(myManifest)\n * if (!result.success) {\n * result.errors?.forEach(err => console.error(err))\n * }\n * ```\n */\nexport function validateManifest(input: unknown): ValidationResult {\n const errors: string[] = []\n\n if (!input || typeof input !== 'object') {\n return { success: false, errors: ['Manifest must be an object'] }\n }\n\n const manifest = input as Record<string, unknown>\n\n // Required fields\n if (typeof manifest.name !== 'string' || manifest.name.trim() === '') {\n errors.push('name: Required and must be a non-empty string')\n }\n\n if (typeof manifest.version !== 'string' || !/^\\d+\\.\\d+\\.\\d+/.test(manifest.version)) {\n errors.push('version: Required and must be a valid semver (e.g., \"1.0.0\")')\n }\n\n if (typeof manifest.sdkVersion !== 'string' || !/^\\d+\\.\\d+\\.\\d+/.test(manifest.sdkVersion)) {\n errors.push('sdkVersion: Required and must be a valid semver (e.g., \"2.2.0\")')\n }\n\n // Optional string fields\n const optionalStrings = ['description', 'author', 'type', 'license', 'homepage', 'repository']\n for (const field of optionalStrings) {\n if (manifest[field] !== undefined && typeof manifest[field] !== 'string') {\n errors.push(`${field}: Must be a string`)\n }\n }\n\n // Categories\n if (manifest.categories !== undefined) {\n if (!Array.isArray(manifest.categories)) {\n errors.push('categories: Must be an array of strings')\n } else if (!manifest.categories.every(c => typeof c === 'string')) {\n errors.push('categories: All items must be strings')\n }\n }\n\n // Settings validation\n if (manifest.settings !== undefined) {\n if (typeof manifest.settings !== 'object' || manifest.settings === null) {\n errors.push('settings: Must be an object')\n } else {\n const settings = manifest.settings as Record<string, unknown>\n for (const [key, value] of Object.entries(settings)) {\n if (typeof value !== 'object' || value === null) {\n errors.push(`settings.${key}: Must be an object`)\n continue\n }\n\n const setting = value as Record<string, unknown>\n const validTypes: SettingType[] = [\n 'string', 'number', 'boolean', 'color', 'select', 'range',\n 'image', 'video', 'audio', 'file', 'section', 'textarea', 'radio', 'gradient', 'vector2',\n 'button', 'multi-select'\n ]\n\n if (!validTypes.includes(setting.type as SettingType)) {\n errors.push(`settings.${key}.type: Must be one of: ${validTypes.join(', ')}`)\n }\n\n // Type-specific validation\n if (['select', 'radio', 'multi-select'].includes(setting.type as string)) {\n if (!Array.isArray(setting.options) || setting.options.length === 0) {\n errors.push(`settings.${key}.options: Required for ${setting.type} type`)\n }\n }\n\n if (setting.type === 'range') {\n if (typeof setting.min !== 'number' || typeof setting.max !== 'number') {\n errors.push(`settings.${key}: range type requires min and max`)\n }\n }\n }\n }\n }\n\n // Capabilities validation\n if (manifest.capabilities !== undefined) {\n if (typeof manifest.capabilities !== 'object' || manifest.capabilities === null) {\n errors.push('capabilities: Must be an object')\n } else {\n const caps = manifest.capabilities as Record<string, unknown>\n\n if (caps.hotReload !== undefined && typeof caps.hotReload !== 'boolean') {\n errors.push('capabilities.hotReload: Must be a boolean')\n }\n\n if (caps.systemEvents !== undefined) {\n const validEvents: SystemEventType[] = [\n 'viewport:resize', 'theme:change', 'visibility:change',\n 'layer:focus', 'layer:blur', 'app:idle', 'app:active'\n ]\n\n if (!Array.isArray(caps.systemEvents)) {\n errors.push('capabilities.systemEvents: Must be an array')\n } else {\n for (const event of caps.systemEvents) {\n if (!validEvents.includes(event)) {\n errors.push(`capabilities.systemEvents: Invalid event \"${event}\"`)\n }\n }\n }\n }\n }\n }\n\n // Permissions validation\n if (manifest.permissions !== undefined) {\n if (typeof manifest.permissions !== 'object' || manifest.permissions === null) {\n errors.push('permissions: Must be an object')\n } else {\n const perms = manifest.permissions as Record<string, unknown>\n\n if (perms.storage !== undefined) {\n const storage = perms.storage as Record<string, unknown>\n if (typeof storage?.quota !== 'number' || storage.quota <= 0) {\n errors.push('permissions.storage.quota: Must be a positive number')\n }\n }\n\n if (perms.cpu !== undefined) {\n const cpu = perms.cpu as Record<string, unknown>\n if (!['low', 'medium', 'high'].includes(cpu?.limit as string)) {\n errors.push('permissions.cpu.limit: Must be low, medium, or high')\n }\n }\n\n if (perms.network !== undefined) {\n const network = perms.network as Record<string, unknown>\n if (!Array.isArray(network?.domains)) {\n errors.push('permissions.network.domains: Must be an array of domain strings')\n }\n }\n }\n }\n\n // Default layout validation\n if (manifest.defaultLayout !== undefined) {\n if (typeof manifest.defaultLayout !== 'object' || manifest.defaultLayout === null) {\n errors.push('defaultLayout: Must be an object')\n } else {\n const layout = manifest.defaultLayout as Record<string, unknown>\n const numFields = ['xPercent', 'yPercent', 'widthPercent', 'heightPercent', 'rotation']\n for (const field of numFields) {\n if (layout[field] !== undefined && typeof layout[field] !== 'number') {\n errors.push(`defaultLayout.${field}: Must be a number`)\n }\n }\n }\n }\n\n if (errors.length > 0) {\n return { success: false, errors }\n }\n\n return { success: true, manifest: manifest as unknown as AddonManifest }\n}\n\n/**\n * Type guard to check if an object is a valid AddonManifest.\n *\n * @param input - Object to check\n * @returns True if input is a valid AddonManifest\n */\nexport function isValidManifest(input: unknown): input is AddonManifest {\n return validateManifest(input).success\n}\n\n// =============================================================================\n// UTILITY FUNCTIONS\n// =============================================================================\n\n/**\n * Check if manifest declares hot-reload capability.\n */\nexport function supportsHotReload(manifest: AddonManifest): boolean {\n return manifest.capabilities?.hotReload === true\n}\n\n/**\n * Get system events the addon subscribes to.\n */\nexport function getSubscribedEvents(manifest: AddonManifest): SystemEventType[] {\n return manifest.capabilities?.systemEvents ?? []\n}\n\n/**\n * Get required permissions from manifest.\n */\nexport function getRequiredPermissions(manifest: AddonManifest): PermissionType[] {\n const permissions: PermissionType[] = []\n\n if (manifest.permissions?.storage) permissions.push('storage')\n if (manifest.permissions?.cpu) permissions.push('cpu-high')\n if (manifest.permissions?.network) permissions.push('network')\n if (manifest.permissions?.audio) permissions.push('audio')\n if (manifest.permissions?.notifications) permissions.push('notifications')\n\n return permissions\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/manifest.ts"],"names":[],"mappings":"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","file":"manifest.js","sourcesContent":["/**\n * @mywallpaper/addon-sdk - Manifest Schema & Validation\n *\n * Defines the addon manifest format (manifest.json) and provides\n * Zod-based validation for type safety at runtime.\n *\n * @example\n * ```typescript\n * import { validateManifest, type AddonManifest } from '@mywallpaper/addon-sdk/manifest'\n *\n * const manifest: AddonManifest = {\n * name: 'My Addon',\n * version: '1.0.0',\n * sdkVersion: '2.7.0',\n * description: 'A cool addon',\n * settings: {\n * primaryColor: { type: 'color', default: '#ffffff', label: 'Primary Color' }\n * }\n * }\n *\n * // Validate at runtime\n * const result = validateManifest(manifest)\n * if (!result.success) {\n * console.error('Invalid manifest:', result.errors)\n * }\n * ```\n */\n\nimport type { SystemEventType, PermissionType, OAuthProvider } from './types'\n\n// =============================================================================\n// SETTING TYPES\n// =============================================================================\n\n/**\n * Available setting types for addon configuration.\n */\nexport type SettingType =\n | 'string' // Text input\n | 'number' // Numeric input\n | 'boolean' // Checkbox/toggle\n | 'color' // Color picker\n | 'select' // Dropdown selection\n | 'range' // Slider\n | 'image' // Image upload\n | 'video' // Video upload\n | 'audio' // Audio upload (mp3, wav, ogg, etc.)\n | 'file' // Generic file upload (images + videos + audio)\n | 'section' // Visual grouping (no value)\n | 'textarea' // Multi-line text\n | 'radio' // Radio buttons\n | 'gradient' // Gradient picker\n | 'vector2' // 2D vector (x, y)\n | 'button' // Action button (no value)\n | 'multi-select' // Multiple selection\n\n/**\n * Definition for a single addon setting.\n *\n * @example\n * ```typescript\n * const colorSetting: SettingDefinition = {\n * type: 'color',\n * label: 'Background Color',\n * description: 'Choose a background color',\n * default: '#000000'\n * }\n *\n * const speedSetting: SettingDefinition = {\n * type: 'range',\n * label: 'Animation Speed',\n * min: 0.1,\n * max: 10,\n * step: 0.1,\n * default: 1\n * }\n * ```\n */\nexport interface SettingDefinition {\n /** Setting type determines the UI control */\n type: SettingType\n\n /** Human-readable label */\n label?: string\n\n /** Help text shown below the control */\n description?: string\n\n /** Default value (type depends on setting type) */\n default?: unknown\n\n // Select/Radio/Multi-select options\n /** Options for select, radio, multi-select types */\n options?: Array<{ value: string; label: string }>\n\n // Range-specific\n /** Minimum value for range type */\n min?: number\n /** Maximum value for range type */\n max?: number\n /** Step increment for range type */\n step?: number\n\n // Button-specific\n /** Label text on button */\n buttonLabel?: string\n\n // Vector-specific\n /** Axis labels for vector types, e.g., ['X', 'Y'] */\n axisLabels?: [string, string] | [string, string, string]\n\n // Multi-select specific\n /** Maximum number of selections */\n maxItems?: number\n\n // Conditional visibility\n /** Only show if another setting has a specific value */\n showIf?: {\n setting: string\n equals: unknown\n }\n}\n\n// =============================================================================\n// MANIFEST SCHEMA\n// =============================================================================\n\n/**\n * Default layout configuration for addon positioning.\n */\nexport interface AddonDefaultLayout {\n /** X position as percentage of viewport width (0-100) */\n xPercent?: number\n /** Y position as percentage of viewport height (0-100) */\n yPercent?: number\n /** Width as percentage of viewport width (0-100) */\n widthPercent?: number\n /** Height as percentage of viewport height (0-100) */\n heightPercent?: number\n /** Rotation in degrees */\n rotation?: number\n}\n\n/**\n * The complete addon manifest schema.\n * This defines your addon's identity, settings, and capabilities.\n *\n * @example\n * ```json\n * {\n * \"name\": \"Weather Widget\",\n * \"version\": \"1.2.0\",\n * \"description\": \"Displays current weather conditions\",\n * \"author\": \"Jane Developer\",\n * \"categories\": [\"utilities\", \"weather\"],\n * \"capabilities\": {\n * \"hotReload\": true,\n * \"systemEvents\": [\"viewport:resize\", \"theme:change\"]\n * },\n * \"permissions\": {\n * \"storage\": { \"quota\": 512 },\n * \"network\": { \"domains\": [\"api.openweathermap.org\"] }\n * },\n * \"settings\": {\n * \"apiKey\": {\n * \"type\": \"string\",\n * \"label\": \"API Key\",\n * \"description\": \"Get your key at openweathermap.org\"\n * },\n * \"unit\": {\n * \"type\": \"select\",\n * \"label\": \"Temperature Unit\",\n * \"default\": \"celsius\",\n * \"options\": [\n * { \"value\": \"celsius\", \"label\": \"Celsius\" },\n * { \"value\": \"fahrenheit\", \"label\": \"Fahrenheit\" }\n * ]\n * }\n * }\n * }\n * ```\n */\nexport interface AddonManifest {\n // ---------------------------------------------------------------------------\n // REQUIRED FIELDS\n // ---------------------------------------------------------------------------\n\n /** Addon name (displayed in UI) */\n name: string\n\n /** Semantic version (e.g., \"1.0.0\") */\n version: string\n\n /**\n * SDK version this addon was built for.\n * Must be a valid semver (e.g., \"2.3.0\").\n * Host uses this to inject the correct client-side SDK.\n */\n sdkVersion: string\n\n // ---------------------------------------------------------------------------\n // OPTIONAL METADATA\n // ---------------------------------------------------------------------------\n\n /** Short description of the addon */\n description?: string\n\n /** Author name or organization */\n author?: string\n\n /** Addon type/category for filtering */\n type?: string\n\n /** Categories for discoverability */\n categories?: string[]\n\n /** License (e.g., \"MIT\", \"Apache-2.0\") */\n license?: string\n\n /** Homepage or documentation URL */\n homepage?: string\n\n /** Repository URL */\n repository?: string\n\n // ---------------------------------------------------------------------------\n // SETTINGS\n // ---------------------------------------------------------------------------\n\n /**\n * User-configurable settings.\n * Key is the setting ID, value is the definition.\n */\n settings?: Record<string, SettingDefinition>\n\n // ---------------------------------------------------------------------------\n // CAPABILITIES\n // ---------------------------------------------------------------------------\n\n /**\n * Addon capabilities and event subscriptions.\n */\n capabilities?: {\n /** Supports settings changes without full reload */\n hotReload?: boolean\n /** System events the addon wants to receive */\n systemEvents?: SystemEventType[]\n }\n\n // ---------------------------------------------------------------------------\n // PERMISSIONS\n // ---------------------------------------------------------------------------\n\n /**\n * Permissions the addon will request.\n * Declaring them here improves UX by showing upfront.\n */\n permissions?: {\n /** Storage permission with quota in KB */\n storage?: { quota: number }\n /** CPU usage limit */\n cpu?: { limit: 'low' | 'medium' | 'high' }\n /** Network access with allowed domains */\n network?: { domains: string[] }\n /** Audio playback permission */\n audio?: boolean\n /** Notification permission */\n notifications?: boolean\n /**\n * File access permission for uploaded media files.\n * When enabled, the addon can receive uploaded files (video, audio, images)\n * via secure blob transfer for playback.\n *\n * This is required for addons that play user-uploaded media content.\n */\n fileAccess?: {\n /** User-friendly reason why this permission is needed */\n reason: string\n }\n /**\n * Same-origin permission for blob URL support.\n * Required for addons that handle large local files (videos, audio).\n *\n * ⚠️ SECURITY WARNING: This allows the addon to access:\n * - localStorage/sessionStorage of the parent origin\n * - Cookies of the parent domain\n * - IndexedDB of the parent origin\n *\n * Only grant to trusted addons!\n */\n sameOrigin?: {\n /** User-friendly reason why this permission is needed */\n reason: string\n }\n }\n\n // ---------------------------------------------------------------------------\n // LAYOUT\n // ---------------------------------------------------------------------------\n\n /**\n * Default layout for automatic positioning.\n * Used when addon is first added to a wallpaper.\n */\n defaultLayout?: AddonDefaultLayout\n\n // ---------------------------------------------------------------------------\n // FUTURE FIELDS\n // ---------------------------------------------------------------------------\n\n /**\n * OAuth providers and scopes for authenticated addons.\n *\n * Declare the OAuth providers your addon needs and the scopes it requires.\n * Scopes are requested incrementally - users can connect with basic scopes\n * first, and your addon can request additional scopes when needed.\n *\n * @example\n * ```json\n * {\n * \"permissions\": {\n * \"oauth\": {\n * \"required\": [\n * { \"provider\": \"github\", \"scopes\": [\"read:user\"] }\n * ],\n * \"optional\": [\n * { \"provider\": \"github\", \"scopes\": [\"repo\"] }\n * ]\n * }\n * }\n * }\n * ```\n */\n oauth?: {\n /** OAuth providers required for the addon to function */\n required?: Array<{\n provider: OAuthProvider\n scopes: string[]\n /** Reason shown to user when requesting this permission */\n reason?: string\n }>\n /** OAuth providers that enhance functionality but aren't required */\n optional?: Array<{\n provider: OAuthProvider\n scopes: string[]\n /** Reason shown to user when requesting this permission */\n reason?: string\n }>\n }\n}\n\n// =============================================================================\n// VALIDATION\n// =============================================================================\n\n/**\n * Validation result type.\n */\nexport interface ValidationResult {\n success: boolean\n errors?: string[]\n manifest?: AddonManifest\n}\n\n/**\n * Validates an addon manifest object.\n *\n * @param input - The manifest object to validate\n * @returns ValidationResult with success status and any errors\n *\n * @example\n * ```typescript\n * const result = validateManifest(myManifest)\n * if (!result.success) {\n * result.errors?.forEach(err => console.error(err))\n * }\n * ```\n */\nexport function validateManifest(input: unknown): ValidationResult {\n const errors: string[] = []\n\n if (!input || typeof input !== 'object') {\n return { success: false, errors: ['Manifest must be an object'] }\n }\n\n const manifest = input as Record<string, unknown>\n\n // Required fields\n if (typeof manifest.name !== 'string' || manifest.name.trim() === '') {\n errors.push('name: Required and must be a non-empty string')\n }\n\n if (typeof manifest.version !== 'string' || !/^\\d+\\.\\d+\\.\\d+/.test(manifest.version)) {\n errors.push('version: Required and must be a valid semver (e.g., \"1.0.0\")')\n }\n\n if (typeof manifest.sdkVersion !== 'string' || !/^\\d+\\.\\d+\\.\\d+/.test(manifest.sdkVersion)) {\n errors.push('sdkVersion: Required and must be a valid semver (e.g., \"2.2.0\")')\n }\n\n // Optional string fields\n const optionalStrings = ['description', 'author', 'type', 'license', 'homepage', 'repository']\n for (const field of optionalStrings) {\n if (manifest[field] !== undefined && typeof manifest[field] !== 'string') {\n errors.push(`${field}: Must be a string`)\n }\n }\n\n // Categories\n if (manifest.categories !== undefined) {\n if (!Array.isArray(manifest.categories)) {\n errors.push('categories: Must be an array of strings')\n } else if (!manifest.categories.every(c => typeof c === 'string')) {\n errors.push('categories: All items must be strings')\n }\n }\n\n // Settings validation\n if (manifest.settings !== undefined) {\n if (typeof manifest.settings !== 'object' || manifest.settings === null) {\n errors.push('settings: Must be an object')\n } else {\n const settings = manifest.settings as Record<string, unknown>\n for (const [key, value] of Object.entries(settings)) {\n if (typeof value !== 'object' || value === null) {\n errors.push(`settings.${key}: Must be an object`)\n continue\n }\n\n const setting = value as Record<string, unknown>\n const validTypes: SettingType[] = [\n 'string', 'number', 'boolean', 'color', 'select', 'range',\n 'image', 'video', 'audio', 'file', 'section', 'textarea', 'radio', 'gradient', 'vector2',\n 'button', 'multi-select'\n ]\n\n if (!validTypes.includes(setting.type as SettingType)) {\n errors.push(`settings.${key}.type: Must be one of: ${validTypes.join(', ')}`)\n }\n\n // Type-specific validation\n if (['select', 'radio', 'multi-select'].includes(setting.type as string)) {\n if (!Array.isArray(setting.options) || setting.options.length === 0) {\n errors.push(`settings.${key}.options: Required for ${setting.type} type`)\n }\n }\n\n if (setting.type === 'range') {\n if (typeof setting.min !== 'number' || typeof setting.max !== 'number') {\n errors.push(`settings.${key}: range type requires min and max`)\n }\n }\n }\n }\n }\n\n // Capabilities validation\n if (manifest.capabilities !== undefined) {\n if (typeof manifest.capabilities !== 'object' || manifest.capabilities === null) {\n errors.push('capabilities: Must be an object')\n } else {\n const caps = manifest.capabilities as Record<string, unknown>\n\n if (caps.hotReload !== undefined && typeof caps.hotReload !== 'boolean') {\n errors.push('capabilities.hotReload: Must be a boolean')\n }\n\n if (caps.systemEvents !== undefined) {\n const validEvents: SystemEventType[] = [\n 'viewport:resize', 'theme:change', 'visibility:change',\n 'layer:focus', 'layer:blur', 'app:idle', 'app:active'\n ]\n\n if (!Array.isArray(caps.systemEvents)) {\n errors.push('capabilities.systemEvents: Must be an array')\n } else {\n for (const event of caps.systemEvents) {\n if (!validEvents.includes(event)) {\n errors.push(`capabilities.systemEvents: Invalid event \"${event}\"`)\n }\n }\n }\n }\n }\n }\n\n // Permissions validation\n if (manifest.permissions !== undefined) {\n if (typeof manifest.permissions !== 'object' || manifest.permissions === null) {\n errors.push('permissions: Must be an object')\n } else {\n const perms = manifest.permissions as Record<string, unknown>\n\n if (perms.storage !== undefined) {\n const storage = perms.storage as Record<string, unknown>\n if (typeof storage?.quota !== 'number' || storage.quota <= 0) {\n errors.push('permissions.storage.quota: Must be a positive number')\n }\n }\n\n if (perms.cpu !== undefined) {\n const cpu = perms.cpu as Record<string, unknown>\n if (!['low', 'medium', 'high'].includes(cpu?.limit as string)) {\n errors.push('permissions.cpu.limit: Must be low, medium, or high')\n }\n }\n\n if (perms.network !== undefined) {\n const network = perms.network as Record<string, unknown>\n if (!Array.isArray(network?.domains)) {\n errors.push('permissions.network.domains: Must be an array of domain strings')\n }\n }\n }\n }\n\n // Default layout validation\n if (manifest.defaultLayout !== undefined) {\n if (typeof manifest.defaultLayout !== 'object' || manifest.defaultLayout === null) {\n errors.push('defaultLayout: Must be an object')\n } else {\n const layout = manifest.defaultLayout as Record<string, unknown>\n const numFields = ['xPercent', 'yPercent', 'widthPercent', 'heightPercent', 'rotation']\n for (const field of numFields) {\n if (layout[field] !== undefined && typeof layout[field] !== 'number') {\n errors.push(`defaultLayout.${field}: Must be a number`)\n }\n }\n }\n }\n\n if (errors.length > 0) {\n return { success: false, errors }\n }\n\n return { success: true, manifest: manifest as unknown as AddonManifest }\n}\n\n/**\n * Type guard to check if an object is a valid AddonManifest.\n *\n * @param input - Object to check\n * @returns True if input is a valid AddonManifest\n */\nexport function isValidManifest(input: unknown): input is AddonManifest {\n return validateManifest(input).success\n}\n\n// =============================================================================\n// UTILITY FUNCTIONS\n// =============================================================================\n\n/**\n * Check if manifest declares hot-reload capability.\n */\nexport function supportsHotReload(manifest: AddonManifest): boolean {\n return manifest.capabilities?.hotReload === true\n}\n\n/**\n * Get system events the addon subscribes to.\n */\nexport function getSubscribedEvents(manifest: AddonManifest): SystemEventType[] {\n return manifest.capabilities?.systemEvents ?? []\n}\n\n/**\n * Get required permissions from manifest.\n */\nexport function getRequiredPermissions(manifest: AddonManifest): PermissionType[] {\n const permissions: PermissionType[] = []\n\n if (manifest.permissions?.storage) permissions.push('storage')\n if (manifest.permissions?.cpu) permissions.push('cpu-high')\n if (manifest.permissions?.network) permissions.push('network')\n if (manifest.permissions?.audio) permissions.push('audio')\n if (manifest.permissions?.notifications) permissions.push('notifications')\n\n return permissions\n}\n"]}
|
package/dist/manifest.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/manifest.ts"],"names":[],"mappings":";AA0XO,SAAS,iBAAiB,KAAA,EAAkC;AACjE,EAAA,MAAM,SAAmB,EAAC;AAE1B,EAAA,IAAI,CAAC,KAAA,IAAS,OAAO,KAAA,KAAU,QAAA,EAAU;AACvC,IAAA,OAAO,EAAE,OAAA,EAAS,KAAA,EAAO,MAAA,EAAQ,CAAC,4BAA4B,CAAA,EAAE;AAAA,EAClE;AAEA,EAAA,MAAM,QAAA,GAAW,KAAA;AAGjB,EAAA,IAAI,OAAO,SAAS,IAAA,KAAS,QAAA,IAAY,SAAS,IAAA,CAAK,IAAA,OAAW,EAAA,EAAI;AACpE,IAAA,MAAA,CAAO,KAAK,+CAA+C,CAAA;AAAA,EAC7D;AAEA,EAAA,IAAI,OAAO,SAAS,OAAA,KAAY,QAAA,IAAY,CAAC,gBAAA,CAAiB,IAAA,CAAK,QAAA,CAAS,OAAO,CAAA,EAAG;AACpF,IAAA,MAAA,CAAO,KAAK,8DAA8D,CAAA;AAAA,EAC5E;AAEA,EAAA,IAAI,OAAO,SAAS,UAAA,KAAe,QAAA,IAAY,CAAC,gBAAA,CAAiB,IAAA,CAAK,QAAA,CAAS,UAAU,CAAA,EAAG;AAC1F,IAAA,MAAA,CAAO,KAAK,iEAAiE,CAAA;AAAA,EAC/E;AAGA,EAAA,MAAM,kBAAkB,CAAC,aAAA,EAAe,UAAU,MAAA,EAAQ,SAAA,EAAW,YAAY,YAAY,CAAA;AAC7F,EAAA,KAAA,MAAW,SAAS,eAAA,EAAiB;AACnC,IAAA,IAAI,QAAA,CAAS,KAAK,CAAA,KAAM,MAAA,IAAa,OAAO,QAAA,CAAS,KAAK,MAAM,QAAA,EAAU;AACxE,MAAA,MAAA,CAAO,IAAA,CAAK,CAAA,EAAG,KAAK,CAAA,kBAAA,CAAoB,CAAA;AAAA,IAC1C;AAAA,EACF;AAGA,EAAA,IAAI,QAAA,CAAS,eAAe,MAAA,EAAW;AACrC,IAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,QAAA,CAAS,UAAU,CAAA,EAAG;AACvC,MAAA,MAAA,CAAO,KAAK,yCAAyC,CAAA;AAAA,IACvD,CAAA,MAAA,IAAW,CAAC,QAAA,CAAS,UAAA,CAAW,MAAM,CAAA,CAAA,KAAK,OAAO,CAAA,KAAM,QAAQ,CAAA,EAAG;AACjE,MAAA,MAAA,CAAO,KAAK,uCAAuC,CAAA;AAAA,IACrD;AAAA,EACF;AAGA,EAAA,IAAI,QAAA,CAAS,aAAa,MAAA,EAAW;AACnC,IAAA,IAAI,OAAO,QAAA,CAAS,QAAA,KAAa,QAAA,IAAY,QAAA,CAAS,aAAa,IAAA,EAAM;AACvE,MAAA,MAAA,CAAO,KAAK,6BAA6B,CAAA;AAAA,IAC3C,CAAA,MAAO;AACL,MAAA,MAAM,WAAW,QAAA,CAAS,QAAA;AAC1B,MAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,QAAQ,CAAA,EAAG;AACnD,QAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,IAAA,EAAM;AAC/C,UAAA,MAAA,CAAO,IAAA,CAAK,CAAA,SAAA,EAAY,GAAG,CAAA,mBAAA,CAAqB,CAAA;AAChD,UAAA;AAAA,QACF;AAEA,QAAA,MAAM,OAAA,GAAU,KAAA;AAChB,QAAA,MAAM,UAAA,GAA4B;AAAA,UAChC,QAAA;AAAA,UAAU,QAAA;AAAA,UAAU,SAAA;AAAA,UAAW,OAAA;AAAA,UAAS,QAAA;AAAA,UAAU,OAAA;AAAA,UAClD,OAAA;AAAA,UAAS,OAAA;AAAA,UAAS,OAAA;AAAA,UAAS,MAAA;AAAA,UAAQ,SAAA;AAAA,UAAW,UAAA;AAAA,UAAY,OAAA;AAAA,UAAS,UAAA;AAAA,UAAY,SAAA;AAAA,UAC/E,QAAA;AAAA,UAAU;AAAA,SACZ;AAEA,QAAA,IAAI,CAAC,UAAA,CAAW,QAAA,CAAS,OAAA,CAAQ,IAAmB,CAAA,EAAG;AACrD,UAAA,MAAA,CAAO,IAAA,CAAK,YAAY,GAAG,CAAA,uBAAA,EAA0B,WAAW,IAAA,CAAK,IAAI,CAAC,CAAA,CAAE,CAAA;AAAA,QAC9E;AAGA,QAAA,IAAI,CAAC,UAAU,OAAA,EAAS,cAAc,EAAE,QAAA,CAAS,OAAA,CAAQ,IAAc,CAAA,EAAG;AACxE,UAAA,IAAI,CAAC,MAAM,OAAA,CAAQ,OAAA,CAAQ,OAAO,CAAA,IAAK,OAAA,CAAQ,OAAA,CAAQ,MAAA,KAAW,CAAA,EAAG;AACnE,YAAA,MAAA,CAAO,KAAK,CAAA,SAAA,EAAY,GAAG,CAAA,uBAAA,EAA0B,OAAA,CAAQ,IAAI,CAAA,KAAA,CAAO,CAAA;AAAA,UAC1E;AAAA,QACF;AAEA,QAAA,IAAI,OAAA,CAAQ,SAAS,OAAA,EAAS;AAC5B,UAAA,IAAI,OAAO,OAAA,CAAQ,GAAA,KAAQ,YAAY,OAAO,OAAA,CAAQ,QAAQ,QAAA,EAAU;AACtE,YAAA,MAAA,CAAO,IAAA,CAAK,CAAA,SAAA,EAAY,GAAG,CAAA,iCAAA,CAAmC,CAAA;AAAA,UAChE;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,EAAA,IAAI,QAAA,CAAS,iBAAiB,MAAA,EAAW;AACvC,IAAA,IAAI,OAAO,QAAA,CAAS,YAAA,KAAiB,QAAA,IAAY,QAAA,CAAS,iBAAiB,IAAA,EAAM;AAC/E,MAAA,MAAA,CAAO,KAAK,iCAAiC,CAAA;AAAA,IAC/C,CAAA,MAAO;AACL,MAAA,MAAM,OAAO,QAAA,CAAS,YAAA;AAEtB,MAAA,IAAI,KAAK,SAAA,KAAc,MAAA,IAAa,OAAO,IAAA,CAAK,cAAc,SAAA,EAAW;AACvE,QAAA,MAAA,CAAO,KAAK,2CAA2C,CAAA;AAAA,MACzD;AAEA,MAAA,IAAI,IAAA,CAAK,iBAAiB,MAAA,EAAW;AACnC,QAAA,MAAM,WAAA,GAAiC;AAAA,UACrC,iBAAA;AAAA,UAAmB,cAAA;AAAA,UAAgB,mBAAA;AAAA,UACnC,aAAA;AAAA,UAAe,YAAA;AAAA,UAAc,UAAA;AAAA,UAAY;AAAA,SAC3C;AAEA,QAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,IAAA,CAAK,YAAY,CAAA,EAAG;AACrC,UAAA,MAAA,CAAO,KAAK,6CAA6C,CAAA;AAAA,QAC3D,CAAA,MAAO;AACL,UAAA,KAAA,MAAW,KAAA,IAAS,KAAK,YAAA,EAAc;AACrC,YAAA,IAAI,CAAC,WAAA,CAAY,QAAA,CAAS,KAAK,CAAA,EAAG;AAChC,cAAA,MAAA,CAAO,IAAA,CAAK,CAAA,0CAAA,EAA6C,KAAK,CAAA,CAAA,CAAG,CAAA;AAAA,YACnE;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,EAAA,IAAI,QAAA,CAAS,gBAAgB,MAAA,EAAW;AACtC,IAAA,IAAI,OAAO,QAAA,CAAS,WAAA,KAAgB,QAAA,IAAY,QAAA,CAAS,gBAAgB,IAAA,EAAM;AAC7E,MAAA,MAAA,CAAO,KAAK,gCAAgC,CAAA;AAAA,IAC9C,CAAA,MAAO;AACL,MAAA,MAAM,QAAQ,QAAA,CAAS,WAAA;AAEvB,MAAA,IAAI,KAAA,CAAM,YAAY,MAAA,EAAW;AAC/B,QAAA,MAAM,UAAU,KAAA,CAAM,OAAA;AACtB,QAAA,IAAI,OAAO,OAAA,EAAS,KAAA,KAAU,QAAA,IAAY,OAAA,CAAQ,SAAS,CAAA,EAAG;AAC5D,UAAA,MAAA,CAAO,KAAK,sDAAsD,CAAA;AAAA,QACpE;AAAA,MACF;AAEA,MAAA,IAAI,KAAA,CAAM,QAAQ,MAAA,EAAW;AAC3B,QAAA,MAAM,MAAM,KAAA,CAAM,GAAA;AAClB,QAAA,IAAI,CAAC,CAAC,KAAA,EAAO,QAAA,EAAU,MAAM,CAAA,CAAE,QAAA,CAAS,GAAA,EAAK,KAAe,CAAA,EAAG;AAC7D,UAAA,MAAA,CAAO,KAAK,qDAAqD,CAAA;AAAA,QACnE;AAAA,MACF;AAEA,MAAA,IAAI,KAAA,CAAM,YAAY,MAAA,EAAW;AAC/B,QAAA,MAAM,UAAU,KAAA,CAAM,OAAA;AACtB,QAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,OAAA,EAAS,OAAO,CAAA,EAAG;AACpC,UAAA,MAAA,CAAO,KAAK,iEAAiE,CAAA;AAAA,QAC/E;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,EAAA,IAAI,QAAA,CAAS,kBAAkB,MAAA,EAAW;AACxC,IAAA,IAAI,OAAO,QAAA,CAAS,aAAA,KAAkB,QAAA,IAAY,QAAA,CAAS,kBAAkB,IAAA,EAAM;AACjF,MAAA,MAAA,CAAO,KAAK,kCAAkC,CAAA;AAAA,IAChD,CAAA,MAAO;AACL,MAAA,MAAM,SAAS,QAAA,CAAS,aAAA;AACxB,MAAA,MAAM,YAAY,CAAC,UAAA,EAAY,UAAA,EAAY,cAAA,EAAgB,iBAAiB,UAAU,CAAA;AACtF,MAAA,KAAA,MAAW,SAAS,SAAA,EAAW;AAC7B,QAAA,IAAI,MAAA,CAAO,KAAK,CAAA,KAAM,MAAA,IAAa,OAAO,MAAA,CAAO,KAAK,MAAM,QAAA,EAAU;AACpE,UAAA,MAAA,CAAO,IAAA,CAAK,CAAA,cAAA,EAAiB,KAAK,CAAA,kBAAA,CAAoB,CAAA;AAAA,QACxD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,EAAA,IAAI,MAAA,CAAO,SAAS,CAAA,EAAG;AACrB,IAAA,OAAO,EAAE,OAAA,EAAS,KAAA,EAAO,MAAA,EAAO;AAAA,EAClC;AAEA,EAAA,OAAO,EAAE,OAAA,EAAS,IAAA,EAAM,QAAA,EAA+C;AACzE;AAQO,SAAS,gBAAgB,KAAA,EAAwC;AACtE,EAAA,OAAO,gBAAA,CAAiB,KAAK,CAAA,CAAE,OAAA;AACjC;AASO,SAAS,kBAAkB,QAAA,EAAkC;AAClE,EAAA,OAAO,QAAA,CAAS,cAAc,SAAA,KAAc,IAAA;AAC9C;AAKO,SAAS,oBAAoB,QAAA,EAA4C;AAC9E,EAAA,OAAO,QAAA,CAAS,YAAA,EAAc,YAAA,IAAgB,EAAC;AACjD;AAKO,SAAS,uBAAuB,QAAA,EAA2C;AAChF,EAAA,MAAM,cAAgC,EAAC;AAEvC,EAAA,IAAI,QAAA,CAAS,WAAA,EAAa,OAAA,EAAS,WAAA,CAAY,KAAK,SAAS,CAAA;AAC7D,EAAA,IAAI,QAAA,CAAS,WAAA,EAAa,GAAA,EAAK,WAAA,CAAY,KAAK,UAAU,CAAA;AAC1D,EAAA,IAAI,QAAA,CAAS,WAAA,EAAa,OAAA,EAAS,WAAA,CAAY,KAAK,SAAS,CAAA;AAC7D,EAAA,IAAI,QAAA,CAAS,WAAA,EAAa,KAAA,EAAO,WAAA,CAAY,KAAK,OAAO,CAAA;AACzD,EAAA,IAAI,QAAA,CAAS,WAAA,EAAa,aAAA,EAAe,WAAA,CAAY,KAAK,eAAe,CAAA;AAEzE,EAAA,OAAO,WAAA;AACT","file":"manifest.mjs","sourcesContent":["/**\n * @mywallpaper/addon-sdk - Manifest Schema & Validation\n *\n * Defines the addon manifest format (manifest.json) and provides\n * Zod-based validation for type safety at runtime.\n *\n * @example\n * ```typescript\n * import { validateManifest, type AddonManifest } from '@mywallpaper/addon-sdk/manifest'\n *\n * const manifest: AddonManifest = {\n * name: 'My Addon',\n * version: '1.0.0',\n * sdkVersion: '2.3.0',\n * description: 'A cool addon',\n * settings: {\n * primaryColor: { type: 'color', default: '#ffffff', label: 'Primary Color' }\n * }\n * }\n *\n * // Validate at runtime\n * const result = validateManifest(manifest)\n * if (!result.success) {\n * console.error('Invalid manifest:', result.errors)\n * }\n * ```\n */\n\nimport type { SystemEventType, PermissionType, OAuthProvider } from './types'\n\n// =============================================================================\n// SETTING TYPES\n// =============================================================================\n\n/**\n * Available setting types for addon configuration.\n */\nexport type SettingType =\n | 'string' // Text input\n | 'number' // Numeric input\n | 'boolean' // Checkbox/toggle\n | 'color' // Color picker\n | 'select' // Dropdown selection\n | 'range' // Slider\n | 'image' // Image upload\n | 'video' // Video upload\n | 'audio' // Audio upload (mp3, wav, ogg, etc.)\n | 'file' // Generic file upload (images + videos + audio)\n | 'section' // Visual grouping (no value)\n | 'textarea' // Multi-line text\n | 'radio' // Radio buttons\n | 'gradient' // Gradient picker\n | 'vector2' // 2D vector (x, y)\n | 'button' // Action button (no value)\n | 'multi-select' // Multiple selection\n\n/**\n * Definition for a single addon setting.\n *\n * @example\n * ```typescript\n * const colorSetting: SettingDefinition = {\n * type: 'color',\n * label: 'Background Color',\n * description: 'Choose a background color',\n * default: '#000000'\n * }\n *\n * const speedSetting: SettingDefinition = {\n * type: 'range',\n * label: 'Animation Speed',\n * min: 0.1,\n * max: 10,\n * step: 0.1,\n * default: 1\n * }\n * ```\n */\nexport interface SettingDefinition {\n /** Setting type determines the UI control */\n type: SettingType\n\n /** Human-readable label */\n label?: string\n\n /** Help text shown below the control */\n description?: string\n\n /** Default value (type depends on setting type) */\n default?: unknown\n\n // Select/Radio/Multi-select options\n /** Options for select, radio, multi-select types */\n options?: Array<{ value: string; label: string }>\n\n // Range-specific\n /** Minimum value for range type */\n min?: number\n /** Maximum value for range type */\n max?: number\n /** Step increment for range type */\n step?: number\n\n // Button-specific\n /** Label text on button */\n buttonLabel?: string\n\n // Vector-specific\n /** Axis labels for vector types, e.g., ['X', 'Y'] */\n axisLabels?: [string, string] | [string, string, string]\n\n // Multi-select specific\n /** Maximum number of selections */\n maxItems?: number\n\n // Conditional visibility\n /** Only show if another setting has a specific value */\n showIf?: {\n setting: string\n equals: unknown\n }\n}\n\n// =============================================================================\n// MANIFEST SCHEMA\n// =============================================================================\n\n/**\n * Default layout configuration for addon positioning.\n */\nexport interface AddonDefaultLayout {\n /** X position as percentage of viewport width (0-100) */\n xPercent?: number\n /** Y position as percentage of viewport height (0-100) */\n yPercent?: number\n /** Width as percentage of viewport width (0-100) */\n widthPercent?: number\n /** Height as percentage of viewport height (0-100) */\n heightPercent?: number\n /** Rotation in degrees */\n rotation?: number\n}\n\n/**\n * The complete addon manifest schema.\n * This defines your addon's identity, settings, and capabilities.\n *\n * @example\n * ```json\n * {\n * \"name\": \"Weather Widget\",\n * \"version\": \"1.2.0\",\n * \"description\": \"Displays current weather conditions\",\n * \"author\": \"Jane Developer\",\n * \"categories\": [\"utilities\", \"weather\"],\n * \"capabilities\": {\n * \"hotReload\": true,\n * \"systemEvents\": [\"viewport:resize\", \"theme:change\"]\n * },\n * \"permissions\": {\n * \"storage\": { \"quota\": 512 },\n * \"network\": { \"domains\": [\"api.openweathermap.org\"] }\n * },\n * \"settings\": {\n * \"apiKey\": {\n * \"type\": \"string\",\n * \"label\": \"API Key\",\n * \"description\": \"Get your key at openweathermap.org\"\n * },\n * \"unit\": {\n * \"type\": \"select\",\n * \"label\": \"Temperature Unit\",\n * \"default\": \"celsius\",\n * \"options\": [\n * { \"value\": \"celsius\", \"label\": \"Celsius\" },\n * { \"value\": \"fahrenheit\", \"label\": \"Fahrenheit\" }\n * ]\n * }\n * }\n * }\n * ```\n */\nexport interface AddonManifest {\n // ---------------------------------------------------------------------------\n // REQUIRED FIELDS\n // ---------------------------------------------------------------------------\n\n /** Addon name (displayed in UI) */\n name: string\n\n /** Semantic version (e.g., \"1.0.0\") */\n version: string\n\n /**\n * SDK version this addon was built for.\n * Must be a valid semver (e.g., \"2.3.0\").\n * Host uses this to inject the correct client-side SDK.\n */\n sdkVersion: string\n\n // ---------------------------------------------------------------------------\n // OPTIONAL METADATA\n // ---------------------------------------------------------------------------\n\n /** Short description of the addon */\n description?: string\n\n /** Author name or organization */\n author?: string\n\n /** Addon type/category for filtering */\n type?: string\n\n /** Categories for discoverability */\n categories?: string[]\n\n /** License (e.g., \"MIT\", \"Apache-2.0\") */\n license?: string\n\n /** Homepage or documentation URL */\n homepage?: string\n\n /** Repository URL */\n repository?: string\n\n // ---------------------------------------------------------------------------\n // SETTINGS\n // ---------------------------------------------------------------------------\n\n /**\n * User-configurable settings.\n * Key is the setting ID, value is the definition.\n */\n settings?: Record<string, SettingDefinition>\n\n // ---------------------------------------------------------------------------\n // CAPABILITIES\n // ---------------------------------------------------------------------------\n\n /**\n * Addon capabilities and event subscriptions.\n */\n capabilities?: {\n /** Supports settings changes without full reload */\n hotReload?: boolean\n /** System events the addon wants to receive */\n systemEvents?: SystemEventType[]\n }\n\n // ---------------------------------------------------------------------------\n // PERMISSIONS\n // ---------------------------------------------------------------------------\n\n /**\n * Permissions the addon will request.\n * Declaring them here improves UX by showing upfront.\n */\n permissions?: {\n /** Storage permission with quota in KB */\n storage?: { quota: number }\n /** CPU usage limit */\n cpu?: { limit: 'low' | 'medium' | 'high' }\n /** Network access with allowed domains */\n network?: { domains: string[] }\n /** Audio playback permission */\n audio?: boolean\n /** Notification permission */\n notifications?: boolean\n /**\n * File access permission for uploaded media files.\n * When enabled, the addon can receive uploaded files (video, audio, images)\n * via secure blob transfer for playback.\n *\n * This is required for addons that play user-uploaded media content.\n */\n fileAccess?: {\n /** User-friendly reason why this permission is needed */\n reason: string\n }\n /**\n * Same-origin permission for blob URL support.\n * Required for addons that handle large local files (videos, audio).\n *\n * ⚠️ SECURITY WARNING: This allows the addon to access:\n * - localStorage/sessionStorage of the parent origin\n * - Cookies of the parent domain\n * - IndexedDB of the parent origin\n *\n * Only grant to trusted addons!\n */\n sameOrigin?: {\n /** User-friendly reason why this permission is needed */\n reason: string\n }\n }\n\n // ---------------------------------------------------------------------------\n // LAYOUT\n // ---------------------------------------------------------------------------\n\n /**\n * Default layout for automatic positioning.\n * Used when addon is first added to a wallpaper.\n */\n defaultLayout?: AddonDefaultLayout\n\n // ---------------------------------------------------------------------------\n // FUTURE FIELDS\n // ---------------------------------------------------------------------------\n\n /**\n * OAuth providers and scopes for authenticated addons.\n *\n * Declare the OAuth providers your addon needs and the scopes it requires.\n * Scopes are requested incrementally - users can connect with basic scopes\n * first, and your addon can request additional scopes when needed.\n *\n * @example\n * ```json\n * {\n * \"permissions\": {\n * \"oauth\": {\n * \"required\": [\n * { \"provider\": \"github\", \"scopes\": [\"read:user\"] }\n * ],\n * \"optional\": [\n * { \"provider\": \"github\", \"scopes\": [\"repo\"] }\n * ]\n * }\n * }\n * }\n * ```\n */\n oauth?: {\n /** OAuth providers required for the addon to function */\n required?: Array<{\n provider: OAuthProvider\n scopes: string[]\n /** Reason shown to user when requesting this permission */\n reason?: string\n }>\n /** OAuth providers that enhance functionality but aren't required */\n optional?: Array<{\n provider: OAuthProvider\n scopes: string[]\n /** Reason shown to user when requesting this permission */\n reason?: string\n }>\n }\n}\n\n// =============================================================================\n// VALIDATION\n// =============================================================================\n\n/**\n * Validation result type.\n */\nexport interface ValidationResult {\n success: boolean\n errors?: string[]\n manifest?: AddonManifest\n}\n\n/**\n * Validates an addon manifest object.\n *\n * @param input - The manifest object to validate\n * @returns ValidationResult with success status and any errors\n *\n * @example\n * ```typescript\n * const result = validateManifest(myManifest)\n * if (!result.success) {\n * result.errors?.forEach(err => console.error(err))\n * }\n * ```\n */\nexport function validateManifest(input: unknown): ValidationResult {\n const errors: string[] = []\n\n if (!input || typeof input !== 'object') {\n return { success: false, errors: ['Manifest must be an object'] }\n }\n\n const manifest = input as Record<string, unknown>\n\n // Required fields\n if (typeof manifest.name !== 'string' || manifest.name.trim() === '') {\n errors.push('name: Required and must be a non-empty string')\n }\n\n if (typeof manifest.version !== 'string' || !/^\\d+\\.\\d+\\.\\d+/.test(manifest.version)) {\n errors.push('version: Required and must be a valid semver (e.g., \"1.0.0\")')\n }\n\n if (typeof manifest.sdkVersion !== 'string' || !/^\\d+\\.\\d+\\.\\d+/.test(manifest.sdkVersion)) {\n errors.push('sdkVersion: Required and must be a valid semver (e.g., \"2.2.0\")')\n }\n\n // Optional string fields\n const optionalStrings = ['description', 'author', 'type', 'license', 'homepage', 'repository']\n for (const field of optionalStrings) {\n if (manifest[field] !== undefined && typeof manifest[field] !== 'string') {\n errors.push(`${field}: Must be a string`)\n }\n }\n\n // Categories\n if (manifest.categories !== undefined) {\n if (!Array.isArray(manifest.categories)) {\n errors.push('categories: Must be an array of strings')\n } else if (!manifest.categories.every(c => typeof c === 'string')) {\n errors.push('categories: All items must be strings')\n }\n }\n\n // Settings validation\n if (manifest.settings !== undefined) {\n if (typeof manifest.settings !== 'object' || manifest.settings === null) {\n errors.push('settings: Must be an object')\n } else {\n const settings = manifest.settings as Record<string, unknown>\n for (const [key, value] of Object.entries(settings)) {\n if (typeof value !== 'object' || value === null) {\n errors.push(`settings.${key}: Must be an object`)\n continue\n }\n\n const setting = value as Record<string, unknown>\n const validTypes: SettingType[] = [\n 'string', 'number', 'boolean', 'color', 'select', 'range',\n 'image', 'video', 'audio', 'file', 'section', 'textarea', 'radio', 'gradient', 'vector2',\n 'button', 'multi-select'\n ]\n\n if (!validTypes.includes(setting.type as SettingType)) {\n errors.push(`settings.${key}.type: Must be one of: ${validTypes.join(', ')}`)\n }\n\n // Type-specific validation\n if (['select', 'radio', 'multi-select'].includes(setting.type as string)) {\n if (!Array.isArray(setting.options) || setting.options.length === 0) {\n errors.push(`settings.${key}.options: Required for ${setting.type} type`)\n }\n }\n\n if (setting.type === 'range') {\n if (typeof setting.min !== 'number' || typeof setting.max !== 'number') {\n errors.push(`settings.${key}: range type requires min and max`)\n }\n }\n }\n }\n }\n\n // Capabilities validation\n if (manifest.capabilities !== undefined) {\n if (typeof manifest.capabilities !== 'object' || manifest.capabilities === null) {\n errors.push('capabilities: Must be an object')\n } else {\n const caps = manifest.capabilities as Record<string, unknown>\n\n if (caps.hotReload !== undefined && typeof caps.hotReload !== 'boolean') {\n errors.push('capabilities.hotReload: Must be a boolean')\n }\n\n if (caps.systemEvents !== undefined) {\n const validEvents: SystemEventType[] = [\n 'viewport:resize', 'theme:change', 'visibility:change',\n 'layer:focus', 'layer:blur', 'app:idle', 'app:active'\n ]\n\n if (!Array.isArray(caps.systemEvents)) {\n errors.push('capabilities.systemEvents: Must be an array')\n } else {\n for (const event of caps.systemEvents) {\n if (!validEvents.includes(event)) {\n errors.push(`capabilities.systemEvents: Invalid event \"${event}\"`)\n }\n }\n }\n }\n }\n }\n\n // Permissions validation\n if (manifest.permissions !== undefined) {\n if (typeof manifest.permissions !== 'object' || manifest.permissions === null) {\n errors.push('permissions: Must be an object')\n } else {\n const perms = manifest.permissions as Record<string, unknown>\n\n if (perms.storage !== undefined) {\n const storage = perms.storage as Record<string, unknown>\n if (typeof storage?.quota !== 'number' || storage.quota <= 0) {\n errors.push('permissions.storage.quota: Must be a positive number')\n }\n }\n\n if (perms.cpu !== undefined) {\n const cpu = perms.cpu as Record<string, unknown>\n if (!['low', 'medium', 'high'].includes(cpu?.limit as string)) {\n errors.push('permissions.cpu.limit: Must be low, medium, or high')\n }\n }\n\n if (perms.network !== undefined) {\n const network = perms.network as Record<string, unknown>\n if (!Array.isArray(network?.domains)) {\n errors.push('permissions.network.domains: Must be an array of domain strings')\n }\n }\n }\n }\n\n // Default layout validation\n if (manifest.defaultLayout !== undefined) {\n if (typeof manifest.defaultLayout !== 'object' || manifest.defaultLayout === null) {\n errors.push('defaultLayout: Must be an object')\n } else {\n const layout = manifest.defaultLayout as Record<string, unknown>\n const numFields = ['xPercent', 'yPercent', 'widthPercent', 'heightPercent', 'rotation']\n for (const field of numFields) {\n if (layout[field] !== undefined && typeof layout[field] !== 'number') {\n errors.push(`defaultLayout.${field}: Must be a number`)\n }\n }\n }\n }\n\n if (errors.length > 0) {\n return { success: false, errors }\n }\n\n return { success: true, manifest: manifest as unknown as AddonManifest }\n}\n\n/**\n * Type guard to check if an object is a valid AddonManifest.\n *\n * @param input - Object to check\n * @returns True if input is a valid AddonManifest\n */\nexport function isValidManifest(input: unknown): input is AddonManifest {\n return validateManifest(input).success\n}\n\n// =============================================================================\n// UTILITY FUNCTIONS\n// =============================================================================\n\n/**\n * Check if manifest declares hot-reload capability.\n */\nexport function supportsHotReload(manifest: AddonManifest): boolean {\n return manifest.capabilities?.hotReload === true\n}\n\n/**\n * Get system events the addon subscribes to.\n */\nexport function getSubscribedEvents(manifest: AddonManifest): SystemEventType[] {\n return manifest.capabilities?.systemEvents ?? []\n}\n\n/**\n * Get required permissions from manifest.\n */\nexport function getRequiredPermissions(manifest: AddonManifest): PermissionType[] {\n const permissions: PermissionType[] = []\n\n if (manifest.permissions?.storage) permissions.push('storage')\n if (manifest.permissions?.cpu) permissions.push('cpu-high')\n if (manifest.permissions?.network) permissions.push('network')\n if (manifest.permissions?.audio) permissions.push('audio')\n if (manifest.permissions?.notifications) permissions.push('notifications')\n\n return permissions\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/manifest.ts"],"names":[],"mappings":"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","file":"manifest.mjs","sourcesContent":["/**\n * @mywallpaper/addon-sdk - Manifest Schema & Validation\n *\n * Defines the addon manifest format (manifest.json) and provides\n * Zod-based validation for type safety at runtime.\n *\n * @example\n * ```typescript\n * import { validateManifest, type AddonManifest } from '@mywallpaper/addon-sdk/manifest'\n *\n * const manifest: AddonManifest = {\n * name: 'My Addon',\n * version: '1.0.0',\n * sdkVersion: '2.7.0',\n * description: 'A cool addon',\n * settings: {\n * primaryColor: { type: 'color', default: '#ffffff', label: 'Primary Color' }\n * }\n * }\n *\n * // Validate at runtime\n * const result = validateManifest(manifest)\n * if (!result.success) {\n * console.error('Invalid manifest:', result.errors)\n * }\n * ```\n */\n\nimport type { SystemEventType, PermissionType, OAuthProvider } from './types'\n\n// =============================================================================\n// SETTING TYPES\n// =============================================================================\n\n/**\n * Available setting types for addon configuration.\n */\nexport type SettingType =\n | 'string' // Text input\n | 'number' // Numeric input\n | 'boolean' // Checkbox/toggle\n | 'color' // Color picker\n | 'select' // Dropdown selection\n | 'range' // Slider\n | 'image' // Image upload\n | 'video' // Video upload\n | 'audio' // Audio upload (mp3, wav, ogg, etc.)\n | 'file' // Generic file upload (images + videos + audio)\n | 'section' // Visual grouping (no value)\n | 'textarea' // Multi-line text\n | 'radio' // Radio buttons\n | 'gradient' // Gradient picker\n | 'vector2' // 2D vector (x, y)\n | 'button' // Action button (no value)\n | 'multi-select' // Multiple selection\n\n/**\n * Definition for a single addon setting.\n *\n * @example\n * ```typescript\n * const colorSetting: SettingDefinition = {\n * type: 'color',\n * label: 'Background Color',\n * description: 'Choose a background color',\n * default: '#000000'\n * }\n *\n * const speedSetting: SettingDefinition = {\n * type: 'range',\n * label: 'Animation Speed',\n * min: 0.1,\n * max: 10,\n * step: 0.1,\n * default: 1\n * }\n * ```\n */\nexport interface SettingDefinition {\n /** Setting type determines the UI control */\n type: SettingType\n\n /** Human-readable label */\n label?: string\n\n /** Help text shown below the control */\n description?: string\n\n /** Default value (type depends on setting type) */\n default?: unknown\n\n // Select/Radio/Multi-select options\n /** Options for select, radio, multi-select types */\n options?: Array<{ value: string; label: string }>\n\n // Range-specific\n /** Minimum value for range type */\n min?: number\n /** Maximum value for range type */\n max?: number\n /** Step increment for range type */\n step?: number\n\n // Button-specific\n /** Label text on button */\n buttonLabel?: string\n\n // Vector-specific\n /** Axis labels for vector types, e.g., ['X', 'Y'] */\n axisLabels?: [string, string] | [string, string, string]\n\n // Multi-select specific\n /** Maximum number of selections */\n maxItems?: number\n\n // Conditional visibility\n /** Only show if another setting has a specific value */\n showIf?: {\n setting: string\n equals: unknown\n }\n}\n\n// =============================================================================\n// MANIFEST SCHEMA\n// =============================================================================\n\n/**\n * Default layout configuration for addon positioning.\n */\nexport interface AddonDefaultLayout {\n /** X position as percentage of viewport width (0-100) */\n xPercent?: number\n /** Y position as percentage of viewport height (0-100) */\n yPercent?: number\n /** Width as percentage of viewport width (0-100) */\n widthPercent?: number\n /** Height as percentage of viewport height (0-100) */\n heightPercent?: number\n /** Rotation in degrees */\n rotation?: number\n}\n\n/**\n * The complete addon manifest schema.\n * This defines your addon's identity, settings, and capabilities.\n *\n * @example\n * ```json\n * {\n * \"name\": \"Weather Widget\",\n * \"version\": \"1.2.0\",\n * \"description\": \"Displays current weather conditions\",\n * \"author\": \"Jane Developer\",\n * \"categories\": [\"utilities\", \"weather\"],\n * \"capabilities\": {\n * \"hotReload\": true,\n * \"systemEvents\": [\"viewport:resize\", \"theme:change\"]\n * },\n * \"permissions\": {\n * \"storage\": { \"quota\": 512 },\n * \"network\": { \"domains\": [\"api.openweathermap.org\"] }\n * },\n * \"settings\": {\n * \"apiKey\": {\n * \"type\": \"string\",\n * \"label\": \"API Key\",\n * \"description\": \"Get your key at openweathermap.org\"\n * },\n * \"unit\": {\n * \"type\": \"select\",\n * \"label\": \"Temperature Unit\",\n * \"default\": \"celsius\",\n * \"options\": [\n * { \"value\": \"celsius\", \"label\": \"Celsius\" },\n * { \"value\": \"fahrenheit\", \"label\": \"Fahrenheit\" }\n * ]\n * }\n * }\n * }\n * ```\n */\nexport interface AddonManifest {\n // ---------------------------------------------------------------------------\n // REQUIRED FIELDS\n // ---------------------------------------------------------------------------\n\n /** Addon name (displayed in UI) */\n name: string\n\n /** Semantic version (e.g., \"1.0.0\") */\n version: string\n\n /**\n * SDK version this addon was built for.\n * Must be a valid semver (e.g., \"2.3.0\").\n * Host uses this to inject the correct client-side SDK.\n */\n sdkVersion: string\n\n // ---------------------------------------------------------------------------\n // OPTIONAL METADATA\n // ---------------------------------------------------------------------------\n\n /** Short description of the addon */\n description?: string\n\n /** Author name or organization */\n author?: string\n\n /** Addon type/category for filtering */\n type?: string\n\n /** Categories for discoverability */\n categories?: string[]\n\n /** License (e.g., \"MIT\", \"Apache-2.0\") */\n license?: string\n\n /** Homepage or documentation URL */\n homepage?: string\n\n /** Repository URL */\n repository?: string\n\n // ---------------------------------------------------------------------------\n // SETTINGS\n // ---------------------------------------------------------------------------\n\n /**\n * User-configurable settings.\n * Key is the setting ID, value is the definition.\n */\n settings?: Record<string, SettingDefinition>\n\n // ---------------------------------------------------------------------------\n // CAPABILITIES\n // ---------------------------------------------------------------------------\n\n /**\n * Addon capabilities and event subscriptions.\n */\n capabilities?: {\n /** Supports settings changes without full reload */\n hotReload?: boolean\n /** System events the addon wants to receive */\n systemEvents?: SystemEventType[]\n }\n\n // ---------------------------------------------------------------------------\n // PERMISSIONS\n // ---------------------------------------------------------------------------\n\n /**\n * Permissions the addon will request.\n * Declaring them here improves UX by showing upfront.\n */\n permissions?: {\n /** Storage permission with quota in KB */\n storage?: { quota: number }\n /** CPU usage limit */\n cpu?: { limit: 'low' | 'medium' | 'high' }\n /** Network access with allowed domains */\n network?: { domains: string[] }\n /** Audio playback permission */\n audio?: boolean\n /** Notification permission */\n notifications?: boolean\n /**\n * File access permission for uploaded media files.\n * When enabled, the addon can receive uploaded files (video, audio, images)\n * via secure blob transfer for playback.\n *\n * This is required for addons that play user-uploaded media content.\n */\n fileAccess?: {\n /** User-friendly reason why this permission is needed */\n reason: string\n }\n /**\n * Same-origin permission for blob URL support.\n * Required for addons that handle large local files (videos, audio).\n *\n * ⚠️ SECURITY WARNING: This allows the addon to access:\n * - localStorage/sessionStorage of the parent origin\n * - Cookies of the parent domain\n * - IndexedDB of the parent origin\n *\n * Only grant to trusted addons!\n */\n sameOrigin?: {\n /** User-friendly reason why this permission is needed */\n reason: string\n }\n }\n\n // ---------------------------------------------------------------------------\n // LAYOUT\n // ---------------------------------------------------------------------------\n\n /**\n * Default layout for automatic positioning.\n * Used when addon is first added to a wallpaper.\n */\n defaultLayout?: AddonDefaultLayout\n\n // ---------------------------------------------------------------------------\n // FUTURE FIELDS\n // ---------------------------------------------------------------------------\n\n /**\n * OAuth providers and scopes for authenticated addons.\n *\n * Declare the OAuth providers your addon needs and the scopes it requires.\n * Scopes are requested incrementally - users can connect with basic scopes\n * first, and your addon can request additional scopes when needed.\n *\n * @example\n * ```json\n * {\n * \"permissions\": {\n * \"oauth\": {\n * \"required\": [\n * { \"provider\": \"github\", \"scopes\": [\"read:user\"] }\n * ],\n * \"optional\": [\n * { \"provider\": \"github\", \"scopes\": [\"repo\"] }\n * ]\n * }\n * }\n * }\n * ```\n */\n oauth?: {\n /** OAuth providers required for the addon to function */\n required?: Array<{\n provider: OAuthProvider\n scopes: string[]\n /** Reason shown to user when requesting this permission */\n reason?: string\n }>\n /** OAuth providers that enhance functionality but aren't required */\n optional?: Array<{\n provider: OAuthProvider\n scopes: string[]\n /** Reason shown to user when requesting this permission */\n reason?: string\n }>\n }\n}\n\n// =============================================================================\n// VALIDATION\n// =============================================================================\n\n/**\n * Validation result type.\n */\nexport interface ValidationResult {\n success: boolean\n errors?: string[]\n manifest?: AddonManifest\n}\n\n/**\n * Validates an addon manifest object.\n *\n * @param input - The manifest object to validate\n * @returns ValidationResult with success status and any errors\n *\n * @example\n * ```typescript\n * const result = validateManifest(myManifest)\n * if (!result.success) {\n * result.errors?.forEach(err => console.error(err))\n * }\n * ```\n */\nexport function validateManifest(input: unknown): ValidationResult {\n const errors: string[] = []\n\n if (!input || typeof input !== 'object') {\n return { success: false, errors: ['Manifest must be an object'] }\n }\n\n const manifest = input as Record<string, unknown>\n\n // Required fields\n if (typeof manifest.name !== 'string' || manifest.name.trim() === '') {\n errors.push('name: Required and must be a non-empty string')\n }\n\n if (typeof manifest.version !== 'string' || !/^\\d+\\.\\d+\\.\\d+/.test(manifest.version)) {\n errors.push('version: Required and must be a valid semver (e.g., \"1.0.0\")')\n }\n\n if (typeof manifest.sdkVersion !== 'string' || !/^\\d+\\.\\d+\\.\\d+/.test(manifest.sdkVersion)) {\n errors.push('sdkVersion: Required and must be a valid semver (e.g., \"2.2.0\")')\n }\n\n // Optional string fields\n const optionalStrings = ['description', 'author', 'type', 'license', 'homepage', 'repository']\n for (const field of optionalStrings) {\n if (manifest[field] !== undefined && typeof manifest[field] !== 'string') {\n errors.push(`${field}: Must be a string`)\n }\n }\n\n // Categories\n if (manifest.categories !== undefined) {\n if (!Array.isArray(manifest.categories)) {\n errors.push('categories: Must be an array of strings')\n } else if (!manifest.categories.every(c => typeof c === 'string')) {\n errors.push('categories: All items must be strings')\n }\n }\n\n // Settings validation\n if (manifest.settings !== undefined) {\n if (typeof manifest.settings !== 'object' || manifest.settings === null) {\n errors.push('settings: Must be an object')\n } else {\n const settings = manifest.settings as Record<string, unknown>\n for (const [key, value] of Object.entries(settings)) {\n if (typeof value !== 'object' || value === null) {\n errors.push(`settings.${key}: Must be an object`)\n continue\n }\n\n const setting = value as Record<string, unknown>\n const validTypes: SettingType[] = [\n 'string', 'number', 'boolean', 'color', 'select', 'range',\n 'image', 'video', 'audio', 'file', 'section', 'textarea', 'radio', 'gradient', 'vector2',\n 'button', 'multi-select'\n ]\n\n if (!validTypes.includes(setting.type as SettingType)) {\n errors.push(`settings.${key}.type: Must be one of: ${validTypes.join(', ')}`)\n }\n\n // Type-specific validation\n if (['select', 'radio', 'multi-select'].includes(setting.type as string)) {\n if (!Array.isArray(setting.options) || setting.options.length === 0) {\n errors.push(`settings.${key}.options: Required for ${setting.type} type`)\n }\n }\n\n if (setting.type === 'range') {\n if (typeof setting.min !== 'number' || typeof setting.max !== 'number') {\n errors.push(`settings.${key}: range type requires min and max`)\n }\n }\n }\n }\n }\n\n // Capabilities validation\n if (manifest.capabilities !== undefined) {\n if (typeof manifest.capabilities !== 'object' || manifest.capabilities === null) {\n errors.push('capabilities: Must be an object')\n } else {\n const caps = manifest.capabilities as Record<string, unknown>\n\n if (caps.hotReload !== undefined && typeof caps.hotReload !== 'boolean') {\n errors.push('capabilities.hotReload: Must be a boolean')\n }\n\n if (caps.systemEvents !== undefined) {\n const validEvents: SystemEventType[] = [\n 'viewport:resize', 'theme:change', 'visibility:change',\n 'layer:focus', 'layer:blur', 'app:idle', 'app:active'\n ]\n\n if (!Array.isArray(caps.systemEvents)) {\n errors.push('capabilities.systemEvents: Must be an array')\n } else {\n for (const event of caps.systemEvents) {\n if (!validEvents.includes(event)) {\n errors.push(`capabilities.systemEvents: Invalid event \"${event}\"`)\n }\n }\n }\n }\n }\n }\n\n // Permissions validation\n if (manifest.permissions !== undefined) {\n if (typeof manifest.permissions !== 'object' || manifest.permissions === null) {\n errors.push('permissions: Must be an object')\n } else {\n const perms = manifest.permissions as Record<string, unknown>\n\n if (perms.storage !== undefined) {\n const storage = perms.storage as Record<string, unknown>\n if (typeof storage?.quota !== 'number' || storage.quota <= 0) {\n errors.push('permissions.storage.quota: Must be a positive number')\n }\n }\n\n if (perms.cpu !== undefined) {\n const cpu = perms.cpu as Record<string, unknown>\n if (!['low', 'medium', 'high'].includes(cpu?.limit as string)) {\n errors.push('permissions.cpu.limit: Must be low, medium, or high')\n }\n }\n\n if (perms.network !== undefined) {\n const network = perms.network as Record<string, unknown>\n if (!Array.isArray(network?.domains)) {\n errors.push('permissions.network.domains: Must be an array of domain strings')\n }\n }\n }\n }\n\n // Default layout validation\n if (manifest.defaultLayout !== undefined) {\n if (typeof manifest.defaultLayout !== 'object' || manifest.defaultLayout === null) {\n errors.push('defaultLayout: Must be an object')\n } else {\n const layout = manifest.defaultLayout as Record<string, unknown>\n const numFields = ['xPercent', 'yPercent', 'widthPercent', 'heightPercent', 'rotation']\n for (const field of numFields) {\n if (layout[field] !== undefined && typeof layout[field] !== 'number') {\n errors.push(`defaultLayout.${field}: Must be a number`)\n }\n }\n }\n }\n\n if (errors.length > 0) {\n return { success: false, errors }\n }\n\n return { success: true, manifest: manifest as unknown as AddonManifest }\n}\n\n/**\n * Type guard to check if an object is a valid AddonManifest.\n *\n * @param input - Object to check\n * @returns True if input is a valid AddonManifest\n */\nexport function isValidManifest(input: unknown): input is AddonManifest {\n return validateManifest(input).success\n}\n\n// =============================================================================\n// UTILITY FUNCTIONS\n// =============================================================================\n\n/**\n * Check if manifest declares hot-reload capability.\n */\nexport function supportsHotReload(manifest: AddonManifest): boolean {\n return manifest.capabilities?.hotReload === true\n}\n\n/**\n * Get system events the addon subscribes to.\n */\nexport function getSubscribedEvents(manifest: AddonManifest): SystemEventType[] {\n return manifest.capabilities?.systemEvents ?? []\n}\n\n/**\n * Get required permissions from manifest.\n */\nexport function getRequiredPermissions(manifest: AddonManifest): PermissionType[] {\n const permissions: PermissionType[] = []\n\n if (manifest.permissions?.storage) permissions.push('storage')\n if (manifest.permissions?.cpu) permissions.push('cpu-high')\n if (manifest.permissions?.network) permissions.push('network')\n if (manifest.permissions?.audio) permissions.push('audio')\n if (manifest.permissions?.notifications) permissions.push('notifications')\n\n return permissions\n}\n"]}
|
package/package.json
CHANGED
|
@@ -31,6 +31,17 @@
|
|
|
31
31
|
var pendingFileAccess = new Map()
|
|
32
32
|
var grantedBlobUrls = new Map() // settingKey -> blobUrl
|
|
33
33
|
|
|
34
|
+
// Audio state (synced from host)
|
|
35
|
+
var audioState = {
|
|
36
|
+
status: 'none',
|
|
37
|
+
currentTime: 0,
|
|
38
|
+
duration: 0,
|
|
39
|
+
volume: 1,
|
|
40
|
+
loop: false,
|
|
41
|
+
playbackRate: 1
|
|
42
|
+
}
|
|
43
|
+
var audioCallbacks = new Set()
|
|
44
|
+
|
|
34
45
|
// Get the original fetch saved by network-sandbox.js (before it was blocked)
|
|
35
46
|
var originalFetch = window.__MYWALLPAPER_ORIGINAL_FETCH__ || null
|
|
36
47
|
|
|
@@ -44,7 +55,28 @@
|
|
|
44
55
|
case 'SETTINGS_UPDATE':
|
|
45
56
|
var p = d.payload || d
|
|
46
57
|
config = p.settings || p
|
|
47
|
-
|
|
58
|
+
// changedKeys may be in p.payload (nested structure from host)
|
|
59
|
+
var keys = p.changedKeys || (p.payload && p.payload.changedKeys) || Object.keys(config)
|
|
60
|
+
|
|
61
|
+
// AUTO-UPDATE CSS VARIABLES (DX v2.3+)
|
|
62
|
+
// Only update primitive values (string, number, boolean) to avoid [object Object]
|
|
63
|
+
try {
|
|
64
|
+
var root = document.documentElement
|
|
65
|
+
for (var i = 0; i < keys.length; i++) {
|
|
66
|
+
var key = keys[i]
|
|
67
|
+
var value = config[key]
|
|
68
|
+
if (value !== undefined && value !== null) {
|
|
69
|
+
var valueType = typeof value
|
|
70
|
+
// Only set CSS variables for primitive types
|
|
71
|
+
if (valueType === 'string' || valueType === 'number' || valueType === 'boolean') {
|
|
72
|
+
root.style.setProperty('--mw-config-' + key, String(value))
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} catch (cssErr) {
|
|
77
|
+
// Silent fail - CSS variable updates are a DX convenience, not critical
|
|
78
|
+
}
|
|
79
|
+
|
|
48
80
|
settingsCallbacks.forEach(function (cb) {
|
|
49
81
|
try { cb(config, keys) } catch (err) { console.error('[MyWallpaper]', err) }
|
|
50
82
|
})
|
|
@@ -164,6 +196,14 @@
|
|
|
164
196
|
}
|
|
165
197
|
}
|
|
166
198
|
break
|
|
199
|
+
|
|
200
|
+
case 'AUDIO_STATE':
|
|
201
|
+
// Sync audio state from host
|
|
202
|
+
audioState = d.payload || d
|
|
203
|
+
audioCallbacks.forEach(function (cb) {
|
|
204
|
+
try { cb(audioState) } catch (err) { console.error('[MyWallpaper] Audio callback error:', err) }
|
|
205
|
+
})
|
|
206
|
+
break
|
|
167
207
|
}
|
|
168
208
|
}
|
|
169
209
|
|
|
@@ -482,6 +522,125 @@
|
|
|
482
522
|
grantedBlobUrls.delete(settingKey)
|
|
483
523
|
}
|
|
484
524
|
}
|
|
525
|
+
},
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Audio API - Parent-managed audio playback
|
|
529
|
+
* Audio is played by the host (parent window) to bypass iframe sandbox restrictions
|
|
530
|
+
* The addon controls playback via commands, host syncs state back
|
|
531
|
+
*
|
|
532
|
+
* @example
|
|
533
|
+
* // Play audio from a URL
|
|
534
|
+
* MyWallpaper.audio.play('https://example.com/sound.mp3', { volume: 0.8 })
|
|
535
|
+
*
|
|
536
|
+
* // Play audio from a file setting
|
|
537
|
+
* MyWallpaper.audio.play('audioFile') // settingKey
|
|
538
|
+
*
|
|
539
|
+
* // Listen to state changes
|
|
540
|
+
* MyWallpaper.audio.onStateChange(function(state) {
|
|
541
|
+
* console.log('Audio status:', state.status, 'Time:', state.currentTime)
|
|
542
|
+
* })
|
|
543
|
+
*/
|
|
544
|
+
audio: {
|
|
545
|
+
/**
|
|
546
|
+
* Play audio from a source
|
|
547
|
+
* @param {string|object} source - URL, settingKey, or FileReference
|
|
548
|
+
* @param {object} options - { volume, loop, playbackRate, startTime }
|
|
549
|
+
*/
|
|
550
|
+
play: function (source, options) {
|
|
551
|
+
var payload = { command: 'play', options: options || {} }
|
|
552
|
+
|
|
553
|
+
if (typeof source === 'string') {
|
|
554
|
+
if (source.startsWith('http') || source.startsWith('data:') || source.startsWith('blob:')) {
|
|
555
|
+
payload.url = source
|
|
556
|
+
} else {
|
|
557
|
+
// Assume it's a settingKey
|
|
558
|
+
payload.settingKey = source
|
|
559
|
+
}
|
|
560
|
+
} else if (source && source.__type === 'file-reference') {
|
|
561
|
+
payload.fileRef = source
|
|
562
|
+
} else if (source && source.settingKey) {
|
|
563
|
+
payload.settingKey = source.settingKey
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
send('AUDIO_COMMAND', payload)
|
|
567
|
+
},
|
|
568
|
+
|
|
569
|
+
/** Pause audio playback */
|
|
570
|
+
pause: function () {
|
|
571
|
+
send('AUDIO_COMMAND', { command: 'pause' })
|
|
572
|
+
},
|
|
573
|
+
|
|
574
|
+
/** Resume audio playback */
|
|
575
|
+
resume: function () {
|
|
576
|
+
send('AUDIO_COMMAND', { command: 'resume' })
|
|
577
|
+
},
|
|
578
|
+
|
|
579
|
+
/** Stop and reset audio */
|
|
580
|
+
stop: function () {
|
|
581
|
+
send('AUDIO_COMMAND', { command: 'stop' })
|
|
582
|
+
},
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Seek to position
|
|
586
|
+
* @param {number} seconds - Position in seconds
|
|
587
|
+
*/
|
|
588
|
+
seek: function (seconds) {
|
|
589
|
+
send('AUDIO_COMMAND', { command: 'seek', value: seconds })
|
|
590
|
+
},
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Set volume
|
|
594
|
+
* @param {number} volume - Volume from 0.0 to 1.0
|
|
595
|
+
*/
|
|
596
|
+
setVolume: function (volume) {
|
|
597
|
+
send('AUDIO_COMMAND', { command: 'setVolume', value: Math.max(0, Math.min(1, volume)) })
|
|
598
|
+
},
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Enable or disable loop
|
|
602
|
+
* @param {boolean} loop - Whether to loop
|
|
603
|
+
*/
|
|
604
|
+
setLoop: function (loop) {
|
|
605
|
+
send('AUDIO_COMMAND', { command: 'setLoop', value: !!loop })
|
|
606
|
+
},
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Set playback rate
|
|
610
|
+
* @param {number} rate - Playback rate (0.25 to 4.0)
|
|
611
|
+
*/
|
|
612
|
+
setPlaybackRate: function (rate) {
|
|
613
|
+
send('AUDIO_COMMAND', { command: 'setPlaybackRate', value: rate })
|
|
614
|
+
},
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Get current audio state
|
|
618
|
+
* @returns {object} { status, currentTime, duration, volume, loop, playbackRate, error? }
|
|
619
|
+
*/
|
|
620
|
+
getState: function () {
|
|
621
|
+
return {
|
|
622
|
+
status: audioState.status,
|
|
623
|
+
currentTime: audioState.currentTime,
|
|
624
|
+
duration: audioState.duration,
|
|
625
|
+
volume: audioState.volume,
|
|
626
|
+
loop: audioState.loop,
|
|
627
|
+
playbackRate: audioState.playbackRate,
|
|
628
|
+
error: audioState.error
|
|
629
|
+
}
|
|
630
|
+
},
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Listen to audio state changes
|
|
634
|
+
* @param {function} callback - Called with AudioState on each change
|
|
635
|
+
* @returns {function} Unsubscribe function
|
|
636
|
+
*/
|
|
637
|
+
onStateChange: function (callback) {
|
|
638
|
+
if (typeof callback !== 'function') return function () { }
|
|
639
|
+
audioCallbacks.add(callback)
|
|
640
|
+
// Call immediately with current state
|
|
641
|
+
callback(audioState)
|
|
642
|
+
return function () { audioCallbacks.delete(callback) }
|
|
643
|
+
}
|
|
485
644
|
}
|
|
486
645
|
}
|
|
487
646
|
|