@mywallpaper/addon-sdk 2.5.0 → 2.6.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/dist/index.d.mts CHANGED
@@ -541,6 +541,23 @@ interface MyWallpaperAPI {
541
541
  * OAuth API for making authenticated requests to provider APIs.
542
542
  */
543
543
  oauth: OAuthAPI;
544
+ /**
545
+ * File Access API for requesting access to user-uploaded files.
546
+ * Files are not sent automatically - the addon must request access.
547
+ * A modal is shown to the user EVERY TIME (not persisted).
548
+ *
549
+ * @example
550
+ * ```typescript
551
+ * const videoRef = MyWallpaper.config.backgroundVideo
552
+ * if (MyWallpaper.files.isFileReference(videoRef)) {
553
+ * const result = await MyWallpaper.files.request('backgroundVideo')
554
+ * if (result.granted) {
555
+ * video.src = result.blobUrl!
556
+ * }
557
+ * }
558
+ * ```
559
+ */
560
+ files: FileAccessAPI;
544
561
  /**
545
562
  * Utility functions for common addon tasks.
546
563
  */
@@ -571,6 +588,97 @@ declare global {
571
588
  * Lifecycle event names.
572
589
  */
573
590
  type LifecycleEvent = 'mount' | 'unmount' | 'pause' | 'resume';
591
+ /**
592
+ * File info returned when settings contain a file reference.
593
+ * The addon receives this placeholder instead of the actual file data.
594
+ */
595
+ interface FileReference {
596
+ /** Type marker for file references */
597
+ __type: 'file-reference';
598
+ /** The setting key this file belongs to */
599
+ settingKey: string;
600
+ /** Original filename if available */
601
+ filename?: string;
602
+ /** MIME type of the file */
603
+ mimeType: string;
604
+ /** File size in bytes */
605
+ size: number;
606
+ }
607
+ /**
608
+ * Result of a file access request.
609
+ */
610
+ interface FileAccessResult {
611
+ /** Whether access was granted */
612
+ granted: boolean;
613
+ /** Blob URL if granted (valid only in addon context) */
614
+ blobUrl?: string;
615
+ /** Error message if denied */
616
+ error?: string;
617
+ }
618
+ /**
619
+ * File Access API for requesting access to user-uploaded files.
620
+ * Access via `window.MyWallpaper.files`
621
+ *
622
+ * **How it works:**
623
+ * 1. User uploads a file in the addon settings (video, image, audio)
624
+ * 2. Addon receives a FileReference placeholder (not the actual file)
625
+ * 3. Addon calls `files.request(settingKey)` to request access
626
+ * 4. Host shows a permission modal: "Widget X requests access to: filename.mp4"
627
+ * 5. If user approves, addon receives a blob URL it can use
628
+ * 6. This modal is shown EVERY TIME (not persisted)
629
+ *
630
+ * @example
631
+ * ```typescript
632
+ * const api = window.MyWallpaper
633
+ *
634
+ * // Check if a setting is a file reference
635
+ * const videoRef = api.config.backgroundVideo
636
+ * if (api.files.isFileReference(videoRef)) {
637
+ * // Request access to the file
638
+ * const result = await api.files.request('backgroundVideo')
639
+ * if (result.granted && result.blobUrl) {
640
+ * videoElement.src = result.blobUrl
641
+ * videoElement.play()
642
+ * }
643
+ * }
644
+ * ```
645
+ */
646
+ interface FileAccessAPI {
647
+ /**
648
+ * Check if a value is a file reference placeholder.
649
+ * File references are sent instead of actual file data until access is granted.
650
+ *
651
+ * @param value - Any value from config
652
+ * @returns true if this is a FileReference that requires access request
653
+ */
654
+ isFileReference(value: unknown): value is FileReference;
655
+ /**
656
+ * Request access to a file from the user.
657
+ * Shows a modal to the user asking for permission.
658
+ * This is asked EVERY TIME - permissions are not persisted.
659
+ *
660
+ * @param settingKey - The setting key containing the file reference
661
+ * @returns Promise resolving to FileAccessResult
662
+ *
663
+ * @example
664
+ * ```typescript
665
+ * const result = await api.files.request('backgroundVideo')
666
+ * if (result.granted) {
667
+ * video.src = result.blobUrl!
668
+ * } else {
669
+ * console.log('Access denied:', result.error)
670
+ * }
671
+ * ```
672
+ */
673
+ request(settingKey: string): Promise<FileAccessResult>;
674
+ /**
675
+ * Revoke access to a previously granted file.
676
+ * The blob URL will be invalidated.
677
+ *
678
+ * @param settingKey - The setting key to revoke access for
679
+ */
680
+ revoke(settingKey: string): void;
681
+ }
574
682
  /**
575
683
  * Generic addon values (settings configuration).
576
684
  */
@@ -1104,4 +1212,4 @@ declare const SDK_VERSION = "2.3.0";
1104
1212
  */
1105
1213
  declare const MIN_HOST_VERSION = "2.0.0";
1106
1214
 
1107
- export { type AddonCapability, type AddonDefaultLayout, type AddonManifest, type AddonValues, type EventCallback, type LifecycleEvent, MIN_HOST_VERSION, type MyWallpaperAPI, type NetworkAPI, type NetworkResponse, type OAuthAPI, type OAuthPermissionType, type OAuthProvider, type OAuthResponse, type OAuthScopesError, type PermissionConstraints, type PermissionType, type ReadyOptions, SDK_VERSION, type SettingDefinition, type SettingType, type SettingsCallback, type StorageAPI, type SystemEventData, type SystemEventType, type ValidationResult, type VersionComparison, compareVersions, createDefaultConfig, debounce, formatBytes, getRequiredPermissions, getStorageSize, getSubscribedEvents, isPlainObject, isValidColor, isValidManifest, mergeConfigs, once, satisfiesMinVersion, supportsHotReload, throttle, validateManifest };
1215
+ export { type AddonCapability, type AddonDefaultLayout, type AddonManifest, type AddonValues, type EventCallback, type FileAccessAPI, type FileAccessResult, type FileReference, type LifecycleEvent, MIN_HOST_VERSION, type MyWallpaperAPI, type NetworkAPI, type NetworkResponse, type OAuthAPI, type OAuthPermissionType, type OAuthProvider, type OAuthResponse, type OAuthScopesError, type PermissionConstraints, type PermissionType, type ReadyOptions, SDK_VERSION, type SettingDefinition, type SettingType, type SettingsCallback, type StorageAPI, type SystemEventData, type SystemEventType, type ValidationResult, type VersionComparison, compareVersions, createDefaultConfig, debounce, formatBytes, getRequiredPermissions, getStorageSize, getSubscribedEvents, isPlainObject, isValidColor, isValidManifest, mergeConfigs, once, satisfiesMinVersion, supportsHotReload, throttle, validateManifest };
package/dist/index.d.ts CHANGED
@@ -541,6 +541,23 @@ interface MyWallpaperAPI {
541
541
  * OAuth API for making authenticated requests to provider APIs.
542
542
  */
543
543
  oauth: OAuthAPI;
544
+ /**
545
+ * File Access API for requesting access to user-uploaded files.
546
+ * Files are not sent automatically - the addon must request access.
547
+ * A modal is shown to the user EVERY TIME (not persisted).
548
+ *
549
+ * @example
550
+ * ```typescript
551
+ * const videoRef = MyWallpaper.config.backgroundVideo
552
+ * if (MyWallpaper.files.isFileReference(videoRef)) {
553
+ * const result = await MyWallpaper.files.request('backgroundVideo')
554
+ * if (result.granted) {
555
+ * video.src = result.blobUrl!
556
+ * }
557
+ * }
558
+ * ```
559
+ */
560
+ files: FileAccessAPI;
544
561
  /**
545
562
  * Utility functions for common addon tasks.
546
563
  */
@@ -571,6 +588,97 @@ declare global {
571
588
  * Lifecycle event names.
572
589
  */
573
590
  type LifecycleEvent = 'mount' | 'unmount' | 'pause' | 'resume';
591
+ /**
592
+ * File info returned when settings contain a file reference.
593
+ * The addon receives this placeholder instead of the actual file data.
594
+ */
595
+ interface FileReference {
596
+ /** Type marker for file references */
597
+ __type: 'file-reference';
598
+ /** The setting key this file belongs to */
599
+ settingKey: string;
600
+ /** Original filename if available */
601
+ filename?: string;
602
+ /** MIME type of the file */
603
+ mimeType: string;
604
+ /** File size in bytes */
605
+ size: number;
606
+ }
607
+ /**
608
+ * Result of a file access request.
609
+ */
610
+ interface FileAccessResult {
611
+ /** Whether access was granted */
612
+ granted: boolean;
613
+ /** Blob URL if granted (valid only in addon context) */
614
+ blobUrl?: string;
615
+ /** Error message if denied */
616
+ error?: string;
617
+ }
618
+ /**
619
+ * File Access API for requesting access to user-uploaded files.
620
+ * Access via `window.MyWallpaper.files`
621
+ *
622
+ * **How it works:**
623
+ * 1. User uploads a file in the addon settings (video, image, audio)
624
+ * 2. Addon receives a FileReference placeholder (not the actual file)
625
+ * 3. Addon calls `files.request(settingKey)` to request access
626
+ * 4. Host shows a permission modal: "Widget X requests access to: filename.mp4"
627
+ * 5. If user approves, addon receives a blob URL it can use
628
+ * 6. This modal is shown EVERY TIME (not persisted)
629
+ *
630
+ * @example
631
+ * ```typescript
632
+ * const api = window.MyWallpaper
633
+ *
634
+ * // Check if a setting is a file reference
635
+ * const videoRef = api.config.backgroundVideo
636
+ * if (api.files.isFileReference(videoRef)) {
637
+ * // Request access to the file
638
+ * const result = await api.files.request('backgroundVideo')
639
+ * if (result.granted && result.blobUrl) {
640
+ * videoElement.src = result.blobUrl
641
+ * videoElement.play()
642
+ * }
643
+ * }
644
+ * ```
645
+ */
646
+ interface FileAccessAPI {
647
+ /**
648
+ * Check if a value is a file reference placeholder.
649
+ * File references are sent instead of actual file data until access is granted.
650
+ *
651
+ * @param value - Any value from config
652
+ * @returns true if this is a FileReference that requires access request
653
+ */
654
+ isFileReference(value: unknown): value is FileReference;
655
+ /**
656
+ * Request access to a file from the user.
657
+ * Shows a modal to the user asking for permission.
658
+ * This is asked EVERY TIME - permissions are not persisted.
659
+ *
660
+ * @param settingKey - The setting key containing the file reference
661
+ * @returns Promise resolving to FileAccessResult
662
+ *
663
+ * @example
664
+ * ```typescript
665
+ * const result = await api.files.request('backgroundVideo')
666
+ * if (result.granted) {
667
+ * video.src = result.blobUrl!
668
+ * } else {
669
+ * console.log('Access denied:', result.error)
670
+ * }
671
+ * ```
672
+ */
673
+ request(settingKey: string): Promise<FileAccessResult>;
674
+ /**
675
+ * Revoke access to a previously granted file.
676
+ * The blob URL will be invalidated.
677
+ *
678
+ * @param settingKey - The setting key to revoke access for
679
+ */
680
+ revoke(settingKey: string): void;
681
+ }
574
682
  /**
575
683
  * Generic addon values (settings configuration).
576
684
  */
@@ -1104,4 +1212,4 @@ declare const SDK_VERSION = "2.3.0";
1104
1212
  */
1105
1213
  declare const MIN_HOST_VERSION = "2.0.0";
1106
1214
 
1107
- export { type AddonCapability, type AddonDefaultLayout, type AddonManifest, type AddonValues, type EventCallback, type LifecycleEvent, MIN_HOST_VERSION, type MyWallpaperAPI, type NetworkAPI, type NetworkResponse, type OAuthAPI, type OAuthPermissionType, type OAuthProvider, type OAuthResponse, type OAuthScopesError, type PermissionConstraints, type PermissionType, type ReadyOptions, SDK_VERSION, type SettingDefinition, type SettingType, type SettingsCallback, type StorageAPI, type SystemEventData, type SystemEventType, type ValidationResult, type VersionComparison, compareVersions, createDefaultConfig, debounce, formatBytes, getRequiredPermissions, getStorageSize, getSubscribedEvents, isPlainObject, isValidColor, isValidManifest, mergeConfigs, once, satisfiesMinVersion, supportsHotReload, throttle, validateManifest };
1215
+ export { type AddonCapability, type AddonDefaultLayout, type AddonManifest, type AddonValues, type EventCallback, type FileAccessAPI, type FileAccessResult, type FileReference, type LifecycleEvent, MIN_HOST_VERSION, type MyWallpaperAPI, type NetworkAPI, type NetworkResponse, type OAuthAPI, type OAuthPermissionType, type OAuthProvider, type OAuthResponse, type OAuthScopesError, type PermissionConstraints, type PermissionType, type ReadyOptions, SDK_VERSION, type SettingDefinition, type SettingType, type SettingsCallback, type StorageAPI, type SystemEventData, type SystemEventType, type ValidationResult, type VersionComparison, compareVersions, createDefaultConfig, debounce, formatBytes, getRequiredPermissions, getStorageSize, getSubscribedEvents, isPlainObject, isValidColor, isValidManifest, mergeConfigs, once, satisfiesMinVersion, supportsHotReload, throttle, validateManifest };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/manifest.ts","../src/utils.ts","../src/index.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;;;ACliBO,SAAS,oBAAoB,QAAA,EAAkD;AACpF,EAAA,MAAM,SAAkC,EAAC;AAEzC,EAAA,IAAI,CAAC,QAAA,CAAS,QAAA,EAAU,OAAO,MAAA;AAE/B,EAAA,KAAA,MAAW,CAAC,KAAK,OAAO,CAAA,IAAK,OAAO,OAAA,CAAQ,QAAA,CAAS,QAAQ,CAAA,EAAG;AAC9D,IAAA,IAAI,OAAA,CAAQ,IAAA,KAAS,SAAA,IAAa,OAAA,CAAQ,SAAS,QAAA,EAAU;AAE3D,MAAA;AAAA,IACF;AACA,IAAA,MAAA,CAAO,GAAG,CAAA,GAAI,OAAA,CAAQ,OAAA,IAAW,eAAe,OAAO,CAAA;AAAA,EACzD;AAEA,EAAA,OAAO,MAAA;AACT;AAKA,SAAS,eAAe,OAAA,EAAqC;AAC3D,EAAA,QAAQ,QAAQ,IAAA;AAAM,IACpB,KAAK,QAAA;AAAA,IACL,KAAK,UAAA;AACH,MAAA,OAAO,EAAA;AAAA,IACT,KAAK,QAAA;AACH,MAAA,OAAO,CAAA;AAAA,IACT,KAAK,SAAA;AACH,MAAA,OAAO,KAAA;AAAA,IACT,KAAK,OAAA;AACH,MAAA,OAAO,SAAA;AAAA,IACT,KAAK,QAAA;AAAA,IACL,KAAK,OAAA;AACH,MAAA,OAAO,OAAA,CAAQ,OAAA,GAAU,CAAC,CAAA,EAAG,KAAA,IAAS,EAAA;AAAA,IACxC,KAAK,OAAA;AACH,MAAA,OAAO,QAAQ,GAAA,IAAO,CAAA;AAAA,IACxB,KAAK,OAAA;AACH,MAAA,OAAO,IAAA;AAAA,IACT,KAAK,UAAA;AACH,MAAA,OAAO,EAAE,KAAA,EAAO,CAAC,EAAE,OAAO,SAAA,EAAW,QAAA,EAAU,CAAA,EAAE,EAAG,EAAE,KAAA,EAAO,SAAA,EAAW,QAAA,EAAU,GAAA,EAAK,CAAA,EAAE;AAAA,IAC3F,KAAK,SAAA;AACH,MAAA,OAAO,EAAE,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA,EAAE;AAAA,IACtB,KAAK,cAAA;AACH,MAAA,OAAO,EAAC;AAAA,IACV;AACE,MAAA,OAAO,IAAA;AAAA;AAEb;AAkBO,SAAS,YAAA,CACd,UACA,UAAA,EACyB;AACzB,EAAA,OAAO,EAAE,GAAG,QAAA,EAAU,GAAG,UAAA,EAAW;AACtC;AAyBO,SAAS,eAAA,CAAgB,GAAW,CAAA,EAA8B;AACvE,EAAA,MAAM,YAAA,GAAe,CAAC,CAAA,KAAwB;AAC5C,IAAA,OAAO,CAAA,CAAE,KAAA,CAAM,GAAG,CAAA,CAAE,GAAA,CAAI,OAAK,QAAA,CAAS,CAAA,EAAG,EAAE,CAAA,IAAK,CAAC,CAAA;AAAA,EACnD,CAAA;AAEA,EAAA,MAAM,MAAA,GAAS,aAAa,CAAC,CAAA;AAC7B,EAAA,MAAM,MAAA,GAAS,aAAa,CAAC,CAAA;AAE7B,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,OAAO,MAAA,EAAQ,MAAA,CAAO,MAAM,CAAA,EAAG,CAAA,EAAA,EAAK;AAC/D,IAAA,MAAM,IAAA,GAAO,MAAA,CAAO,CAAC,CAAA,IAAK,CAAA;AAC1B,IAAA,MAAM,IAAA,GAAO,MAAA,CAAO,CAAC,CAAA,IAAK,CAAA;AAE1B,IAAA,IAAI,IAAA,GAAO,MAAM,OAAO,EAAA;AACxB,IAAA,IAAI,IAAA,GAAO,MAAM,OAAO,CAAA;AAAA,EAC1B;AAEA,EAAA,OAAO,CAAA;AACT;AASO,SAAS,mBAAA,CAAoB,SAAiB,OAAA,EAA0B;AAC7E,EAAA,OAAO,eAAA,CAAgB,OAAA,EAAS,OAAO,CAAA,IAAK,CAAA;AAC9C;AAaO,SAAS,eAAe,KAAA,EAAwB;AACrD,EAAA,IAAI;AACF,IAAA,OAAO,IAAI,KAAK,CAAC,IAAA,CAAK,UAAU,KAAK,CAAC,CAAC,CAAA,CAAE,IAAA;AAAA,EAC3C,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,CAAA;AAAA,EACT;AACF;AAQO,SAAS,YAAY,KAAA,EAAuB;AACjD,EAAA,IAAI,KAAA,KAAU,GAAG,OAAO,KAAA;AAExB,EAAA,MAAM,KAAA,GAAQ,CAAC,GAAA,EAAK,IAAA,EAAM,MAAM,IAAI,CAAA;AACpC,EAAA,MAAM,CAAA,GAAI,IAAA;AACV,EAAA,MAAM,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,GAAA,CAAI,KAAK,CAAC,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,CAAC,CAAC,CAAA;AAE5D,EAAA,OAAO,IAAI,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,CAAC,CAAA,EAAG,OAAA,CAAQ,CAAA,GAAI,CAAA,GAAI,IAAI,CAAC,CAAC,CAAA,CAAA,EAAI,KAAA,CAAM,CAAC,CAAC,CAAA,CAAA;AACvE;AASO,SAAS,cAAc,KAAA,EAAkD;AAC9E,EAAA,OAAO,OAAO,UAAU,QAAA,IAAY,KAAA,KAAU,QAAQ,CAAC,KAAA,CAAM,QAAQ,KAAK,CAAA;AAC5E;AAKO,SAAS,aAAa,KAAA,EAAiC;AAC5D,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,EAAU,OAAO,KAAA;AACtC,EAAA,OAAO,mDAAA,CAAoD,KAAK,KAAK,CAAA;AACvE;AAyBO,SAAS,QAAA,CACd,IACA,OAAA,EACkC;AAClC,EAAA,IAAI,SAAA,GAAkD,IAAA;AAEtD,EAAA,OAAO,IAAI,IAAA,KAAwB;AACjC,IAAA,IAAI,SAAA,eAAwB,SAAS,CAAA;AACrC,IAAA,SAAA,GAAY,WAAW,MAAM,EAAA,CAAG,GAAG,IAAI,GAAG,OAAO,CAAA;AAAA,EACnD,CAAA;AACF;AASO,SAAS,QAAA,CACd,IACA,UAAA,EACkC;AAClC,EAAA,IAAI,QAAA,GAAW,CAAA;AAEf,EAAA,OAAO,IAAI,IAAA,KAAwB;AACjC,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,IAAA,IAAI,GAAA,GAAM,YAAY,UAAA,EAAY;AAChC,MAAA,QAAA,GAAW,GAAA;AACX,MAAA,EAAA,CAAG,GAAG,IAAI,CAAA;AAAA,IACZ;AAAA,EACF,CAAA;AACF;AAaO,SAAS,IAAA,CACd,OAAA,EACA,KAAA,EACA,OAAA,EACM;AACN,EAAA,MAAM,cAAA,GAAgC,CAAC,CAAA,KAAM;AAC3C,IAAA,OAAA,CAAQ,mBAAA,CAAoB,OAAO,cAAc,CAAA;AACjD,IAAA,OAAA,CAAQ,CAAC,CAAA;AAAA,EACX,CAAA;AACA,EAAA,OAAA,CAAQ,gBAAA,CAAiB,OAAO,cAAc,CAAA;AAChD;;;ACtHO,IAAM,WAAA,GAAc;AAKpB,IAAM,gBAAA,GAAmB","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 // 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.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 +1 @@
1
- {"version":3,"sources":["../src/manifest.ts","../src/utils.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.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","/**\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 // 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.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","/**\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"]}
@@ -526,6 +526,23 @@ interface MyWallpaperAPI {
526
526
  * OAuth API for making authenticated requests to provider APIs.
527
527
  */
528
528
  oauth: OAuthAPI;
529
+ /**
530
+ * File Access API for requesting access to user-uploaded files.
531
+ * Files are not sent automatically - the addon must request access.
532
+ * A modal is shown to the user EVERY TIME (not persisted).
533
+ *
534
+ * @example
535
+ * ```typescript
536
+ * const videoRef = MyWallpaper.config.backgroundVideo
537
+ * if (MyWallpaper.files.isFileReference(videoRef)) {
538
+ * const result = await MyWallpaper.files.request('backgroundVideo')
539
+ * if (result.granted) {
540
+ * video.src = result.blobUrl!
541
+ * }
542
+ * }
543
+ * ```
544
+ */
545
+ files: FileAccessAPI;
529
546
  /**
530
547
  * Utility functions for common addon tasks.
531
548
  */
@@ -552,6 +569,97 @@ declare global {
552
569
  MyWallpaper: MyWallpaperAPI;
553
570
  }
554
571
  }
572
+ /**
573
+ * File info returned when settings contain a file reference.
574
+ * The addon receives this placeholder instead of the actual file data.
575
+ */
576
+ interface FileReference {
577
+ /** Type marker for file references */
578
+ __type: 'file-reference';
579
+ /** The setting key this file belongs to */
580
+ settingKey: string;
581
+ /** Original filename if available */
582
+ filename?: string;
583
+ /** MIME type of the file */
584
+ mimeType: string;
585
+ /** File size in bytes */
586
+ size: number;
587
+ }
588
+ /**
589
+ * Result of a file access request.
590
+ */
591
+ interface FileAccessResult {
592
+ /** Whether access was granted */
593
+ granted: boolean;
594
+ /** Blob URL if granted (valid only in addon context) */
595
+ blobUrl?: string;
596
+ /** Error message if denied */
597
+ error?: string;
598
+ }
599
+ /**
600
+ * File Access API for requesting access to user-uploaded files.
601
+ * Access via `window.MyWallpaper.files`
602
+ *
603
+ * **How it works:**
604
+ * 1. User uploads a file in the addon settings (video, image, audio)
605
+ * 2. Addon receives a FileReference placeholder (not the actual file)
606
+ * 3. Addon calls `files.request(settingKey)` to request access
607
+ * 4. Host shows a permission modal: "Widget X requests access to: filename.mp4"
608
+ * 5. If user approves, addon receives a blob URL it can use
609
+ * 6. This modal is shown EVERY TIME (not persisted)
610
+ *
611
+ * @example
612
+ * ```typescript
613
+ * const api = window.MyWallpaper
614
+ *
615
+ * // Check if a setting is a file reference
616
+ * const videoRef = api.config.backgroundVideo
617
+ * if (api.files.isFileReference(videoRef)) {
618
+ * // Request access to the file
619
+ * const result = await api.files.request('backgroundVideo')
620
+ * if (result.granted && result.blobUrl) {
621
+ * videoElement.src = result.blobUrl
622
+ * videoElement.play()
623
+ * }
624
+ * }
625
+ * ```
626
+ */
627
+ interface FileAccessAPI {
628
+ /**
629
+ * Check if a value is a file reference placeholder.
630
+ * File references are sent instead of actual file data until access is granted.
631
+ *
632
+ * @param value - Any value from config
633
+ * @returns true if this is a FileReference that requires access request
634
+ */
635
+ isFileReference(value: unknown): value is FileReference;
636
+ /**
637
+ * Request access to a file from the user.
638
+ * Shows a modal to the user asking for permission.
639
+ * This is asked EVERY TIME - permissions are not persisted.
640
+ *
641
+ * @param settingKey - The setting key containing the file reference
642
+ * @returns Promise resolving to FileAccessResult
643
+ *
644
+ * @example
645
+ * ```typescript
646
+ * const result = await api.files.request('backgroundVideo')
647
+ * if (result.granted) {
648
+ * video.src = result.blobUrl!
649
+ * } else {
650
+ * console.log('Access denied:', result.error)
651
+ * }
652
+ * ```
653
+ */
654
+ request(settingKey: string): Promise<FileAccessResult>;
655
+ /**
656
+ * Revoke access to a previously granted file.
657
+ * The blob URL will be invalidated.
658
+ *
659
+ * @param settingKey - The setting key to revoke access for
660
+ */
661
+ revoke(settingKey: string): void;
662
+ }
555
663
 
556
664
  /**
557
665
  * @mywallpaper/addon-sdk - Manifest Schema & Validation
@@ -526,6 +526,23 @@ interface MyWallpaperAPI {
526
526
  * OAuth API for making authenticated requests to provider APIs.
527
527
  */
528
528
  oauth: OAuthAPI;
529
+ /**
530
+ * File Access API for requesting access to user-uploaded files.
531
+ * Files are not sent automatically - the addon must request access.
532
+ * A modal is shown to the user EVERY TIME (not persisted).
533
+ *
534
+ * @example
535
+ * ```typescript
536
+ * const videoRef = MyWallpaper.config.backgroundVideo
537
+ * if (MyWallpaper.files.isFileReference(videoRef)) {
538
+ * const result = await MyWallpaper.files.request('backgroundVideo')
539
+ * if (result.granted) {
540
+ * video.src = result.blobUrl!
541
+ * }
542
+ * }
543
+ * ```
544
+ */
545
+ files: FileAccessAPI;
529
546
  /**
530
547
  * Utility functions for common addon tasks.
531
548
  */
@@ -552,6 +569,97 @@ declare global {
552
569
  MyWallpaper: MyWallpaperAPI;
553
570
  }
554
571
  }
572
+ /**
573
+ * File info returned when settings contain a file reference.
574
+ * The addon receives this placeholder instead of the actual file data.
575
+ */
576
+ interface FileReference {
577
+ /** Type marker for file references */
578
+ __type: 'file-reference';
579
+ /** The setting key this file belongs to */
580
+ settingKey: string;
581
+ /** Original filename if available */
582
+ filename?: string;
583
+ /** MIME type of the file */
584
+ mimeType: string;
585
+ /** File size in bytes */
586
+ size: number;
587
+ }
588
+ /**
589
+ * Result of a file access request.
590
+ */
591
+ interface FileAccessResult {
592
+ /** Whether access was granted */
593
+ granted: boolean;
594
+ /** Blob URL if granted (valid only in addon context) */
595
+ blobUrl?: string;
596
+ /** Error message if denied */
597
+ error?: string;
598
+ }
599
+ /**
600
+ * File Access API for requesting access to user-uploaded files.
601
+ * Access via `window.MyWallpaper.files`
602
+ *
603
+ * **How it works:**
604
+ * 1. User uploads a file in the addon settings (video, image, audio)
605
+ * 2. Addon receives a FileReference placeholder (not the actual file)
606
+ * 3. Addon calls `files.request(settingKey)` to request access
607
+ * 4. Host shows a permission modal: "Widget X requests access to: filename.mp4"
608
+ * 5. If user approves, addon receives a blob URL it can use
609
+ * 6. This modal is shown EVERY TIME (not persisted)
610
+ *
611
+ * @example
612
+ * ```typescript
613
+ * const api = window.MyWallpaper
614
+ *
615
+ * // Check if a setting is a file reference
616
+ * const videoRef = api.config.backgroundVideo
617
+ * if (api.files.isFileReference(videoRef)) {
618
+ * // Request access to the file
619
+ * const result = await api.files.request('backgroundVideo')
620
+ * if (result.granted && result.blobUrl) {
621
+ * videoElement.src = result.blobUrl
622
+ * videoElement.play()
623
+ * }
624
+ * }
625
+ * ```
626
+ */
627
+ interface FileAccessAPI {
628
+ /**
629
+ * Check if a value is a file reference placeholder.
630
+ * File references are sent instead of actual file data until access is granted.
631
+ *
632
+ * @param value - Any value from config
633
+ * @returns true if this is a FileReference that requires access request
634
+ */
635
+ isFileReference(value: unknown): value is FileReference;
636
+ /**
637
+ * Request access to a file from the user.
638
+ * Shows a modal to the user asking for permission.
639
+ * This is asked EVERY TIME - permissions are not persisted.
640
+ *
641
+ * @param settingKey - The setting key containing the file reference
642
+ * @returns Promise resolving to FileAccessResult
643
+ *
644
+ * @example
645
+ * ```typescript
646
+ * const result = await api.files.request('backgroundVideo')
647
+ * if (result.granted) {
648
+ * video.src = result.blobUrl!
649
+ * } else {
650
+ * console.log('Access denied:', result.error)
651
+ * }
652
+ * ```
653
+ */
654
+ request(settingKey: string): Promise<FileAccessResult>;
655
+ /**
656
+ * Revoke access to a previously granted file.
657
+ * The blob URL will be invalidated.
658
+ *
659
+ * @param settingKey - The setting key to revoke access for
660
+ */
661
+ revoke(settingKey: string): void;
662
+ }
555
663
 
556
664
  /**
557
665
  * @mywallpaper/addon-sdk - Manifest Schema & Validation
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mywallpaper/addon-sdk",
3
- "version": "2.5.0",
3
+ "version": "2.6.1",
4
4
  "description": "SDK for building MyWallpaper addons - TypeScript types, manifest validation, and utilities",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -64,4 +64,4 @@
64
64
  "engines": {
65
65
  "node": ">=18.0.0"
66
66
  }
67
- }
67
+ }