@mywallpaper/addon-sdk 2.8.1 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -151,6 +151,15 @@ interface NetworkResponse {
151
151
  /** Response data (auto-parsed JSON, text, or base64 for binary) */
152
152
  data: unknown;
153
153
  }
154
+ /**
155
+ * Result of a network domain access request.
156
+ */
157
+ interface NetworkAccessResult {
158
+ /** Whether access was granted */
159
+ granted: boolean;
160
+ /** Error message if denied */
161
+ error?: string;
162
+ }
154
163
  /**
155
164
  * Network API for making HTTP requests through the secure host proxy.
156
165
  * Access via `window.MyWallpaper.network`
@@ -158,7 +167,9 @@ interface NetworkResponse {
158
167
  * **IMPORTANT:** Direct `fetch()` and `XMLHttpRequest` are blocked for security.
159
168
  * All network requests MUST go through this API.
160
169
  *
161
- * **IMPORTANT:** Domains must be declared in your manifest.json:
170
+ * **Two ways to access external domains:**
171
+ *
172
+ * 1. **Manifest declaration** (pre-approved):
162
173
  * ```json
163
174
  * {
164
175
  * "permissions": {
@@ -167,14 +178,25 @@ interface NetworkResponse {
167
178
  * }
168
179
  * ```
169
180
  *
181
+ * 2. **On-demand permission** (user approval at runtime):
182
+ * ```typescript
183
+ * const result = await network.requestAccess('fonts.example.com', 'Load custom fonts')
184
+ * if (result.granted) {
185
+ * const response = await network.fetch('https://fonts.example.com/myfont.woff2')
186
+ * }
187
+ * ```
188
+ *
170
189
  * @example
171
190
  * ```typescript
172
191
  * const { network } = window.MyWallpaper
173
192
  *
174
- * // Simple GET request
175
- * const response = await network.fetch('https://api.weather.com/current')
176
- * if (response.ok) {
177
- * console.log(response.data)
193
+ * // On-demand access to a domain (shows permission modal)
194
+ * const access = await network.requestAccess('api.weather.com', 'Fetch weather data')
195
+ * if (access.granted) {
196
+ * const response = await network.fetch('https://api.weather.com/current')
197
+ * if (response.ok) {
198
+ * console.log(response.data)
199
+ * }
178
200
  * }
179
201
  *
180
202
  * // POST request with JSON body
@@ -186,9 +208,33 @@ interface NetworkResponse {
186
208
  * ```
187
209
  */
188
210
  interface NetworkAPI {
211
+ /**
212
+ * Request on-demand access to a domain.
213
+ * Shows a permission modal to the user: "Widget X requests access to: domain.com"
214
+ * This permission lasts for the current session only (not persisted).
215
+ *
216
+ * @param domain - The domain to request access to (e.g., 'fonts.cdnfonts.com')
217
+ * @param reason - User-friendly explanation of why access is needed
218
+ * @returns Promise resolving to NetworkAccessResult
219
+ *
220
+ * @example
221
+ * ```typescript
222
+ * const result = await api.network.requestAccess(
223
+ * 'fonts.cdnfonts.com',
224
+ * 'Load custom font for text display'
225
+ * )
226
+ * if (result.granted) {
227
+ * // Now we can fetch from this domain
228
+ * const css = await api.network.fetch('https://fonts.cdnfonts.com/css/anurati')
229
+ * }
230
+ * ```
231
+ */
232
+ requestAccess(domain: string, reason: string): Promise<NetworkAccessResult>;
189
233
  /**
190
234
  * Make an HTTP request through the secure host proxy.
191
- * Domain must be whitelisted in manifest.json permissions.
235
+ * Domain must be either:
236
+ * - Whitelisted in manifest.json permissions, OR
237
+ * - Approved via requestAccess() in the current session
192
238
  *
193
239
  * @param url - Full URL to fetch (must be in allowed domains)
194
240
  * @param options - Optional request options
@@ -557,6 +603,21 @@ interface MyWallpaperAPI {
557
603
  * ```
558
604
  */
559
605
  audio: AudioAPI;
606
+ /**
607
+ * Settings API for dynamically modifying setting options at runtime.
608
+ * Allows widgets to populate dropdowns based on external data.
609
+ *
610
+ * @example
611
+ * ```typescript
612
+ * // After loading font CSS, update the font family dropdown
613
+ * const fonts = parseFontFaces(cssContent)
614
+ * api.settings.updateOptions('fontFamily', fonts.map(f => ({
615
+ * label: f.family,
616
+ * value: f.family
617
+ * })))
618
+ * ```
619
+ */
620
+ settings: SettingsAPI;
560
621
  /**
561
622
  * File Access API for requesting access to user-uploaded files.
562
623
  * Files are not sent automatically - the addon must request access.
@@ -824,6 +885,86 @@ interface AudioAPI {
824
885
  */
825
886
  onStateChange(callback: (state: AudioState) => void): () => void;
826
887
  }
888
+ /**
889
+ * A setting option for select/dropdown fields.
890
+ */
891
+ interface SettingOption {
892
+ /** Display label shown to user */
893
+ label: string;
894
+ /** Value stored when selected */
895
+ value: string | number | boolean;
896
+ /** Optional description */
897
+ description?: string;
898
+ }
899
+ /**
900
+ * Settings API for dynamically modifying setting options at runtime.
901
+ * Access via `window.MyWallpaper.settings`
902
+ *
903
+ * **Why this exists:**
904
+ * Some widgets need to populate setting options dynamically based on
905
+ * external data (e.g., font families from a CSS file, available themes
906
+ * from an API, etc.). This API allows widgets to modify their own
907
+ * setting options after loading.
908
+ *
909
+ * **Important:**
910
+ * - Only works for settings declared in manifest.json
911
+ * - Only 'select' type settings can have their options updated
912
+ * - Changes are session-only (manifest defines the defaults)
913
+ *
914
+ * @example
915
+ * ```typescript
916
+ * const api = window.MyWallpaper
917
+ *
918
+ * // After fetching a font CSS, parse available fonts and update dropdown
919
+ * const fonts = parseFontFaces(cssContent)
920
+ * api.settings.updateOptions('fontFamily', fonts.map(f => ({
921
+ * label: f.family,
922
+ * value: f.family
923
+ * })))
924
+ *
925
+ * // Update multiple settings at once
926
+ * api.settings.updateOptions('fontWeight', [
927
+ * { label: 'Light', value: '300' },
928
+ * { label: 'Regular', value: '400' },
929
+ * { label: 'Bold', value: '700' }
930
+ * ])
931
+ * ```
932
+ */
933
+ interface SettingsAPI {
934
+ /**
935
+ * Update the options for a select-type setting.
936
+ * The new options replace existing ones from the manifest.
937
+ *
938
+ * @param settingKey - The setting key to update (must be a 'select' type)
939
+ * @param options - Array of new options
940
+ * @param defaultValue - Optional new default value
941
+ *
942
+ * @example
943
+ * ```typescript
944
+ * // Update font family options based on loaded CSS
945
+ * api.settings.updateOptions('customFontFamily', [
946
+ * { label: 'Roboto', value: 'Roboto' },
947
+ * { label: 'Open Sans', value: 'Open Sans' },
948
+ * { label: 'Lato', value: 'Lato' }
949
+ * ], 'Roboto')
950
+ * ```
951
+ */
952
+ updateOptions(settingKey: string, options: SettingOption[], defaultValue?: string | number | boolean): void;
953
+ /**
954
+ * Get the current options for a setting.
955
+ * Returns the dynamic options if set, otherwise the manifest defaults.
956
+ *
957
+ * @param settingKey - The setting key to get options for
958
+ * @returns Array of options or undefined if not a select setting
959
+ */
960
+ getOptions(settingKey: string): SettingOption[] | undefined;
961
+ /**
962
+ * Reset a setting's options back to the manifest defaults.
963
+ *
964
+ * @param settingKey - The setting key to reset
965
+ */
966
+ resetOptions(settingKey: string): void;
967
+ }
827
968
  /**
828
969
  * Generic addon values (settings configuration).
829
970
  */
package/dist/index.d.ts CHANGED
@@ -151,6 +151,15 @@ interface NetworkResponse {
151
151
  /** Response data (auto-parsed JSON, text, or base64 for binary) */
152
152
  data: unknown;
153
153
  }
154
+ /**
155
+ * Result of a network domain access request.
156
+ */
157
+ interface NetworkAccessResult {
158
+ /** Whether access was granted */
159
+ granted: boolean;
160
+ /** Error message if denied */
161
+ error?: string;
162
+ }
154
163
  /**
155
164
  * Network API for making HTTP requests through the secure host proxy.
156
165
  * Access via `window.MyWallpaper.network`
@@ -158,7 +167,9 @@ interface NetworkResponse {
158
167
  * **IMPORTANT:** Direct `fetch()` and `XMLHttpRequest` are blocked for security.
159
168
  * All network requests MUST go through this API.
160
169
  *
161
- * **IMPORTANT:** Domains must be declared in your manifest.json:
170
+ * **Two ways to access external domains:**
171
+ *
172
+ * 1. **Manifest declaration** (pre-approved):
162
173
  * ```json
163
174
  * {
164
175
  * "permissions": {
@@ -167,14 +178,25 @@ interface NetworkResponse {
167
178
  * }
168
179
  * ```
169
180
  *
181
+ * 2. **On-demand permission** (user approval at runtime):
182
+ * ```typescript
183
+ * const result = await network.requestAccess('fonts.example.com', 'Load custom fonts')
184
+ * if (result.granted) {
185
+ * const response = await network.fetch('https://fonts.example.com/myfont.woff2')
186
+ * }
187
+ * ```
188
+ *
170
189
  * @example
171
190
  * ```typescript
172
191
  * const { network } = window.MyWallpaper
173
192
  *
174
- * // Simple GET request
175
- * const response = await network.fetch('https://api.weather.com/current')
176
- * if (response.ok) {
177
- * console.log(response.data)
193
+ * // On-demand access to a domain (shows permission modal)
194
+ * const access = await network.requestAccess('api.weather.com', 'Fetch weather data')
195
+ * if (access.granted) {
196
+ * const response = await network.fetch('https://api.weather.com/current')
197
+ * if (response.ok) {
198
+ * console.log(response.data)
199
+ * }
178
200
  * }
179
201
  *
180
202
  * // POST request with JSON body
@@ -186,9 +208,33 @@ interface NetworkResponse {
186
208
  * ```
187
209
  */
188
210
  interface NetworkAPI {
211
+ /**
212
+ * Request on-demand access to a domain.
213
+ * Shows a permission modal to the user: "Widget X requests access to: domain.com"
214
+ * This permission lasts for the current session only (not persisted).
215
+ *
216
+ * @param domain - The domain to request access to (e.g., 'fonts.cdnfonts.com')
217
+ * @param reason - User-friendly explanation of why access is needed
218
+ * @returns Promise resolving to NetworkAccessResult
219
+ *
220
+ * @example
221
+ * ```typescript
222
+ * const result = await api.network.requestAccess(
223
+ * 'fonts.cdnfonts.com',
224
+ * 'Load custom font for text display'
225
+ * )
226
+ * if (result.granted) {
227
+ * // Now we can fetch from this domain
228
+ * const css = await api.network.fetch('https://fonts.cdnfonts.com/css/anurati')
229
+ * }
230
+ * ```
231
+ */
232
+ requestAccess(domain: string, reason: string): Promise<NetworkAccessResult>;
189
233
  /**
190
234
  * Make an HTTP request through the secure host proxy.
191
- * Domain must be whitelisted in manifest.json permissions.
235
+ * Domain must be either:
236
+ * - Whitelisted in manifest.json permissions, OR
237
+ * - Approved via requestAccess() in the current session
192
238
  *
193
239
  * @param url - Full URL to fetch (must be in allowed domains)
194
240
  * @param options - Optional request options
@@ -557,6 +603,21 @@ interface MyWallpaperAPI {
557
603
  * ```
558
604
  */
559
605
  audio: AudioAPI;
606
+ /**
607
+ * Settings API for dynamically modifying setting options at runtime.
608
+ * Allows widgets to populate dropdowns based on external data.
609
+ *
610
+ * @example
611
+ * ```typescript
612
+ * // After loading font CSS, update the font family dropdown
613
+ * const fonts = parseFontFaces(cssContent)
614
+ * api.settings.updateOptions('fontFamily', fonts.map(f => ({
615
+ * label: f.family,
616
+ * value: f.family
617
+ * })))
618
+ * ```
619
+ */
620
+ settings: SettingsAPI;
560
621
  /**
561
622
  * File Access API for requesting access to user-uploaded files.
562
623
  * Files are not sent automatically - the addon must request access.
@@ -824,6 +885,86 @@ interface AudioAPI {
824
885
  */
825
886
  onStateChange(callback: (state: AudioState) => void): () => void;
826
887
  }
888
+ /**
889
+ * A setting option for select/dropdown fields.
890
+ */
891
+ interface SettingOption {
892
+ /** Display label shown to user */
893
+ label: string;
894
+ /** Value stored when selected */
895
+ value: string | number | boolean;
896
+ /** Optional description */
897
+ description?: string;
898
+ }
899
+ /**
900
+ * Settings API for dynamically modifying setting options at runtime.
901
+ * Access via `window.MyWallpaper.settings`
902
+ *
903
+ * **Why this exists:**
904
+ * Some widgets need to populate setting options dynamically based on
905
+ * external data (e.g., font families from a CSS file, available themes
906
+ * from an API, etc.). This API allows widgets to modify their own
907
+ * setting options after loading.
908
+ *
909
+ * **Important:**
910
+ * - Only works for settings declared in manifest.json
911
+ * - Only 'select' type settings can have their options updated
912
+ * - Changes are session-only (manifest defines the defaults)
913
+ *
914
+ * @example
915
+ * ```typescript
916
+ * const api = window.MyWallpaper
917
+ *
918
+ * // After fetching a font CSS, parse available fonts and update dropdown
919
+ * const fonts = parseFontFaces(cssContent)
920
+ * api.settings.updateOptions('fontFamily', fonts.map(f => ({
921
+ * label: f.family,
922
+ * value: f.family
923
+ * })))
924
+ *
925
+ * // Update multiple settings at once
926
+ * api.settings.updateOptions('fontWeight', [
927
+ * { label: 'Light', value: '300' },
928
+ * { label: 'Regular', value: '400' },
929
+ * { label: 'Bold', value: '700' }
930
+ * ])
931
+ * ```
932
+ */
933
+ interface SettingsAPI {
934
+ /**
935
+ * Update the options for a select-type setting.
936
+ * The new options replace existing ones from the manifest.
937
+ *
938
+ * @param settingKey - The setting key to update (must be a 'select' type)
939
+ * @param options - Array of new options
940
+ * @param defaultValue - Optional new default value
941
+ *
942
+ * @example
943
+ * ```typescript
944
+ * // Update font family options based on loaded CSS
945
+ * api.settings.updateOptions('customFontFamily', [
946
+ * { label: 'Roboto', value: 'Roboto' },
947
+ * { label: 'Open Sans', value: 'Open Sans' },
948
+ * { label: 'Lato', value: 'Lato' }
949
+ * ], 'Roboto')
950
+ * ```
951
+ */
952
+ updateOptions(settingKey: string, options: SettingOption[], defaultValue?: string | number | boolean): void;
953
+ /**
954
+ * Get the current options for a setting.
955
+ * Returns the dynamic options if set, otherwise the manifest defaults.
956
+ *
957
+ * @param settingKey - The setting key to get options for
958
+ * @returns Array of options or undefined if not a select setting
959
+ */
960
+ getOptions(settingKey: string): SettingOption[] | undefined;
961
+ /**
962
+ * Reset a setting's options back to the manifest defaults.
963
+ *
964
+ * @param settingKey - The setting key to reset
965
+ */
966
+ resetOptions(settingKey: string): void;
967
+ }
827
968
  /**
828
969
  * Generic addon values (settings configuration).
829
970
  */
@@ -136,6 +136,15 @@ interface NetworkResponse {
136
136
  /** Response data (auto-parsed JSON, text, or base64 for binary) */
137
137
  data: unknown;
138
138
  }
139
+ /**
140
+ * Result of a network domain access request.
141
+ */
142
+ interface NetworkAccessResult {
143
+ /** Whether access was granted */
144
+ granted: boolean;
145
+ /** Error message if denied */
146
+ error?: string;
147
+ }
139
148
  /**
140
149
  * Network API for making HTTP requests through the secure host proxy.
141
150
  * Access via `window.MyWallpaper.network`
@@ -143,7 +152,9 @@ interface NetworkResponse {
143
152
  * **IMPORTANT:** Direct `fetch()` and `XMLHttpRequest` are blocked for security.
144
153
  * All network requests MUST go through this API.
145
154
  *
146
- * **IMPORTANT:** Domains must be declared in your manifest.json:
155
+ * **Two ways to access external domains:**
156
+ *
157
+ * 1. **Manifest declaration** (pre-approved):
147
158
  * ```json
148
159
  * {
149
160
  * "permissions": {
@@ -152,14 +163,25 @@ interface NetworkResponse {
152
163
  * }
153
164
  * ```
154
165
  *
166
+ * 2. **On-demand permission** (user approval at runtime):
167
+ * ```typescript
168
+ * const result = await network.requestAccess('fonts.example.com', 'Load custom fonts')
169
+ * if (result.granted) {
170
+ * const response = await network.fetch('https://fonts.example.com/myfont.woff2')
171
+ * }
172
+ * ```
173
+ *
155
174
  * @example
156
175
  * ```typescript
157
176
  * const { network } = window.MyWallpaper
158
177
  *
159
- * // Simple GET request
160
- * const response = await network.fetch('https://api.weather.com/current')
161
- * if (response.ok) {
162
- * console.log(response.data)
178
+ * // On-demand access to a domain (shows permission modal)
179
+ * const access = await network.requestAccess('api.weather.com', 'Fetch weather data')
180
+ * if (access.granted) {
181
+ * const response = await network.fetch('https://api.weather.com/current')
182
+ * if (response.ok) {
183
+ * console.log(response.data)
184
+ * }
163
185
  * }
164
186
  *
165
187
  * // POST request with JSON body
@@ -171,9 +193,33 @@ interface NetworkResponse {
171
193
  * ```
172
194
  */
173
195
  interface NetworkAPI {
196
+ /**
197
+ * Request on-demand access to a domain.
198
+ * Shows a permission modal to the user: "Widget X requests access to: domain.com"
199
+ * This permission lasts for the current session only (not persisted).
200
+ *
201
+ * @param domain - The domain to request access to (e.g., 'fonts.cdnfonts.com')
202
+ * @param reason - User-friendly explanation of why access is needed
203
+ * @returns Promise resolving to NetworkAccessResult
204
+ *
205
+ * @example
206
+ * ```typescript
207
+ * const result = await api.network.requestAccess(
208
+ * 'fonts.cdnfonts.com',
209
+ * 'Load custom font for text display'
210
+ * )
211
+ * if (result.granted) {
212
+ * // Now we can fetch from this domain
213
+ * const css = await api.network.fetch('https://fonts.cdnfonts.com/css/anurati')
214
+ * }
215
+ * ```
216
+ */
217
+ requestAccess(domain: string, reason: string): Promise<NetworkAccessResult>;
174
218
  /**
175
219
  * Make an HTTP request through the secure host proxy.
176
- * Domain must be whitelisted in manifest.json permissions.
220
+ * Domain must be either:
221
+ * - Whitelisted in manifest.json permissions, OR
222
+ * - Approved via requestAccess() in the current session
177
223
  *
178
224
  * @param url - Full URL to fetch (must be in allowed domains)
179
225
  * @param options - Optional request options
@@ -542,6 +588,21 @@ interface MyWallpaperAPI {
542
588
  * ```
543
589
  */
544
590
  audio: AudioAPI;
591
+ /**
592
+ * Settings API for dynamically modifying setting options at runtime.
593
+ * Allows widgets to populate dropdowns based on external data.
594
+ *
595
+ * @example
596
+ * ```typescript
597
+ * // After loading font CSS, update the font family dropdown
598
+ * const fonts = parseFontFaces(cssContent)
599
+ * api.settings.updateOptions('fontFamily', fonts.map(f => ({
600
+ * label: f.family,
601
+ * value: f.family
602
+ * })))
603
+ * ```
604
+ */
605
+ settings: SettingsAPI;
545
606
  /**
546
607
  * File Access API for requesting access to user-uploaded files.
547
608
  * Files are not sent automatically - the addon must request access.
@@ -805,6 +866,86 @@ interface AudioAPI {
805
866
  */
806
867
  onStateChange(callback: (state: AudioState) => void): () => void;
807
868
  }
869
+ /**
870
+ * A setting option for select/dropdown fields.
871
+ */
872
+ interface SettingOption {
873
+ /** Display label shown to user */
874
+ label: string;
875
+ /** Value stored when selected */
876
+ value: string | number | boolean;
877
+ /** Optional description */
878
+ description?: string;
879
+ }
880
+ /**
881
+ * Settings API for dynamically modifying setting options at runtime.
882
+ * Access via `window.MyWallpaper.settings`
883
+ *
884
+ * **Why this exists:**
885
+ * Some widgets need to populate setting options dynamically based on
886
+ * external data (e.g., font families from a CSS file, available themes
887
+ * from an API, etc.). This API allows widgets to modify their own
888
+ * setting options after loading.
889
+ *
890
+ * **Important:**
891
+ * - Only works for settings declared in manifest.json
892
+ * - Only 'select' type settings can have their options updated
893
+ * - Changes are session-only (manifest defines the defaults)
894
+ *
895
+ * @example
896
+ * ```typescript
897
+ * const api = window.MyWallpaper
898
+ *
899
+ * // After fetching a font CSS, parse available fonts and update dropdown
900
+ * const fonts = parseFontFaces(cssContent)
901
+ * api.settings.updateOptions('fontFamily', fonts.map(f => ({
902
+ * label: f.family,
903
+ * value: f.family
904
+ * })))
905
+ *
906
+ * // Update multiple settings at once
907
+ * api.settings.updateOptions('fontWeight', [
908
+ * { label: 'Light', value: '300' },
909
+ * { label: 'Regular', value: '400' },
910
+ * { label: 'Bold', value: '700' }
911
+ * ])
912
+ * ```
913
+ */
914
+ interface SettingsAPI {
915
+ /**
916
+ * Update the options for a select-type setting.
917
+ * The new options replace existing ones from the manifest.
918
+ *
919
+ * @param settingKey - The setting key to update (must be a 'select' type)
920
+ * @param options - Array of new options
921
+ * @param defaultValue - Optional new default value
922
+ *
923
+ * @example
924
+ * ```typescript
925
+ * // Update font family options based on loaded CSS
926
+ * api.settings.updateOptions('customFontFamily', [
927
+ * { label: 'Roboto', value: 'Roboto' },
928
+ * { label: 'Open Sans', value: 'Open Sans' },
929
+ * { label: 'Lato', value: 'Lato' }
930
+ * ], 'Roboto')
931
+ * ```
932
+ */
933
+ updateOptions(settingKey: string, options: SettingOption[], defaultValue?: string | number | boolean): void;
934
+ /**
935
+ * Get the current options for a setting.
936
+ * Returns the dynamic options if set, otherwise the manifest defaults.
937
+ *
938
+ * @param settingKey - The setting key to get options for
939
+ * @returns Array of options or undefined if not a select setting
940
+ */
941
+ getOptions(settingKey: string): SettingOption[] | undefined;
942
+ /**
943
+ * Reset a setting's options back to the manifest defaults.
944
+ *
945
+ * @param settingKey - The setting key to reset
946
+ */
947
+ resetOptions(settingKey: string): void;
948
+ }
808
949
 
809
950
  /**
810
951
  * @mywallpaper/addon-sdk - Manifest Schema & Validation
@@ -136,6 +136,15 @@ interface NetworkResponse {
136
136
  /** Response data (auto-parsed JSON, text, or base64 for binary) */
137
137
  data: unknown;
138
138
  }
139
+ /**
140
+ * Result of a network domain access request.
141
+ */
142
+ interface NetworkAccessResult {
143
+ /** Whether access was granted */
144
+ granted: boolean;
145
+ /** Error message if denied */
146
+ error?: string;
147
+ }
139
148
  /**
140
149
  * Network API for making HTTP requests through the secure host proxy.
141
150
  * Access via `window.MyWallpaper.network`
@@ -143,7 +152,9 @@ interface NetworkResponse {
143
152
  * **IMPORTANT:** Direct `fetch()` and `XMLHttpRequest` are blocked for security.
144
153
  * All network requests MUST go through this API.
145
154
  *
146
- * **IMPORTANT:** Domains must be declared in your manifest.json:
155
+ * **Two ways to access external domains:**
156
+ *
157
+ * 1. **Manifest declaration** (pre-approved):
147
158
  * ```json
148
159
  * {
149
160
  * "permissions": {
@@ -152,14 +163,25 @@ interface NetworkResponse {
152
163
  * }
153
164
  * ```
154
165
  *
166
+ * 2. **On-demand permission** (user approval at runtime):
167
+ * ```typescript
168
+ * const result = await network.requestAccess('fonts.example.com', 'Load custom fonts')
169
+ * if (result.granted) {
170
+ * const response = await network.fetch('https://fonts.example.com/myfont.woff2')
171
+ * }
172
+ * ```
173
+ *
155
174
  * @example
156
175
  * ```typescript
157
176
  * const { network } = window.MyWallpaper
158
177
  *
159
- * // Simple GET request
160
- * const response = await network.fetch('https://api.weather.com/current')
161
- * if (response.ok) {
162
- * console.log(response.data)
178
+ * // On-demand access to a domain (shows permission modal)
179
+ * const access = await network.requestAccess('api.weather.com', 'Fetch weather data')
180
+ * if (access.granted) {
181
+ * const response = await network.fetch('https://api.weather.com/current')
182
+ * if (response.ok) {
183
+ * console.log(response.data)
184
+ * }
163
185
  * }
164
186
  *
165
187
  * // POST request with JSON body
@@ -171,9 +193,33 @@ interface NetworkResponse {
171
193
  * ```
172
194
  */
173
195
  interface NetworkAPI {
196
+ /**
197
+ * Request on-demand access to a domain.
198
+ * Shows a permission modal to the user: "Widget X requests access to: domain.com"
199
+ * This permission lasts for the current session only (not persisted).
200
+ *
201
+ * @param domain - The domain to request access to (e.g., 'fonts.cdnfonts.com')
202
+ * @param reason - User-friendly explanation of why access is needed
203
+ * @returns Promise resolving to NetworkAccessResult
204
+ *
205
+ * @example
206
+ * ```typescript
207
+ * const result = await api.network.requestAccess(
208
+ * 'fonts.cdnfonts.com',
209
+ * 'Load custom font for text display'
210
+ * )
211
+ * if (result.granted) {
212
+ * // Now we can fetch from this domain
213
+ * const css = await api.network.fetch('https://fonts.cdnfonts.com/css/anurati')
214
+ * }
215
+ * ```
216
+ */
217
+ requestAccess(domain: string, reason: string): Promise<NetworkAccessResult>;
174
218
  /**
175
219
  * Make an HTTP request through the secure host proxy.
176
- * Domain must be whitelisted in manifest.json permissions.
220
+ * Domain must be either:
221
+ * - Whitelisted in manifest.json permissions, OR
222
+ * - Approved via requestAccess() in the current session
177
223
  *
178
224
  * @param url - Full URL to fetch (must be in allowed domains)
179
225
  * @param options - Optional request options
@@ -542,6 +588,21 @@ interface MyWallpaperAPI {
542
588
  * ```
543
589
  */
544
590
  audio: AudioAPI;
591
+ /**
592
+ * Settings API for dynamically modifying setting options at runtime.
593
+ * Allows widgets to populate dropdowns based on external data.
594
+ *
595
+ * @example
596
+ * ```typescript
597
+ * // After loading font CSS, update the font family dropdown
598
+ * const fonts = parseFontFaces(cssContent)
599
+ * api.settings.updateOptions('fontFamily', fonts.map(f => ({
600
+ * label: f.family,
601
+ * value: f.family
602
+ * })))
603
+ * ```
604
+ */
605
+ settings: SettingsAPI;
545
606
  /**
546
607
  * File Access API for requesting access to user-uploaded files.
547
608
  * Files are not sent automatically - the addon must request access.
@@ -805,6 +866,86 @@ interface AudioAPI {
805
866
  */
806
867
  onStateChange(callback: (state: AudioState) => void): () => void;
807
868
  }
869
+ /**
870
+ * A setting option for select/dropdown fields.
871
+ */
872
+ interface SettingOption {
873
+ /** Display label shown to user */
874
+ label: string;
875
+ /** Value stored when selected */
876
+ value: string | number | boolean;
877
+ /** Optional description */
878
+ description?: string;
879
+ }
880
+ /**
881
+ * Settings API for dynamically modifying setting options at runtime.
882
+ * Access via `window.MyWallpaper.settings`
883
+ *
884
+ * **Why this exists:**
885
+ * Some widgets need to populate setting options dynamically based on
886
+ * external data (e.g., font families from a CSS file, available themes
887
+ * from an API, etc.). This API allows widgets to modify their own
888
+ * setting options after loading.
889
+ *
890
+ * **Important:**
891
+ * - Only works for settings declared in manifest.json
892
+ * - Only 'select' type settings can have their options updated
893
+ * - Changes are session-only (manifest defines the defaults)
894
+ *
895
+ * @example
896
+ * ```typescript
897
+ * const api = window.MyWallpaper
898
+ *
899
+ * // After fetching a font CSS, parse available fonts and update dropdown
900
+ * const fonts = parseFontFaces(cssContent)
901
+ * api.settings.updateOptions('fontFamily', fonts.map(f => ({
902
+ * label: f.family,
903
+ * value: f.family
904
+ * })))
905
+ *
906
+ * // Update multiple settings at once
907
+ * api.settings.updateOptions('fontWeight', [
908
+ * { label: 'Light', value: '300' },
909
+ * { label: 'Regular', value: '400' },
910
+ * { label: 'Bold', value: '700' }
911
+ * ])
912
+ * ```
913
+ */
914
+ interface SettingsAPI {
915
+ /**
916
+ * Update the options for a select-type setting.
917
+ * The new options replace existing ones from the manifest.
918
+ *
919
+ * @param settingKey - The setting key to update (must be a 'select' type)
920
+ * @param options - Array of new options
921
+ * @param defaultValue - Optional new default value
922
+ *
923
+ * @example
924
+ * ```typescript
925
+ * // Update font family options based on loaded CSS
926
+ * api.settings.updateOptions('customFontFamily', [
927
+ * { label: 'Roboto', value: 'Roboto' },
928
+ * { label: 'Open Sans', value: 'Open Sans' },
929
+ * { label: 'Lato', value: 'Lato' }
930
+ * ], 'Roboto')
931
+ * ```
932
+ */
933
+ updateOptions(settingKey: string, options: SettingOption[], defaultValue?: string | number | boolean): void;
934
+ /**
935
+ * Get the current options for a setting.
936
+ * Returns the dynamic options if set, otherwise the manifest defaults.
937
+ *
938
+ * @param settingKey - The setting key to get options for
939
+ * @returns Array of options or undefined if not a select setting
940
+ */
941
+ getOptions(settingKey: string): SettingOption[] | undefined;
942
+ /**
943
+ * Reset a setting's options back to the manifest defaults.
944
+ *
945
+ * @param settingKey - The setting key to reset
946
+ */
947
+ resetOptions(settingKey: string): void;
948
+ }
808
949
 
809
950
  /**
810
951
  * @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.8.1",
3
+ "version": "2.10.0",
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",
@@ -29,7 +29,9 @@
29
29
  var pendingOAuth = new Map()
30
30
  var pendingOAuthScopes = new Map()
31
31
  var pendingFileAccess = new Map()
32
+ var pendingNetworkAccess = new Map() // For on-demand domain permission requests
32
33
  var grantedBlobUrls = new Map() // settingKey -> blobUrl
34
+ var dynamicSettingsOptions = new Map() // settingKey -> options[] (dynamic options from widget)
33
35
 
34
36
  // Audio state (synced from host)
35
37
  var audioState = {
@@ -52,6 +54,39 @@
52
54
  if (!d || d.source !== 'MyWallpaperHost') return
53
55
 
54
56
  switch (d.type) {
57
+ case 'SETTINGS_PREVIEW':
58
+ // FAST PATH: Lightweight preview for drag operations (color picker, sliders)
59
+ // Updates CSS variables AND triggers callbacks for immediate visual feedback
60
+ var previewPayload = d.payload || d
61
+ var previewSettings = previewPayload.settings || {}
62
+ var previewKeys = previewPayload.changedKeys || Object.keys(previewSettings)
63
+
64
+ // Update CSS variables
65
+ try {
66
+ var previewRoot = document.documentElement
67
+ for (var pi = 0; pi < previewKeys.length; pi++) {
68
+ var pKey = previewKeys[pi]
69
+ var pValue = previewSettings[pKey]
70
+ if (pValue !== undefined && pValue !== null) {
71
+ var pType = typeof pValue
72
+ if (pType === 'string' || pType === 'number' || pType === 'boolean') {
73
+ previewRoot.style.setProperty('--mw-config-' + pKey, String(pValue))
74
+ }
75
+ }
76
+ }
77
+ } catch (previewErr) {
78
+ // Silent fail
79
+ }
80
+
81
+ // Also update config and trigger callbacks for addons that use JS
82
+ for (var pk = 0; pk < previewKeys.length; pk++) {
83
+ config[previewKeys[pk]] = previewSettings[previewKeys[pk]]
84
+ }
85
+ settingsCallbacks.forEach(function (cb) {
86
+ try { cb(config, previewKeys) } catch (err) { /* silent */ }
87
+ })
88
+ break
89
+
55
90
  case 'SETTINGS_UPDATE':
56
91
  var p = d.payload || d
57
92
  config = p.settings || p
@@ -197,6 +232,15 @@
197
232
  }
198
233
  break
199
234
 
235
+ case 'NETWORK_ACCESS_RESPONSE':
236
+ // Handle network domain access response from host
237
+ var netAccessOp = pendingNetworkAccess.get(d.requestId)
238
+ if (netAccessOp) {
239
+ pendingNetworkAccess.delete(d.requestId)
240
+ netAccessOp.resolve({ granted: d.granted === true, error: d.error })
241
+ }
242
+ break
243
+
200
244
  case 'AUDIO_STATE':
201
245
  // Sync audio state from host
202
246
  audioState = d.payload || d
@@ -225,10 +269,33 @@
225
269
  })
226
270
  }
227
271
 
272
+ /**
273
+ * Request on-demand access to a network domain
274
+ * Shows a permission modal to the user
275
+ * @param {string} domain - The domain to request access to
276
+ * @param {string} reason - User-friendly explanation
277
+ * @returns {Promise<{granted: boolean, error?: string}>}
278
+ */
279
+ function networkRequestAccess(domain, reason) {
280
+ return new Promise(function (resolve) {
281
+ var id = Date.now() + '-' + Math.random().toString(36).slice(2)
282
+ pendingNetworkAccess.set(id, { resolve: resolve })
283
+ // 60s timeout - user needs time to respond to modal
284
+ setTimeout(function () {
285
+ if (pendingNetworkAccess.has(id)) {
286
+ pendingNetworkAccess.delete(id)
287
+ resolve({ granted: false, error: 'Request timeout' })
288
+ }
289
+ }, 60000)
290
+ send('NETWORK_DOMAIN_REQUEST', { domain: domain, reason: reason, requestId: id })
291
+ })
292
+ }
293
+
228
294
  /**
229
295
  * Network fetch via secure host proxy
230
296
  * This is the ONLY way addons can make network requests
231
297
  * Domain must be declared in manifest.json permissions.network.domains
298
+ * OR approved via networkRequestAccess()
232
299
  */
233
300
  function networkFetch(url, options) {
234
301
  return new Promise(function (resolve, reject) {
@@ -414,11 +481,18 @@
414
481
  * All requests go through the host which validates domain whitelist
415
482
  *
416
483
  * @example
484
+ * // Option 1: Pre-declare domains in manifest.json
417
485
  * // manifest.json: "permissions": { "network": { "domains": ["api.weather.com"] } }
418
486
  * const response = await MyWallpaper.network.fetch('https://api.weather.com/current')
419
- * if (response.ok) console.log(response.data)
487
+ *
488
+ * // Option 2: On-demand permission (shows modal to user)
489
+ * const access = await MyWallpaper.network.requestAccess('fonts.example.com', 'Load fonts')
490
+ * if (access.granted) {
491
+ * const response = await MyWallpaper.network.fetch('https://fonts.example.com/font.woff2')
492
+ * }
420
493
  */
421
494
  network: {
495
+ requestAccess: networkRequestAccess,
422
496
  fetch: networkFetch
423
497
  },
424
498
 
@@ -641,6 +715,76 @@
641
715
  callback(audioState)
642
716
  return function () { audioCallbacks.delete(callback) }
643
717
  }
718
+ },
719
+
720
+ /**
721
+ * Settings API - Dynamically modify setting options at runtime
722
+ * Allows widgets to populate dropdowns based on external data
723
+ *
724
+ * @example
725
+ * // After fetching a font CSS, parse available fonts and update dropdown
726
+ * const fonts = parseFontFaces(cssContent)
727
+ * MyWallpaper.settings.updateOptions('fontFamily', fonts.map(f => ({
728
+ * label: f.family,
729
+ * value: f.family
730
+ * })))
731
+ */
732
+ settings: {
733
+ /**
734
+ * Update the options for a select-type setting
735
+ * @param {string} settingKey - The setting key to update
736
+ * @param {Array<{label: string, value: string|number|boolean, description?: string}>} options - New options
737
+ * @param {string|number|boolean} defaultValue - Optional new default value
738
+ */
739
+ updateOptions: function (settingKey, options, defaultValue) {
740
+ if (!settingKey || typeof settingKey !== 'string') {
741
+ console.error('[MyWallpaper] settings.updateOptions: settingKey is required')
742
+ return
743
+ }
744
+ if (!Array.isArray(options)) {
745
+ console.error('[MyWallpaper] settings.updateOptions: options must be an array')
746
+ return
747
+ }
748
+
749
+ // Validate options format
750
+ var validOptions = options.filter(function (opt) {
751
+ return opt && typeof opt === 'object' && typeof opt.label === 'string' && opt.value !== undefined
752
+ })
753
+
754
+ if (validOptions.length === 0) {
755
+ console.warn('[MyWallpaper] settings.updateOptions: no valid options provided')
756
+ return
757
+ }
758
+
759
+ // Store locally for getOptions()
760
+ dynamicSettingsOptions.set(settingKey, validOptions)
761
+
762
+ // Send to host to update the settings panel
763
+ send('SETTINGS_OPTIONS_UPDATE', {
764
+ settingKey: settingKey,
765
+ options: validOptions,
766
+ defaultValue: defaultValue
767
+ })
768
+ },
769
+
770
+ /**
771
+ * Get the current options for a setting
772
+ * Returns dynamic options if set, otherwise undefined
773
+ * @param {string} settingKey - The setting key
774
+ * @returns {Array|undefined} The options array or undefined
775
+ */
776
+ getOptions: function (settingKey) {
777
+ return dynamicSettingsOptions.get(settingKey)
778
+ },
779
+
780
+ /**
781
+ * Reset a setting's options back to the manifest defaults
782
+ * @param {string} settingKey - The setting key to reset
783
+ */
784
+ resetOptions: function (settingKey) {
785
+ dynamicSettingsOptions.delete(settingKey)
786
+ send('SETTINGS_OPTIONS_RESET', { settingKey: settingKey })
787
+ }
644
788
  }
645
789
  }
646
790