@mywallpaper/addon-sdk 2.6.1 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,7 +30,7 @@ my-addon/
30
30
  {
31
31
  "name": "My Awesome Addon",
32
32
  "version": "1.0.0",
33
- "sdkVersion": "2.3.0",
33
+ "sdkVersion": "2.7.0",
34
34
  "description": "A cool widget for your desktop",
35
35
  "author": "Your Name",
36
36
  "capabilities": {
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mywallpaper/addon-sdk - TypeScript Types v2.4
2
+ * @mywallpaper/addon-sdk - TypeScript Types v2.5
3
3
  *
4
4
  * Complete type definitions for building MyWallpaper addons.
5
5
  * These types describe the runtime API available via window.MyWallpaper
@@ -541,6 +541,22 @@ interface MyWallpaperAPI {
541
541
  * OAuth API for making authenticated requests to provider APIs.
542
542
  */
543
543
  oauth: OAuthAPI;
544
+ /**
545
+ * Audio API for playing audio through the host (parent).
546
+ * Required because sandboxed iframes cannot autoplay audio.
547
+ *
548
+ * @example
549
+ * ```typescript
550
+ * // Play audio from a file setting
551
+ * api.audio.play('mediaFile', { volume: 0.8, loop: true })
552
+ *
553
+ * // Control playback
554
+ * api.audio.pause()
555
+ * api.audio.seek(30)
556
+ * api.audio.resume()
557
+ * ```
558
+ */
559
+ audio: AudioAPI;
544
560
  /**
545
561
  * File Access API for requesting access to user-uploaded files.
546
562
  * Files are not sent automatically - the addon must request access.
@@ -679,6 +695,135 @@ interface FileAccessAPI {
679
695
  */
680
696
  revoke(settingKey: string): void;
681
697
  }
698
+ /**
699
+ * Options for audio playback.
700
+ */
701
+ interface AudioOptions {
702
+ /** Initial volume (0.0 to 1.0). Default: 1.0 */
703
+ volume?: number;
704
+ /** Whether to loop playback. Default: false */
705
+ loop?: boolean;
706
+ /** Playback speed (0.25 to 4.0). Default: 1.0 */
707
+ playbackRate?: number;
708
+ /** Start position in seconds. Default: 0 */
709
+ startTime?: number;
710
+ }
711
+ /**
712
+ * Current audio playback state.
713
+ * Synced from host to addon via AUDIO_STATE messages.
714
+ */
715
+ interface AudioState {
716
+ /** Current playback status */
717
+ status: 'none' | 'loading' | 'playing' | 'paused' | 'ended' | 'error';
718
+ /** Current playback position in seconds */
719
+ currentTime: number;
720
+ /** Total duration in seconds */
721
+ duration: number;
722
+ /** Current volume (0.0 to 1.0) */
723
+ volume: number;
724
+ /** Whether loop is enabled */
725
+ loop: boolean;
726
+ /** Current playback speed */
727
+ playbackRate: number;
728
+ /** Error message if status is 'error' */
729
+ error?: string;
730
+ }
731
+ /**
732
+ * Audio API for playing audio through the host (parent).
733
+ * Access via `window.MyWallpaper.audio`
734
+ *
735
+ * **Why this exists:**
736
+ * Sandboxed iframes (without allow-same-origin) cannot autoplay audio
737
+ * due to browser security policies. This API allows addons to play
738
+ * audio by delegating playback to the host application.
739
+ *
740
+ * **How it works:**
741
+ * 1. Addon calls `audio.play(source)` with a URL or file reference
742
+ * 2. Host creates an `<audio>` element in its own context
743
+ * 3. Host syncs playback state back to addon via AUDIO_STATE messages
744
+ * 4. Addon can control playback with pause/resume/seek/etc.
745
+ *
746
+ * @example
747
+ * ```typescript
748
+ * const api = window.MyWallpaper
749
+ *
750
+ * // Play audio from a setting file reference
751
+ * api.audio.play('backgroundMusic', {
752
+ * volume: 0.8,
753
+ * loop: true
754
+ * })
755
+ *
756
+ * // Or play from URL
757
+ * api.audio.play('https://example.com/audio.mp3')
758
+ *
759
+ * // Listen for state changes
760
+ * api.audio.onStateChange((state) => {
761
+ * console.log(`Audio is ${state.status} at ${state.currentTime}s`)
762
+ * })
763
+ *
764
+ * // Control playback
765
+ * api.audio.pause()
766
+ * api.audio.seek(30) // Jump to 30 seconds
767
+ * api.audio.resume()
768
+ * ```
769
+ */
770
+ interface AudioAPI {
771
+ /**
772
+ * Play audio from a source.
773
+ * Source can be a URL, a setting key name, or a FileReference.
774
+ *
775
+ * @param source - URL string, setting key, or FileReference
776
+ * @param options - Playback options (volume, loop, etc.)
777
+ */
778
+ play(source: string | FileReference | {
779
+ settingKey: string;
780
+ }, options?: AudioOptions): void;
781
+ /**
782
+ * Pause playback.
783
+ */
784
+ pause(): void;
785
+ /**
786
+ * Resume playback from current position.
787
+ */
788
+ resume(): void;
789
+ /**
790
+ * Stop playback and reset to beginning.
791
+ */
792
+ stop(): void;
793
+ /**
794
+ * Seek to a specific position.
795
+ * @param seconds - Position in seconds
796
+ */
797
+ seek(seconds: number): void;
798
+ /**
799
+ * Set the volume.
800
+ * @param volume - Volume level (0.0 to 1.0)
801
+ */
802
+ setVolume(volume: number): void;
803
+ /**
804
+ * Enable or disable loop.
805
+ * @param loop - Whether to loop
806
+ */
807
+ setLoop(loop: boolean): void;
808
+ /**
809
+ * Set the playback speed.
810
+ * @param rate - Speed multiplier (0.25 to 4.0)
811
+ */
812
+ setPlaybackRate(rate: number): void;
813
+ /**
814
+ * Get the current audio state.
815
+ * @returns Current AudioState
816
+ */
817
+ getState(): AudioState;
818
+ /**
819
+ * Subscribe to audio state changes.
820
+ * Callback is called immediately with current state, then on each change.
821
+ *
822
+ * @param callback - Function called with new state
823
+ * @returns Unsubscribe function
824
+ */
825
+ onStateChange(callback: (state: AudioState) => void): () => void;
826
+ }
682
827
  /**
683
828
  * Generic addon values (settings configuration).
684
829
  */
@@ -697,7 +842,7 @@ type AddonValues = Record<string, unknown>;
697
842
  * const manifest: AddonManifest = {
698
843
  * name: 'My Addon',
699
844
  * version: '1.0.0',
700
- * sdkVersion: '2.3.0',
845
+ * sdkVersion: '2.7.0',
701
846
  * description: 'A cool addon',
702
847
  * settings: {
703
848
  * primaryColor: { type: 'color', default: '#ffffff', label: 'Primary Color' }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mywallpaper/addon-sdk - TypeScript Types v2.4
2
+ * @mywallpaper/addon-sdk - TypeScript Types v2.5
3
3
  *
4
4
  * Complete type definitions for building MyWallpaper addons.
5
5
  * These types describe the runtime API available via window.MyWallpaper
@@ -541,6 +541,22 @@ interface MyWallpaperAPI {
541
541
  * OAuth API for making authenticated requests to provider APIs.
542
542
  */
543
543
  oauth: OAuthAPI;
544
+ /**
545
+ * Audio API for playing audio through the host (parent).
546
+ * Required because sandboxed iframes cannot autoplay audio.
547
+ *
548
+ * @example
549
+ * ```typescript
550
+ * // Play audio from a file setting
551
+ * api.audio.play('mediaFile', { volume: 0.8, loop: true })
552
+ *
553
+ * // Control playback
554
+ * api.audio.pause()
555
+ * api.audio.seek(30)
556
+ * api.audio.resume()
557
+ * ```
558
+ */
559
+ audio: AudioAPI;
544
560
  /**
545
561
  * File Access API for requesting access to user-uploaded files.
546
562
  * Files are not sent automatically - the addon must request access.
@@ -679,6 +695,135 @@ interface FileAccessAPI {
679
695
  */
680
696
  revoke(settingKey: string): void;
681
697
  }
698
+ /**
699
+ * Options for audio playback.
700
+ */
701
+ interface AudioOptions {
702
+ /** Initial volume (0.0 to 1.0). Default: 1.0 */
703
+ volume?: number;
704
+ /** Whether to loop playback. Default: false */
705
+ loop?: boolean;
706
+ /** Playback speed (0.25 to 4.0). Default: 1.0 */
707
+ playbackRate?: number;
708
+ /** Start position in seconds. Default: 0 */
709
+ startTime?: number;
710
+ }
711
+ /**
712
+ * Current audio playback state.
713
+ * Synced from host to addon via AUDIO_STATE messages.
714
+ */
715
+ interface AudioState {
716
+ /** Current playback status */
717
+ status: 'none' | 'loading' | 'playing' | 'paused' | 'ended' | 'error';
718
+ /** Current playback position in seconds */
719
+ currentTime: number;
720
+ /** Total duration in seconds */
721
+ duration: number;
722
+ /** Current volume (0.0 to 1.0) */
723
+ volume: number;
724
+ /** Whether loop is enabled */
725
+ loop: boolean;
726
+ /** Current playback speed */
727
+ playbackRate: number;
728
+ /** Error message if status is 'error' */
729
+ error?: string;
730
+ }
731
+ /**
732
+ * Audio API for playing audio through the host (parent).
733
+ * Access via `window.MyWallpaper.audio`
734
+ *
735
+ * **Why this exists:**
736
+ * Sandboxed iframes (without allow-same-origin) cannot autoplay audio
737
+ * due to browser security policies. This API allows addons to play
738
+ * audio by delegating playback to the host application.
739
+ *
740
+ * **How it works:**
741
+ * 1. Addon calls `audio.play(source)` with a URL or file reference
742
+ * 2. Host creates an `<audio>` element in its own context
743
+ * 3. Host syncs playback state back to addon via AUDIO_STATE messages
744
+ * 4. Addon can control playback with pause/resume/seek/etc.
745
+ *
746
+ * @example
747
+ * ```typescript
748
+ * const api = window.MyWallpaper
749
+ *
750
+ * // Play audio from a setting file reference
751
+ * api.audio.play('backgroundMusic', {
752
+ * volume: 0.8,
753
+ * loop: true
754
+ * })
755
+ *
756
+ * // Or play from URL
757
+ * api.audio.play('https://example.com/audio.mp3')
758
+ *
759
+ * // Listen for state changes
760
+ * api.audio.onStateChange((state) => {
761
+ * console.log(`Audio is ${state.status} at ${state.currentTime}s`)
762
+ * })
763
+ *
764
+ * // Control playback
765
+ * api.audio.pause()
766
+ * api.audio.seek(30) // Jump to 30 seconds
767
+ * api.audio.resume()
768
+ * ```
769
+ */
770
+ interface AudioAPI {
771
+ /**
772
+ * Play audio from a source.
773
+ * Source can be a URL, a setting key name, or a FileReference.
774
+ *
775
+ * @param source - URL string, setting key, or FileReference
776
+ * @param options - Playback options (volume, loop, etc.)
777
+ */
778
+ play(source: string | FileReference | {
779
+ settingKey: string;
780
+ }, options?: AudioOptions): void;
781
+ /**
782
+ * Pause playback.
783
+ */
784
+ pause(): void;
785
+ /**
786
+ * Resume playback from current position.
787
+ */
788
+ resume(): void;
789
+ /**
790
+ * Stop playback and reset to beginning.
791
+ */
792
+ stop(): void;
793
+ /**
794
+ * Seek to a specific position.
795
+ * @param seconds - Position in seconds
796
+ */
797
+ seek(seconds: number): void;
798
+ /**
799
+ * Set the volume.
800
+ * @param volume - Volume level (0.0 to 1.0)
801
+ */
802
+ setVolume(volume: number): void;
803
+ /**
804
+ * Enable or disable loop.
805
+ * @param loop - Whether to loop
806
+ */
807
+ setLoop(loop: boolean): void;
808
+ /**
809
+ * Set the playback speed.
810
+ * @param rate - Speed multiplier (0.25 to 4.0)
811
+ */
812
+ setPlaybackRate(rate: number): void;
813
+ /**
814
+ * Get the current audio state.
815
+ * @returns Current AudioState
816
+ */
817
+ getState(): AudioState;
818
+ /**
819
+ * Subscribe to audio state changes.
820
+ * Callback is called immediately with current state, then on each change.
821
+ *
822
+ * @param callback - Function called with new state
823
+ * @returns Unsubscribe function
824
+ */
825
+ onStateChange(callback: (state: AudioState) => void): () => void;
826
+ }
682
827
  /**
683
828
  * Generic addon values (settings configuration).
684
829
  */
@@ -697,7 +842,7 @@ type AddonValues = Record<string, unknown>;
697
842
  * const manifest: AddonManifest = {
698
843
  * name: 'My Addon',
699
844
  * version: '1.0.0',
700
- * sdkVersion: '2.3.0',
845
+ * sdkVersion: '2.7.0',
701
846
  * description: 'A cool addon',
702
847
  * settings: {
703
848
  * primaryColor: { type: 'color', default: '#ffffff', label: 'Primary Color' }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/manifest.ts","../src/utils.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.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","/**\n * @mywallpaper/addon-sdk - Utility Functions\n *\n * Helper utilities for addon development.\n */\n\nimport type { AddonManifest, SettingDefinition } from './manifest'\n\n// =============================================================================\n// CONFIG UTILITIES\n// =============================================================================\n\n/**\n * Creates a default configuration object from manifest settings.\n *\n * @param manifest - Addon manifest with settings definitions\n * @returns Object with all setting keys and their default values\n *\n * @example\n * ```typescript\n * const manifest = {\n * name: 'My Addon',\n * version: '1.0.0',\n * settings: {\n * color: { type: 'color', default: '#ff0000' },\n * speed: { type: 'range', default: 1, min: 0, max: 10 }\n * }\n * }\n *\n * const defaults = createDefaultConfig(manifest)\n * // { color: '#ff0000', speed: 1 }\n * ```\n */\nexport function createDefaultConfig(manifest: AddonManifest): Record<string, unknown> {\n const config: Record<string, unknown> = {}\n\n if (!manifest.settings) return config\n\n for (const [key, setting] of Object.entries(manifest.settings)) {\n if (setting.type === 'section' || setting.type === 'button') {\n // Skip non-value settings\n continue\n }\n config[key] = setting.default ?? getTypeDefault(setting)\n }\n\n return config\n}\n\n/**\n * Get type-appropriate default value when none specified.\n */\nfunction getTypeDefault(setting: SettingDefinition): unknown {\n switch (setting.type) {\n case 'string':\n case 'textarea':\n return ''\n case 'number':\n return 0\n case 'boolean':\n return false\n case 'color':\n return '#000000'\n case 'select':\n case 'radio':\n return setting.options?.[0]?.value ?? ''\n case 'range':\n return setting.min ?? 0\n case 'image':\n return null\n case 'gradient':\n return { stops: [{ color: '#000000', position: 0 }, { color: '#ffffff', position: 100 }] }\n case 'vector2':\n return { x: 0, y: 0 }\n case 'multi-select':\n return []\n default:\n return null\n }\n}\n\n/**\n * Merges user config with defaults, ensuring all keys exist.\n *\n * @param defaults - Default configuration\n * @param userConfig - User-provided configuration (may be partial)\n * @returns Complete configuration with user values taking precedence\n *\n * @example\n * ```typescript\n * const defaults = { color: '#ff0000', speed: 1 }\n * const user = { color: '#00ff00' }\n *\n * const config = mergeConfigs(defaults, user)\n * // { color: '#00ff00', speed: 1 }\n * ```\n */\nexport function mergeConfigs(\n defaults: Record<string, unknown>,\n userConfig: Record<string, unknown>\n): Record<string, unknown> {\n return { ...defaults, ...userConfig }\n}\n\n// =============================================================================\n// VERSION UTILITIES\n// =============================================================================\n\n/**\n * Semantic version comparison result.\n */\nexport type VersionComparison = -1 | 0 | 1\n\n/**\n * Compare two semantic versions.\n *\n * @param a - First version string\n * @param b - Second version string\n * @returns -1 if a < b, 0 if a === b, 1 if a > b\n *\n * @example\n * ```typescript\n * compareVersions('1.0.0', '1.0.1') // -1\n * compareVersions('2.0.0', '1.9.9') // 1\n * compareVersions('1.0.0', '1.0.0') // 0\n * ```\n */\nexport function compareVersions(a: string, b: string): VersionComparison {\n const parseVersion = (v: string): number[] => {\n return v.split('.').map(n => parseInt(n, 10) || 0)\n }\n\n const partsA = parseVersion(a)\n const partsB = parseVersion(b)\n\n for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {\n const numA = partsA[i] ?? 0\n const numB = partsB[i] ?? 0\n\n if (numA < numB) return -1\n if (numA > numB) return 1\n }\n\n return 0\n}\n\n/**\n * Check if version satisfies a minimum requirement.\n *\n * @param version - Version to check\n * @param minimum - Minimum required version\n * @returns True if version >= minimum\n */\nexport function satisfiesMinVersion(version: string, minimum: string): boolean {\n return compareVersions(version, minimum) >= 0\n}\n\n// =============================================================================\n// STORAGE UTILITIES\n// =============================================================================\n\n/**\n * Calculate the byte size of a value when JSON serialized.\n * Useful for quota tracking.\n *\n * @param value - Value to measure\n * @returns Size in bytes\n */\nexport function getStorageSize(value: unknown): number {\n try {\n return new Blob([JSON.stringify(value)]).size\n } catch {\n return 0\n }\n}\n\n/**\n * Format bytes as human-readable string.\n *\n * @param bytes - Number of bytes\n * @returns Formatted string (e.g., \"1.5 KB\", \"2.3 MB\")\n */\nexport function formatBytes(bytes: number): string {\n if (bytes === 0) return '0 B'\n\n const units = ['B', 'KB', 'MB', 'GB']\n const k = 1024\n const i = Math.floor(Math.log(Math.abs(bytes)) / Math.log(k))\n\n return `${(bytes / Math.pow(k, i)).toFixed(i > 0 ? 1 : 0)} ${units[i]}`\n}\n\n// =============================================================================\n// TYPE GUARDS\n// =============================================================================\n\n/**\n * Check if a value is a plain object.\n */\nexport function isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * Check if a value is a valid color hex string.\n */\nexport function isValidColor(value: unknown): value is string {\n if (typeof value !== 'string') return false\n return /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/.test(value)\n}\n\n// =============================================================================\n// DEBOUNCE/THROTTLE\n// =============================================================================\n\n/**\n * Debounce a function - only call after delay with no new calls.\n *\n * @param fn - Function to debounce\n * @param delayMs - Delay in milliseconds\n * @returns Debounced function\n *\n * @example\n * ```typescript\n * const save = debounce((data) => {\n * localStorage.setItem('data', JSON.stringify(data))\n * }, 1000)\n *\n * // Rapid calls only trigger one save after 1s\n * save({ a: 1 })\n * save({ a: 2 })\n * save({ a: 3 }) // Only this one runs (after 1s)\n * ```\n */\nexport function debounce<T extends (...args: unknown[]) => void>(\n fn: T,\n delayMs: number\n): (...args: Parameters<T>) => void {\n let timeoutId: ReturnType<typeof setTimeout> | null = null\n\n return (...args: Parameters<T>) => {\n if (timeoutId) clearTimeout(timeoutId)\n timeoutId = setTimeout(() => fn(...args), delayMs)\n }\n}\n\n/**\n * Throttle a function - call at most once per interval.\n *\n * @param fn - Function to throttle\n * @param intervalMs - Minimum interval between calls\n * @returns Throttled function\n */\nexport function throttle<T extends (...args: unknown[]) => void>(\n fn: T,\n intervalMs: number\n): (...args: Parameters<T>) => void {\n let lastCall = 0\n\n return (...args: Parameters<T>) => {\n const now = Date.now()\n if (now - lastCall >= intervalMs) {\n lastCall = now\n fn(...args)\n }\n }\n}\n\n// =============================================================================\n// EVENT HELPERS\n// =============================================================================\n\n/**\n * Create a one-time event listener that auto-removes after first call.\n *\n * @param element - DOM element or window\n * @param event - Event name\n * @param handler - Event handler\n */\nexport function once(\n element: EventTarget,\n event: string,\n handler: EventListener\n): void {\n const wrappedHandler: EventListener = (e) => {\n element.removeEventListener(event, wrappedHandler)\n handler(e)\n }\n element.addEventListener(event, wrappedHandler)\n}\n","/**\n * @mywallpaper/addon-sdk\n *\n * Official SDK for building MyWallpaper addons.\n *\n * @packageDocumentation\n *\n * ## Installation\n *\n * ```bash\n * npm install @mywallpaper/addon-sdk\n * ```\n *\n * ## Quick Start\n *\n * ```typescript\n * // Your addon's main file\n * import type { MyWallpaperAPI } from '@mywallpaper/addon-sdk'\n *\n * // The API is automatically available on window.MyWallpaper\n * const api = window.MyWallpaper\n *\n * // Access configuration\n * const { backgroundColor, animationSpeed } = api.config\n *\n * // React to setting changes (hot-reload)\n * api.onSettingsChange((settings, changedKeys) => {\n * if (changedKeys.includes('backgroundColor')) {\n * document.body.style.background = settings.backgroundColor as string\n * }\n * })\n *\n * // Subscribe to system events\n * api.onEvent('viewport:resize', ({ width, height }) => {\n * canvas.width = width\n * canvas.height = height\n * })\n *\n * // Signal addon is ready\n * api.ready({\n * capabilities: ['hot-reload'],\n * subscribedEvents: ['viewport:resize']\n * })\n *\n * // After visual rendering is complete\n * await loadImages()\n * render()\n * api.renderComplete()\n * ```\n *\n * ## Manifest Validation\n *\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 * settings: {\n * color: { type: 'color', default: '#ffffff' }\n * }\n * }\n *\n * const result = validateManifest(manifest)\n * if (!result.success) {\n * console.error(result.errors)\n * }\n * ```\n *\n * @module\n */\n\n// =============================================================================\n// TYPE EXPORTS\n// =============================================================================\n\nexport type {\n // Main API\n MyWallpaperAPI,\n StorageAPI,\n NetworkAPI,\n NetworkResponse,\n\n // OAuth API\n OAuthAPI,\n OAuthResponse,\n OAuthScopesError,\n OAuthProvider,\n OAuthPermissionType,\n\n // File Access API\n FileAccessAPI,\n FileReference,\n FileAccessResult,\n\n // Events\n SystemEventType,\n SystemEventData,\n\n // Capabilities & Permissions\n AddonCapability,\n PermissionType,\n PermissionConstraints,\n\n // Callbacks\n SettingsCallback,\n EventCallback,\n ReadyOptions,\n\n // Lifecycle\n LifecycleEvent,\n\n // Utility types\n AddonValues,\n} from './types'\n\n// =============================================================================\n// MANIFEST EXPORTS\n// =============================================================================\n\nexport type {\n // Manifest types\n AddonManifest,\n SettingDefinition,\n SettingType,\n AddonDefaultLayout,\n ValidationResult,\n} from './manifest'\n\nexport {\n // Validation\n validateManifest,\n isValidManifest,\n\n // Manifest utilities\n supportsHotReload,\n getSubscribedEvents,\n getRequiredPermissions,\n} from './manifest'\n\n// =============================================================================\n// UTILITY EXPORTS\n// =============================================================================\n\nexport {\n // Config utilities\n createDefaultConfig,\n mergeConfigs,\n\n // Version utilities\n compareVersions,\n satisfiesMinVersion,\n\n // Storage utilities\n getStorageSize,\n formatBytes,\n\n // Type guards\n isPlainObject,\n isValidColor,\n\n // Function utilities\n debounce,\n throttle,\n once,\n} from './utils'\n\nexport type { VersionComparison } from './utils'\n\n// =============================================================================\n// SDK VERSION\n// =============================================================================\n\n/**\n * Current SDK version.\n */\nexport const SDK_VERSION = '2.3.0'\n\n/**\n * Minimum compatible host version.\n */\nexport const MIN_HOST_VERSION = '2.0.0'\n"]}
1
+ {"version":3,"sources":["../src/manifest.ts","../src/utils.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.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","/**\n * @mywallpaper/addon-sdk - Utility Functions\n *\n * Helper utilities for addon development.\n */\n\nimport type { AddonManifest, SettingDefinition } from './manifest'\n\n// =============================================================================\n// CONFIG UTILITIES\n// =============================================================================\n\n/**\n * Creates a default configuration object from manifest settings.\n *\n * @param manifest - Addon manifest with settings definitions\n * @returns Object with all setting keys and their default values\n *\n * @example\n * ```typescript\n * const manifest = {\n * name: 'My Addon',\n * version: '1.0.0',\n * settings: {\n * color: { type: 'color', default: '#ff0000' },\n * speed: { type: 'range', default: 1, min: 0, max: 10 }\n * }\n * }\n *\n * const defaults = createDefaultConfig(manifest)\n * // { color: '#ff0000', speed: 1 }\n * ```\n */\nexport function createDefaultConfig(manifest: AddonManifest): Record<string, unknown> {\n const config: Record<string, unknown> = {}\n\n if (!manifest.settings) return config\n\n for (const [key, setting] of Object.entries(manifest.settings)) {\n if (setting.type === 'section' || setting.type === 'button') {\n // Skip non-value settings\n continue\n }\n config[key] = setting.default ?? getTypeDefault(setting)\n }\n\n return config\n}\n\n/**\n * Get type-appropriate default value when none specified.\n */\nfunction getTypeDefault(setting: SettingDefinition): unknown {\n switch (setting.type) {\n case 'string':\n case 'textarea':\n return ''\n case 'number':\n return 0\n case 'boolean':\n return false\n case 'color':\n return '#000000'\n case 'select':\n case 'radio':\n return setting.options?.[0]?.value ?? ''\n case 'range':\n return setting.min ?? 0\n case 'image':\n return null\n case 'gradient':\n return { stops: [{ color: '#000000', position: 0 }, { color: '#ffffff', position: 100 }] }\n case 'vector2':\n return { x: 0, y: 0 }\n case 'multi-select':\n return []\n default:\n return null\n }\n}\n\n/**\n * Merges user config with defaults, ensuring all keys exist.\n *\n * @param defaults - Default configuration\n * @param userConfig - User-provided configuration (may be partial)\n * @returns Complete configuration with user values taking precedence\n *\n * @example\n * ```typescript\n * const defaults = { color: '#ff0000', speed: 1 }\n * const user = { color: '#00ff00' }\n *\n * const config = mergeConfigs(defaults, user)\n * // { color: '#00ff00', speed: 1 }\n * ```\n */\nexport function mergeConfigs(\n defaults: Record<string, unknown>,\n userConfig: Record<string, unknown>\n): Record<string, unknown> {\n return { ...defaults, ...userConfig }\n}\n\n// =============================================================================\n// VERSION UTILITIES\n// =============================================================================\n\n/**\n * Semantic version comparison result.\n */\nexport type VersionComparison = -1 | 0 | 1\n\n/**\n * Compare two semantic versions.\n *\n * @param a - First version string\n * @param b - Second version string\n * @returns -1 if a < b, 0 if a === b, 1 if a > b\n *\n * @example\n * ```typescript\n * compareVersions('1.0.0', '1.0.1') // -1\n * compareVersions('2.0.0', '1.9.9') // 1\n * compareVersions('1.0.0', '1.0.0') // 0\n * ```\n */\nexport function compareVersions(a: string, b: string): VersionComparison {\n const parseVersion = (v: string): number[] => {\n return v.split('.').map(n => parseInt(n, 10) || 0)\n }\n\n const partsA = parseVersion(a)\n const partsB = parseVersion(b)\n\n for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {\n const numA = partsA[i] ?? 0\n const numB = partsB[i] ?? 0\n\n if (numA < numB) return -1\n if (numA > numB) return 1\n }\n\n return 0\n}\n\n/**\n * Check if version satisfies a minimum requirement.\n *\n * @param version - Version to check\n * @param minimum - Minimum required version\n * @returns True if version >= minimum\n */\nexport function satisfiesMinVersion(version: string, minimum: string): boolean {\n return compareVersions(version, minimum) >= 0\n}\n\n// =============================================================================\n// STORAGE UTILITIES\n// =============================================================================\n\n/**\n * Calculate the byte size of a value when JSON serialized.\n * Useful for quota tracking.\n *\n * @param value - Value to measure\n * @returns Size in bytes\n */\nexport function getStorageSize(value: unknown): number {\n try {\n return new Blob([JSON.stringify(value)]).size\n } catch {\n return 0\n }\n}\n\n/**\n * Format bytes as human-readable string.\n *\n * @param bytes - Number of bytes\n * @returns Formatted string (e.g., \"1.5 KB\", \"2.3 MB\")\n */\nexport function formatBytes(bytes: number): string {\n if (bytes === 0) return '0 B'\n\n const units = ['B', 'KB', 'MB', 'GB']\n const k = 1024\n const i = Math.floor(Math.log(Math.abs(bytes)) / Math.log(k))\n\n return `${(bytes / Math.pow(k, i)).toFixed(i > 0 ? 1 : 0)} ${units[i]}`\n}\n\n// =============================================================================\n// TYPE GUARDS\n// =============================================================================\n\n/**\n * Check if a value is a plain object.\n */\nexport function isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * Check if a value is a valid color hex string.\n */\nexport function isValidColor(value: unknown): value is string {\n if (typeof value !== 'string') return false\n return /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/.test(value)\n}\n\n// =============================================================================\n// DEBOUNCE/THROTTLE\n// =============================================================================\n\n/**\n * Debounce a function - only call after delay with no new calls.\n *\n * @param fn - Function to debounce\n * @param delayMs - Delay in milliseconds\n * @returns Debounced function\n *\n * @example\n * ```typescript\n * const save = debounce((data) => {\n * localStorage.setItem('data', JSON.stringify(data))\n * }, 1000)\n *\n * // Rapid calls only trigger one save after 1s\n * save({ a: 1 })\n * save({ a: 2 })\n * save({ a: 3 }) // Only this one runs (after 1s)\n * ```\n */\nexport function debounce<T extends (...args: unknown[]) => void>(\n fn: T,\n delayMs: number\n): (...args: Parameters<T>) => void {\n let timeoutId: ReturnType<typeof setTimeout> | null = null\n\n return (...args: Parameters<T>) => {\n if (timeoutId) clearTimeout(timeoutId)\n timeoutId = setTimeout(() => fn(...args), delayMs)\n }\n}\n\n/**\n * Throttle a function - call at most once per interval.\n *\n * @param fn - Function to throttle\n * @param intervalMs - Minimum interval between calls\n * @returns Throttled function\n */\nexport function throttle<T extends (...args: unknown[]) => void>(\n fn: T,\n intervalMs: number\n): (...args: Parameters<T>) => void {\n let lastCall = 0\n\n return (...args: Parameters<T>) => {\n const now = Date.now()\n if (now - lastCall >= intervalMs) {\n lastCall = now\n fn(...args)\n }\n }\n}\n\n// =============================================================================\n// EVENT HELPERS\n// =============================================================================\n\n/**\n * Create a one-time event listener that auto-removes after first call.\n *\n * @param element - DOM element or window\n * @param event - Event name\n * @param handler - Event handler\n */\nexport function once(\n element: EventTarget,\n event: string,\n handler: EventListener\n): void {\n const wrappedHandler: EventListener = (e) => {\n element.removeEventListener(event, wrappedHandler)\n handler(e)\n }\n element.addEventListener(event, wrappedHandler)\n}\n","/**\n * @mywallpaper/addon-sdk\n *\n * Official SDK for building MyWallpaper addons.\n *\n * @packageDocumentation\n *\n * ## Installation\n *\n * ```bash\n * npm install @mywallpaper/addon-sdk\n * ```\n *\n * ## Quick Start\n *\n * ```typescript\n * // Your addon's main file\n * import type { MyWallpaperAPI } from '@mywallpaper/addon-sdk'\n *\n * // The API is automatically available on window.MyWallpaper\n * const api = window.MyWallpaper\n *\n * // Access configuration\n * const { backgroundColor, animationSpeed } = api.config\n *\n * // React to setting changes (hot-reload)\n * api.onSettingsChange((settings, changedKeys) => {\n * if (changedKeys.includes('backgroundColor')) {\n * document.body.style.background = settings.backgroundColor as string\n * }\n * })\n *\n * // Subscribe to system events\n * api.onEvent('viewport:resize', ({ width, height }) => {\n * canvas.width = width\n * canvas.height = height\n * })\n *\n * // Signal addon is ready\n * api.ready({\n * capabilities: ['hot-reload'],\n * subscribedEvents: ['viewport:resize']\n * })\n *\n * // After visual rendering is complete\n * await loadImages()\n * render()\n * api.renderComplete()\n * ```\n *\n * ## Manifest Validation\n *\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 * settings: {\n * color: { type: 'color', default: '#ffffff' }\n * }\n * }\n *\n * const result = validateManifest(manifest)\n * if (!result.success) {\n * console.error(result.errors)\n * }\n * ```\n *\n * @module\n */\n\n// =============================================================================\n// TYPE EXPORTS\n// =============================================================================\n\nexport type {\n // Main API\n MyWallpaperAPI,\n StorageAPI,\n NetworkAPI,\n NetworkResponse,\n\n // OAuth API\n OAuthAPI,\n OAuthResponse,\n OAuthScopesError,\n OAuthProvider,\n OAuthPermissionType,\n\n // File Access API\n FileAccessAPI,\n FileReference,\n FileAccessResult,\n\n // Events\n SystemEventType,\n SystemEventData,\n\n // Capabilities & Permissions\n AddonCapability,\n PermissionType,\n PermissionConstraints,\n\n // Callbacks\n SettingsCallback,\n EventCallback,\n ReadyOptions,\n\n // Lifecycle\n LifecycleEvent,\n\n // Utility types\n AddonValues,\n} from './types'\n\n// =============================================================================\n// MANIFEST EXPORTS\n// =============================================================================\n\nexport type {\n // Manifest types\n AddonManifest,\n SettingDefinition,\n SettingType,\n AddonDefaultLayout,\n ValidationResult,\n} from './manifest'\n\nexport {\n // Validation\n validateManifest,\n isValidManifest,\n\n // Manifest utilities\n supportsHotReload,\n getSubscribedEvents,\n getRequiredPermissions,\n} from './manifest'\n\n// =============================================================================\n// UTILITY EXPORTS\n// =============================================================================\n\nexport {\n // Config utilities\n createDefaultConfig,\n mergeConfigs,\n\n // Version utilities\n compareVersions,\n satisfiesMinVersion,\n\n // Storage utilities\n getStorageSize,\n formatBytes,\n\n // Type guards\n isPlainObject,\n isValidColor,\n\n // Function utilities\n debounce,\n throttle,\n once,\n} from './utils'\n\nexport type { VersionComparison } from './utils'\n\n// =============================================================================\n// SDK VERSION\n// =============================================================================\n\n/**\n * Current SDK version.\n */\nexport const SDK_VERSION = '2.3.0'\n\n/**\n * Minimum compatible host version.\n */\nexport const MIN_HOST_VERSION = '2.0.0'\n"]}