@mywallpaper/addon-sdk 2.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 MyWallpaper Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,581 @@
1
+ # @mywallpaper/addon-sdk
2
+
3
+ Official SDK for building MyWallpaper addons.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @mywallpaper/addon-sdk
9
+ # or
10
+ yarn add @mywallpaper/addon-sdk
11
+ # or
12
+ pnpm add @mywallpaper/addon-sdk
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ### 1. Create your addon files
18
+
19
+ ```
20
+ my-addon/
21
+ ├── manifest.json # Addon metadata and settings
22
+ ├── index.html # Main HTML file
23
+ ├── styles.css # (optional) Styles
24
+ └── script.js # (optional) JavaScript
25
+ ```
26
+
27
+ ### 2. Define your manifest.json
28
+
29
+ ```json
30
+ {
31
+ "name": "My Awesome Addon",
32
+ "version": "1.0.0",
33
+ "description": "A cool widget for your desktop",
34
+ "author": "Your Name",
35
+ "capabilities": {
36
+ "hotReload": true,
37
+ "systemEvents": ["viewport:resize", "theme:change"]
38
+ },
39
+ "settings": {
40
+ "backgroundColor": {
41
+ "type": "color",
42
+ "label": "Background Color",
43
+ "default": "#1a1a2e"
44
+ },
45
+ "animationSpeed": {
46
+ "type": "range",
47
+ "label": "Animation Speed",
48
+ "min": 0.1,
49
+ "max": 5,
50
+ "step": 0.1,
51
+ "default": 1
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ ### 3. Use the SDK in your addon
58
+
59
+ ```html
60
+ <!DOCTYPE html>
61
+ <html>
62
+ <head>
63
+ <style>
64
+ body {
65
+ margin: 0;
66
+ overflow: hidden;
67
+ background: var(--bg-color, #1a1a2e);
68
+ }
69
+ </style>
70
+ </head>
71
+ <body>
72
+ <canvas id="canvas"></canvas>
73
+ <script>
74
+ // The MyWallpaper API is automatically injected
75
+ const api = window.MyWallpaper
76
+
77
+ // Get initial config
78
+ const { backgroundColor, animationSpeed } = api.config
79
+
80
+ // Apply initial config
81
+ document.body.style.setProperty('--bg-color', backgroundColor)
82
+
83
+ // React to settings changes (hot-reload)
84
+ api.onSettingsChange((settings, changedKeys) => {
85
+ if (changedKeys.includes('backgroundColor')) {
86
+ document.body.style.setProperty('--bg-color', settings.backgroundColor)
87
+ }
88
+ if (changedKeys.includes('animationSpeed')) {
89
+ updateAnimationSpeed(settings.animationSpeed)
90
+ }
91
+ })
92
+
93
+ // Handle viewport resize
94
+ api.onEvent('viewport:resize', ({ width, height }) => {
95
+ const canvas = document.getElementById('canvas')
96
+ canvas.width = width
97
+ canvas.height = height
98
+ redraw()
99
+ })
100
+
101
+ // Signal addon is ready
102
+ api.ready({
103
+ capabilities: ['hot-reload'],
104
+ subscribedEvents: ['viewport:resize']
105
+ })
106
+
107
+ // After visual setup is complete
108
+ initCanvas()
109
+ api.renderComplete()
110
+ </script>
111
+ </body>
112
+ </html>
113
+ ```
114
+
115
+ ## TypeScript Support
116
+
117
+ This package includes full TypeScript definitions. Use them for type-safe development:
118
+
119
+ ```typescript
120
+ import type {
121
+ MyWallpaperAPI,
122
+ SystemEventType,
123
+ AddonManifest
124
+ } from '@mywallpaper/addon-sdk'
125
+
126
+ // Type-safe event handling
127
+ const api = window.MyWallpaper
128
+
129
+ api.onEvent('viewport:resize', ({ width, height }) => {
130
+ // TypeScript knows width and height are numbers
131
+ console.log(`${width}x${height}`)
132
+ })
133
+
134
+ api.onEvent('theme:change', ({ theme }) => {
135
+ // TypeScript knows theme is 'light' | 'dark' | 'system'
136
+ document.body.className = theme
137
+ })
138
+ ```
139
+
140
+ ### Manifest Validation
141
+
142
+ ```typescript
143
+ import { validateManifest, type AddonManifest } from '@mywallpaper/addon-sdk/manifest'
144
+
145
+ const manifest: AddonManifest = {
146
+ name: 'My Addon',
147
+ version: '1.0.0',
148
+ settings: {
149
+ primaryColor: { type: 'color', default: '#ff0000' }
150
+ }
151
+ }
152
+
153
+ const result = validateManifest(manifest)
154
+ if (!result.success) {
155
+ console.error('Invalid manifest:', result.errors)
156
+ }
157
+ ```
158
+
159
+ ## API Reference
160
+
161
+ ### `window.MyWallpaper`
162
+
163
+ The main API object, automatically injected into your addon.
164
+
165
+ #### Properties
166
+
167
+ | Property | Type | Description |
168
+ |----------|------|-------------|
169
+ | `config` | `Record<string, unknown>` | Current settings values |
170
+ | `layerId` | `string` | Unique layer instance ID |
171
+ | `version` | `'2.0'` | SDK version |
172
+ | `storage` | `StorageAPI` | Persistent storage API |
173
+ | `network` | `NetworkAPI` | Secure network requests API |
174
+
175
+ #### Methods
176
+
177
+ ##### `ready(options?)`
178
+
179
+ Signal that your addon is initialized and ready.
180
+
181
+ ```typescript
182
+ api.ready({
183
+ capabilities: ['hot-reload', 'storage'],
184
+ subscribedEvents: ['viewport:resize', 'theme:change']
185
+ })
186
+ ```
187
+
188
+ ##### `renderComplete()`
189
+
190
+ Signal that visual rendering is complete (for screenshot service).
191
+
192
+ ```typescript
193
+ await loadAllImages()
194
+ drawCanvas()
195
+ api.renderComplete()
196
+ ```
197
+
198
+ ##### `onSettingsChange(callback)`
199
+
200
+ React to settings changes (hot-reload).
201
+
202
+ ```typescript
203
+ api.onSettingsChange((settings, changedKeys) => {
204
+ changedKeys.forEach(key => {
205
+ console.log(`${key} changed to ${settings[key]}`)
206
+ })
207
+ })
208
+ ```
209
+
210
+ ##### `onEvent(event, callback)` / `offEvent(event, callback)`
211
+
212
+ Subscribe/unsubscribe to system events.
213
+
214
+ ```typescript
215
+ const handler = ({ width, height }) => resize(width, height)
216
+
217
+ api.onEvent('viewport:resize', handler)
218
+ // Later...
219
+ api.offEvent('viewport:resize', handler)
220
+ ```
221
+
222
+ ##### `requestPermission(permission, reason)`
223
+
224
+ Request a permission from the user.
225
+
226
+ ```typescript
227
+ const granted = await api.requestPermission(
228
+ 'storage',
229
+ 'Save your preferences for next time'
230
+ )
231
+
232
+ if (granted) {
233
+ await api.storage.set('initialized', true)
234
+ }
235
+ ```
236
+
237
+ #### Lifecycle Methods
238
+
239
+ ```typescript
240
+ api.onMount(() => console.log('Addon mounted'))
241
+ api.onUnmount(() => cleanup())
242
+ api.onPause(() => pauseAnimations())
243
+ api.onResume(() => resumeAnimations())
244
+ ```
245
+
246
+ ### Storage API
247
+
248
+ Persistent key-value storage that syncs across devices.
249
+
250
+ ```typescript
251
+ const { storage } = window.MyWallpaper
252
+
253
+ // Store data
254
+ await storage.set('preferences', { theme: 'dark', fontSize: 14 })
255
+
256
+ // Retrieve data (with TypeScript generics)
257
+ const prefs = await storage.get<{ theme: string; fontSize: number }>('preferences')
258
+
259
+ // List all keys
260
+ const keys = await storage.keys()
261
+
262
+ // Check storage usage
263
+ const bytesUsed = await storage.size()
264
+ console.log(`Using ${bytesUsed} of 1MB quota`)
265
+
266
+ // Delete a key
267
+ await storage.delete('oldData')
268
+
269
+ // Clear all storage
270
+ await storage.clear()
271
+ ```
272
+
273
+ ### OAuth API
274
+
275
+ Access OAuth provider APIs (GitHub, Google, Discord, Spotify, Twitch) securely without ever seeing the user's tokens.
276
+
277
+ **How it works:**
278
+ 1. User connects their OAuth provider in MyWallpaper settings
279
+ 2. Your addon requests permission (e.g., `oauth:github`)
280
+ 3. API calls are proxied through the host - your addon never sees the token
281
+ 4. The host handles token refresh automatically
282
+
283
+ **Step 1:** Declare required OAuth permission in `manifest.json`:
284
+
285
+ ```json
286
+ {
287
+ "permissions": {
288
+ "required": ["oauth:github"],
289
+ "optional": ["oauth:spotify"]
290
+ }
291
+ }
292
+ ```
293
+
294
+ **Step 2:** Request permission and make API calls:
295
+
296
+ ```typescript
297
+ const api = window.MyWallpaper
298
+
299
+ // Request GitHub permission
300
+ const granted = await api.requestPermission(
301
+ 'oauth:github',
302
+ 'Display your GitHub profile information'
303
+ )
304
+
305
+ if (!granted) {
306
+ console.log('GitHub access denied')
307
+ return
308
+ }
309
+
310
+ // Make API calls through the proxy
311
+ const response = await api.oauth.request('github', '/user')
312
+
313
+ if (response.ok) {
314
+ console.log('GitHub profile:', response.data)
315
+ console.log('Username:', response.data.login)
316
+ console.log('Followers:', response.data.followers)
317
+ }
318
+
319
+ // POST request example
320
+ const result = await api.oauth.request('github', '/user/repos', {
321
+ method: 'POST',
322
+ body: { name: 'new-repo', private: true }
323
+ })
324
+
325
+ // Check if user has connected a provider
326
+ const isConnected = await api.oauth.isConnected('github')
327
+ ```
328
+
329
+ #### Supported OAuth Providers
330
+
331
+ | Provider | Permission | API Base URL |
332
+ |----------|------------|--------------|
333
+ | GitHub | `oauth:github` | `https://api.github.com` |
334
+ | Google | `oauth:google` | `https://www.googleapis.com` |
335
+ | Discord | `oauth:discord` | `https://discord.com/api/v10` |
336
+ | Spotify | `oauth:spotify` | `https://api.spotify.com/v1` |
337
+ | Twitch | `oauth:twitch` | `https://api.twitch.tv/helix` |
338
+
339
+ #### OAuthResponse
340
+
341
+ | Property | Type | Description |
342
+ |----------|------|-------------|
343
+ | `ok` | `boolean` | `true` if status is 200-299 |
344
+ | `status` | `number` | HTTP status code |
345
+ | `headers` | `Record<string, string>` | Response headers |
346
+ | `data` | `unknown` | Parsed JSON response |
347
+
348
+ **Security:** Your addon NEVER sees OAuth tokens. All requests are proxied through the host, which adds the `Authorization` header server-side. Tokens are encrypted at rest with AES-256-GCM.
349
+
350
+ ---
351
+
352
+ ### Network API
353
+
354
+ **SECURITY:** Direct `fetch()` and `XMLHttpRequest` are blocked for security.
355
+ All network requests MUST go through `MyWallpaper.network.fetch()`.
356
+
357
+ **Step 1:** Declare allowed domains in your `manifest.json`:
358
+
359
+ ```json
360
+ {
361
+ "permissions": {
362
+ "network": {
363
+ "domains": ["api.weather.com", "api.example.com"]
364
+ }
365
+ }
366
+ }
367
+ ```
368
+
369
+ **Step 2:** Request permission and make requests:
370
+
371
+ ```typescript
372
+ const { network } = window.MyWallpaper
373
+
374
+ // Request network permission first
375
+ const granted = await MyWallpaper.requestPermission(
376
+ 'network',
377
+ 'Fetch weather data to display current conditions'
378
+ )
379
+
380
+ if (!granted) {
381
+ console.log('Network permission denied')
382
+ return
383
+ }
384
+
385
+ // GET request
386
+ const response = await network.fetch('https://api.weather.com/current?city=Paris')
387
+ if (response.ok) {
388
+ const weather = response.data
389
+ console.log(`Temperature: ${weather.temp}°C`)
390
+ }
391
+
392
+ // POST request with JSON
393
+ const result = await network.fetch('https://api.example.com/data', {
394
+ method: 'POST',
395
+ headers: { 'Content-Type': 'application/json' },
396
+ body: JSON.stringify({ key: 'value' })
397
+ })
398
+
399
+ // Handle errors
400
+ try {
401
+ const data = await network.fetch('https://blocked-domain.com/api')
402
+ } catch (error) {
403
+ // Error: Domain not allowed. Allowed domains: api.weather.com, api.example.com
404
+ }
405
+ ```
406
+
407
+ #### NetworkResponse
408
+
409
+ | Property | Type | Description |
410
+ |----------|------|-------------|
411
+ | `ok` | `boolean` | `true` if status is 200-299 |
412
+ | `status` | `number` | HTTP status code |
413
+ | `statusText` | `string` | HTTP status text |
414
+ | `headers` | `Record<string, string>` | Response headers |
415
+ | `data` | `unknown` | Auto-parsed response (JSON, text, or base64) |
416
+
417
+ ## System Events
418
+
419
+ | Event | Data | Description |
420
+ |-------|------|-------------|
421
+ | `viewport:resize` | `{ width, height, aspectRatio }` | Viewport dimensions changed |
422
+ | `theme:change` | `{ theme: 'light' \| 'dark' \| 'system' }` | User changed theme |
423
+ | `visibility:change` | `{ visible: boolean }` | Page visibility changed |
424
+ | `layer:focus` | `{ layerId: string }` | Layer gained focus |
425
+ | `layer:blur` | `{ layerId: string }` | Layer lost focus |
426
+ | `app:idle` | `{ idleSeconds: number }` | User is idle |
427
+ | `app:active` | `{}` | User became active |
428
+
429
+ ## Setting Types
430
+
431
+ | Type | UI Control | Value Type |
432
+ |------|------------|------------|
433
+ | `string` | Text input | `string` |
434
+ | `number` | Number input | `number` |
435
+ | `boolean` | Toggle | `boolean` |
436
+ | `color` | Color picker | `string` (hex) |
437
+ | `select` | Dropdown | `string` |
438
+ | `range` | Slider | `number` |
439
+ | `image` | Image upload | `string` (URL) |
440
+ | `textarea` | Multi-line text | `string` |
441
+ | `radio` | Radio buttons | `string` |
442
+ | `multi-select` | Checkboxes | `string[]` |
443
+ | `gradient` | Gradient editor | `{ stops: [...] }` |
444
+ | `vector2` | X/Y inputs | `{ x, y }` |
445
+ | `section` | Visual divider | (no value) |
446
+ | `button` | Action button | (no value) |
447
+
448
+ ### Setting Definition Example
449
+
450
+ ```json
451
+ {
452
+ "settings": {
453
+ "header": {
454
+ "type": "section",
455
+ "label": "Appearance"
456
+ },
457
+ "primaryColor": {
458
+ "type": "color",
459
+ "label": "Primary Color",
460
+ "default": "#3b82f6",
461
+ "description": "Main accent color"
462
+ },
463
+ "opacity": {
464
+ "type": "range",
465
+ "label": "Opacity",
466
+ "min": 0,
467
+ "max": 100,
468
+ "step": 1,
469
+ "default": 100
470
+ },
471
+ "style": {
472
+ "type": "select",
473
+ "label": "Style",
474
+ "default": "modern",
475
+ "options": [
476
+ { "value": "modern", "label": "Modern" },
477
+ { "value": "classic", "label": "Classic" },
478
+ { "value": "minimal", "label": "Minimal" }
479
+ ]
480
+ }
481
+ }
482
+ }
483
+ ```
484
+
485
+ ## Permissions
486
+
487
+ | Permission | Description | Default Quota |
488
+ |------------|-------------|---------------|
489
+ | `storage` | Persistent storage | 1MB |
490
+ | `network` | External requests | Domain whitelist |
491
+ | `audio` | Audio playback | - |
492
+ | `cpu-high` | Higher CPU quota | - |
493
+ | `notifications` | Desktop notifications | - |
494
+ | `oauth:github` | GitHub API access | - |
495
+ | `oauth:google` | Google API access | - |
496
+ | `oauth:discord` | Discord API access | - |
497
+ | `oauth:spotify` | Spotify API access | - |
498
+ | `oauth:twitch` | Twitch API access | - |
499
+
500
+ Declare permissions in manifest for better UX:
501
+
502
+ ```json
503
+ {
504
+ "permissions": {
505
+ "storage": { "quota": 512 },
506
+ "network": { "domains": ["api.example.com"] }
507
+ }
508
+ }
509
+ ```
510
+
511
+ ## Utilities
512
+
513
+ The SDK includes helpful utilities:
514
+
515
+ ```typescript
516
+ import {
517
+ createDefaultConfig,
518
+ mergeConfigs,
519
+ compareVersions,
520
+ formatBytes,
521
+ debounce,
522
+ throttle
523
+ } from '@mywallpaper/addon-sdk'
524
+
525
+ // Create defaults from manifest
526
+ const defaults = createDefaultConfig(manifest)
527
+
528
+ // Merge with user config
529
+ const config = mergeConfigs(defaults, userConfig)
530
+
531
+ // Version comparison
532
+ if (compareVersions('2.0.0', '1.9.9') > 0) {
533
+ console.log('v2 is newer')
534
+ }
535
+
536
+ // Format storage size
537
+ console.log(formatBytes(1048576)) // "1 MB"
538
+
539
+ // Debounce expensive operations
540
+ const saveSettings = debounce((settings) => {
541
+ localStorage.setItem('settings', JSON.stringify(settings))
542
+ }, 1000)
543
+
544
+ // Throttle frequent events
545
+ const updatePosition = throttle((x, y) => {
546
+ element.style.transform = `translate(${x}px, ${y}px)`
547
+ }, 16) // ~60fps
548
+ ```
549
+
550
+ ## Examples
551
+
552
+ Check out the `/examples` directory for complete addon examples:
553
+
554
+ - `basic/` - Minimal addon structure with settings
555
+ - `weather-widget/` - Weather widget with network API
556
+ - `github-profile/` - GitHub profile using OAuth API proxy
557
+
558
+ ## Best Practices
559
+
560
+ 1. **Always call `ready()`** - The host waits for this signal
561
+ 2. **Call `renderComplete()`** - Helps screenshot service capture your addon correctly
562
+ 3. **Declare capabilities** - Helps the host optimize resource allocation
563
+ 4. **Use hot-reload** - Better UX when users change settings
564
+ 5. **Request permissions early** - Ask for storage permission on first interaction
565
+ 6. **Handle lifecycle** - Pause animations when hidden/paused to save resources
566
+ 7. **Validate your manifest** - Use `validateManifest()` during development
567
+
568
+ ## Migration from v1.0
569
+
570
+ v2.0 removes backward compatibility with v1.0 addons. Key changes:
571
+
572
+ - `apiVersion` field removed (always v2.0)
573
+ - Trust system types removed (unused)
574
+ - `convertToLegacyFormat()` removed
575
+ - `isLegacyAddonMessage()` removed
576
+ - Permissions now use 30s timeout (was 100ms bug)
577
+ - Storage now properly waits for response
578
+
579
+ ## License
580
+
581
+ MIT