@mywallpaper/addon-sdk 2.8.1 → 2.9.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
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
@@ -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
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mywallpaper/addon-sdk",
3
- "version": "2.8.1",
3
+ "version": "2.9.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,6 +29,7 @@
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
33
34
 
34
35
  // Audio state (synced from host)
@@ -52,6 +53,39 @@
52
53
  if (!d || d.source !== 'MyWallpaperHost') return
53
54
 
54
55
  switch (d.type) {
56
+ case 'SETTINGS_PREVIEW':
57
+ // FAST PATH: Lightweight preview for drag operations (color picker, sliders)
58
+ // Updates CSS variables AND triggers callbacks for immediate visual feedback
59
+ var previewPayload = d.payload || d
60
+ var previewSettings = previewPayload.settings || {}
61
+ var previewKeys = previewPayload.changedKeys || Object.keys(previewSettings)
62
+
63
+ // Update CSS variables
64
+ try {
65
+ var previewRoot = document.documentElement
66
+ for (var pi = 0; pi < previewKeys.length; pi++) {
67
+ var pKey = previewKeys[pi]
68
+ var pValue = previewSettings[pKey]
69
+ if (pValue !== undefined && pValue !== null) {
70
+ var pType = typeof pValue
71
+ if (pType === 'string' || pType === 'number' || pType === 'boolean') {
72
+ previewRoot.style.setProperty('--mw-config-' + pKey, String(pValue))
73
+ }
74
+ }
75
+ }
76
+ } catch (previewErr) {
77
+ // Silent fail
78
+ }
79
+
80
+ // Also update config and trigger callbacks for addons that use JS
81
+ for (var pk = 0; pk < previewKeys.length; pk++) {
82
+ config[previewKeys[pk]] = previewSettings[previewKeys[pk]]
83
+ }
84
+ settingsCallbacks.forEach(function (cb) {
85
+ try { cb(config, previewKeys) } catch (err) { /* silent */ }
86
+ })
87
+ break
88
+
55
89
  case 'SETTINGS_UPDATE':
56
90
  var p = d.payload || d
57
91
  config = p.settings || p
@@ -197,6 +231,15 @@
197
231
  }
198
232
  break
199
233
 
234
+ case 'NETWORK_ACCESS_RESPONSE':
235
+ // Handle network domain access response from host
236
+ var netAccessOp = pendingNetworkAccess.get(d.requestId)
237
+ if (netAccessOp) {
238
+ pendingNetworkAccess.delete(d.requestId)
239
+ netAccessOp.resolve({ granted: d.granted === true, error: d.error })
240
+ }
241
+ break
242
+
200
243
  case 'AUDIO_STATE':
201
244
  // Sync audio state from host
202
245
  audioState = d.payload || d
@@ -225,10 +268,33 @@
225
268
  })
226
269
  }
227
270
 
271
+ /**
272
+ * Request on-demand access to a network domain
273
+ * Shows a permission modal to the user
274
+ * @param {string} domain - The domain to request access to
275
+ * @param {string} reason - User-friendly explanation
276
+ * @returns {Promise<{granted: boolean, error?: string}>}
277
+ */
278
+ function networkRequestAccess(domain, reason) {
279
+ return new Promise(function (resolve) {
280
+ var id = Date.now() + '-' + Math.random().toString(36).slice(2)
281
+ pendingNetworkAccess.set(id, { resolve: resolve })
282
+ // 60s timeout - user needs time to respond to modal
283
+ setTimeout(function () {
284
+ if (pendingNetworkAccess.has(id)) {
285
+ pendingNetworkAccess.delete(id)
286
+ resolve({ granted: false, error: 'Request timeout' })
287
+ }
288
+ }, 60000)
289
+ send('NETWORK_DOMAIN_REQUEST', { domain: domain, reason: reason, requestId: id })
290
+ })
291
+ }
292
+
228
293
  /**
229
294
  * Network fetch via secure host proxy
230
295
  * This is the ONLY way addons can make network requests
231
296
  * Domain must be declared in manifest.json permissions.network.domains
297
+ * OR approved via networkRequestAccess()
232
298
  */
233
299
  function networkFetch(url, options) {
234
300
  return new Promise(function (resolve, reject) {
@@ -414,11 +480,18 @@
414
480
  * All requests go through the host which validates domain whitelist
415
481
  *
416
482
  * @example
483
+ * // Option 1: Pre-declare domains in manifest.json
417
484
  * // manifest.json: "permissions": { "network": { "domains": ["api.weather.com"] } }
418
485
  * const response = await MyWallpaper.network.fetch('https://api.weather.com/current')
419
- * if (response.ok) console.log(response.data)
486
+ *
487
+ * // Option 2: On-demand permission (shows modal to user)
488
+ * const access = await MyWallpaper.network.requestAccess('fonts.example.com', 'Load fonts')
489
+ * if (access.granted) {
490
+ * const response = await MyWallpaper.network.fetch('https://fonts.example.com/font.woff2')
491
+ * }
420
492
  */
421
493
  network: {
494
+ requestAccess: networkRequestAccess,
422
495
  fetch: networkFetch
423
496
  },
424
497