@wealthfolio/addon-sdk 3.6.2 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +57 -0
- package/README.md +312 -206
- package/dist/{chunk-BFZMXPHG.js → chunk-2IM7BFJX.js} +3 -3
- package/dist/{chunk-BFZMXPHG.js.map → chunk-2IM7BFJX.js.map} +1 -1
- package/dist/{chunk-KCG6RCFW.js → chunk-3YZER4XO.js} +16 -2
- package/dist/chunk-3YZER4XO.js.map +1 -0
- package/dist/{chunk-K7KLLX42.js → chunk-5727GMVD.js} +1 -1
- package/dist/chunk-5727GMVD.js.map +1 -0
- package/dist/{chunk-AAIYUZY5.js → chunk-DQNFXUZ7.js} +3 -3
- package/dist/{chunk-AAIYUZY5.js.map → chunk-DQNFXUZ7.js.map} +1 -1
- package/dist/host-dependencies.js +1 -1
- package/dist/index.js +28 -5
- package/dist/index.js.map +1 -1
- package/dist/manifest.js +1 -1
- package/dist/permissions.js +1 -1
- package/dist/src/data-types.d.ts +90 -1
- package/dist/src/host-api.d.ts +67 -3
- package/dist/src/host-dependencies.d.ts +2 -2
- package/dist/src/i18n.d.ts +55 -0
- package/dist/src/index.d.ts +5 -3
- package/dist/src/manifest.d.ts +16 -1
- package/dist/src/types.d.ts +18 -2
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-K7KLLX42.js.map +0 -1
- package/dist/chunk-KCG6RCFW.js.map +0 -1
package/README.md
CHANGED
|
@@ -42,6 +42,8 @@ integrations, and visualizations.
|
|
|
42
42
|
- **ESM Support**: Modern ECMAScript modules with tree-shaking support
|
|
43
43
|
- **Comprehensive Logging**: Built-in logging system with multiple levels
|
|
44
44
|
- **Event System**: Subscribe to application events and state changes
|
|
45
|
+
- **Spend Categorization**: Manage reusable expense, income, and savings rules
|
|
46
|
+
- **Localization**: Follow the host locale with scoped addon translations
|
|
45
47
|
- **Performance Optimized**: Lightweight bundle with minimal overhead
|
|
46
48
|
- **Developer Tools**: Built-in debugging and development utilities
|
|
47
49
|
- **Backwards Compatible**: Stable API with semantic versioning
|
|
@@ -70,12 +72,15 @@ mkdir src && touch src/index.ts
|
|
|
70
72
|
|
|
71
73
|
```typescript
|
|
72
74
|
// src/index.ts
|
|
73
|
-
import {
|
|
74
|
-
import { getAddonContext, type AddonContext } from '@wealthfolio/addon-sdk';
|
|
75
|
+
import type { AddonContext } from '@wealthfolio/addon-sdk';
|
|
75
76
|
import { MyComponent } from './MyComponent';
|
|
76
77
|
|
|
78
|
+
let addonContext: AddonContext | undefined;
|
|
79
|
+
|
|
80
|
+
const MyAddonRoute = () => <MyComponent ctx={addonContext!} />;
|
|
81
|
+
|
|
77
82
|
export default function enable(context: AddonContext) {
|
|
78
|
-
|
|
83
|
+
addonContext = context;
|
|
79
84
|
|
|
80
85
|
// Add navigation item
|
|
81
86
|
const navItem = context.sidebar.addItem({
|
|
@@ -87,11 +92,9 @@ export default function enable(context: AddonContext) {
|
|
|
87
92
|
|
|
88
93
|
// Register route
|
|
89
94
|
context.router.add({
|
|
95
|
+
id: 'my-addon',
|
|
90
96
|
path: '/addons/my-addon',
|
|
91
|
-
|
|
92
|
-
root ??= createRoot(routeRoot);
|
|
93
|
-
root.render(<MyComponent ctx={context} />);
|
|
94
|
-
},
|
|
97
|
+
component: MyAddonRoute,
|
|
95
98
|
});
|
|
96
99
|
|
|
97
100
|
// Log activation
|
|
@@ -99,8 +102,7 @@ export default function enable(context: AddonContext) {
|
|
|
99
102
|
|
|
100
103
|
// Cleanup on disable
|
|
101
104
|
context.onDisable(() => {
|
|
102
|
-
|
|
103
|
-
root = null;
|
|
105
|
+
addonContext = undefined;
|
|
104
106
|
navItem.remove();
|
|
105
107
|
context.api.logger.info('My addon deactivated');
|
|
106
108
|
});
|
|
@@ -122,15 +124,15 @@ pnpm add @wealthfolio/addon-sdk @tanstack/react-query
|
|
|
122
124
|
|
|
123
125
|
### Requirements
|
|
124
126
|
|
|
125
|
-
- **Node.js**: >=
|
|
126
|
-
- **React**: ^
|
|
127
|
+
- **Node.js**: >= 20.0.0
|
|
128
|
+
- **React**: ^19.2.4 (peer dependency and host-provided version)
|
|
127
129
|
- **TypeScript**: ^5.0.0 (recommended for development)
|
|
128
|
-
- **React Query**: ^
|
|
130
|
+
- **React Query**: ^5.90.0 (for data fetching)
|
|
129
131
|
|
|
130
132
|
### Package Information
|
|
131
133
|
|
|
132
134
|
- **Package Name**: `@wealthfolio/addon-sdk`
|
|
133
|
-
- **Current Version**:
|
|
135
|
+
- **Current Version**: 3.8.0
|
|
134
136
|
- **Bundle Format**: ESM (ECMAScript Modules)
|
|
135
137
|
- **Type Definitions**: Included (TypeScript ready)
|
|
136
138
|
- **License**: MIT
|
|
@@ -140,23 +142,28 @@ pnpm add @wealthfolio/addon-sdk @tanstack/react-query
|
|
|
140
142
|
|
|
141
143
|
### Import Methods
|
|
142
144
|
|
|
143
|
-
The SDK supports
|
|
145
|
+
The SDK supports a public entry point plus focused subpath imports:
|
|
144
146
|
|
|
145
147
|
```typescript
|
|
146
|
-
//
|
|
147
|
-
import {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
import {
|
|
148
|
+
// Public entry point (recommended)
|
|
149
|
+
import type {
|
|
150
|
+
AddonContext,
|
|
151
|
+
AddonManifest,
|
|
152
|
+
Holding,
|
|
153
|
+
Permission,
|
|
154
|
+
RiskLevel,
|
|
155
|
+
} from '@wealthfolio/addon-sdk';
|
|
156
|
+
import { PERMISSION_CATEGORIES } from '@wealthfolio/addon-sdk';
|
|
157
|
+
|
|
158
|
+
// Optional module-specific subpath imports
|
|
159
|
+
import type { AddonManifest as Manifest } from '@wealthfolio/addon-sdk/manifest';
|
|
160
|
+
import type { Permission as AddonPermission } from '@wealthfolio/addon-sdk/permissions';
|
|
158
161
|
```
|
|
159
162
|
|
|
163
|
+
The `/types` subpath contains core context, routing, sidebar, and event types.
|
|
164
|
+
Financial data types such as `Account`, `Activity`, and `Holding` are exported
|
|
165
|
+
from the package root.
|
|
166
|
+
|
|
160
167
|
## 🏗️ Project Structure
|
|
161
168
|
|
|
162
169
|
Create your addon with the following recommended structure:
|
|
@@ -179,6 +186,46 @@ my-portfolio-addon/
|
|
|
179
186
|
└── vite.config.ts # Build configuration
|
|
180
187
|
```
|
|
181
188
|
|
|
189
|
+
### Packaged assets
|
|
190
|
+
|
|
191
|
+
Static files below `assets/` and generated files below `dist/assets/` are
|
|
192
|
+
indexed automatically; they do not need to be declared in `manifest.json`.
|
|
193
|
+
JavaScript chunks and CSS in those directories remain runtime code/styles. Load
|
|
194
|
+
other files through the add-on context so the host can keep the opaque iframe
|
|
195
|
+
offline:
|
|
196
|
+
|
|
197
|
+
This API requires Wealthfolio 3.7 or newer. Set `sdkVersion` and
|
|
198
|
+
`minWealthfolioVersion` to `3.7.0` when using it. No permission is required.
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
export default async function enable(context: AddonContext) {
|
|
202
|
+
const logoUrl = await context.assets.getUrl('assets/logo.png');
|
|
203
|
+
const configBlob = await context.assets.getBlob('assets/config.json');
|
|
204
|
+
const config = JSON.parse(await configBlob.text());
|
|
205
|
+
|
|
206
|
+
// Use logoUrl in an <img>, CSS-in-JS value, or component prop.
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
The registry also provides `list()` for public path/MIME/size metadata and
|
|
211
|
+
`has(path)` for feature checks. It never exposes host paths or opaque internal
|
|
212
|
+
identifiers. `context.assets` is unrelated to the financial-instrument API at
|
|
213
|
+
`context.api.assets`.
|
|
214
|
+
|
|
215
|
+
Packaged URLs in extracted CSS are resolved automatically and relative to the
|
|
216
|
+
CSS file. For example, `dist/addon.css` can use `url("./assets/background.png")`
|
|
217
|
+
for `dist/assets/background.png`. `data:` and `blob:` URLs remain unchanged. CSS
|
|
218
|
+
`@import` and remote URLs are not supported; bundle imported CSS and use the
|
|
219
|
+
brokered network API for remote data.
|
|
220
|
+
|
|
221
|
+
JavaScript image imports that compile to relative HTTP URLs cannot work in the
|
|
222
|
+
opaque Blob runtime. Use `context.assets.getUrl()` instead. Blob URLs are cached
|
|
223
|
+
for the add-on lifetime and revoked automatically when it is disabled. Package
|
|
224
|
+
limits remain 5 MiB per file, 25 MiB uncompressed in total, and 256 entries.
|
|
225
|
+
Asset roots must be directories; symlinks are rejected. See the
|
|
226
|
+
[v3.6 to v3.7 migration guide](../../docs/addons/addon-migration-guide-v3.6-to-v3.7.md)
|
|
227
|
+
for compatibility and troubleshooting.
|
|
228
|
+
|
|
182
229
|
## 📋 Manifest Configuration
|
|
183
230
|
|
|
184
231
|
Create a `manifest.json` file in your addon root:
|
|
@@ -193,11 +240,16 @@ Create a `manifest.json` file in your addon root:
|
|
|
193
240
|
"homepage": "https://github.com/yourname/investment-fees-tracker",
|
|
194
241
|
"license": "MIT",
|
|
195
242
|
"main": "dist/addon.js",
|
|
196
|
-
"sdkVersion": "
|
|
197
|
-
"minWealthfolioVersion": "
|
|
243
|
+
"sdkVersion": "3.8.0",
|
|
244
|
+
"minWealthfolioVersion": "3.8.0",
|
|
198
245
|
"keywords": ["portfolio", "fees", "tracking", "analytics"],
|
|
199
246
|
"icon": "data:image/svg+xml;base64,...",
|
|
200
247
|
"permissions": [
|
|
248
|
+
{
|
|
249
|
+
"category": "accounts",
|
|
250
|
+
"functions": ["getAll"],
|
|
251
|
+
"purpose": "List accounts whose holdings will be analyzed"
|
|
252
|
+
},
|
|
201
253
|
{
|
|
202
254
|
"category": "portfolio",
|
|
203
255
|
"functions": ["getHoldings"],
|
|
@@ -207,6 +259,11 @@ Create a `manifest.json` file in your addon root:
|
|
|
207
259
|
"category": "activities",
|
|
208
260
|
"functions": ["getAll"],
|
|
209
261
|
"purpose": "Analyze transaction history for fee calculations"
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
"category": "performance",
|
|
265
|
+
"functions": ["calculateSummary"],
|
|
266
|
+
"purpose": "Calculate account performance alongside fee totals"
|
|
210
267
|
}
|
|
211
268
|
]
|
|
212
269
|
}
|
|
@@ -233,7 +290,7 @@ Create a `manifest.json` file in your addon root:
|
|
|
233
290
|
| `permissions` | `Permission[]` | Security permissions required |
|
|
234
291
|
| `minWealthfolioVersion` | `string` | Minimum Wealthfolio version required |
|
|
235
292
|
| `keywords` | `string[]` | Keywords for discoverability |
|
|
236
|
-
| `icon` | `string` | Addon icon
|
|
293
|
+
| `icon` | `string` | Addon icon value supported by the host |
|
|
237
294
|
|
|
238
295
|
## 🔨 Development Guide
|
|
239
296
|
|
|
@@ -244,9 +301,12 @@ example:
|
|
|
244
301
|
|
|
245
302
|
```typescript
|
|
246
303
|
// src/addon.tsx
|
|
247
|
-
import { createRoot, type Root } from 'react-dom/client';
|
|
248
304
|
import { QueryClientProvider } from '@tanstack/react-query';
|
|
249
|
-
import type {
|
|
305
|
+
import type {
|
|
306
|
+
AddonContext,
|
|
307
|
+
AddonEnableFunction,
|
|
308
|
+
QueryClient,
|
|
309
|
+
} from '@wealthfolio/addon-sdk';
|
|
250
310
|
import FeesPage from './pages/fees-page';
|
|
251
311
|
|
|
252
312
|
// Main addon component
|
|
@@ -264,7 +324,6 @@ const enable: AddonEnableFunction = (context) => {
|
|
|
264
324
|
|
|
265
325
|
// Store references to items for cleanup
|
|
266
326
|
const addedItems: Array<{ remove: () => void }> = [];
|
|
267
|
-
let root: Root | null = null;
|
|
268
327
|
|
|
269
328
|
try {
|
|
270
329
|
// Add sidebar navigation item with a host-supported icon token
|
|
@@ -279,11 +338,11 @@ const enable: AddonEnableFunction = (context) => {
|
|
|
279
338
|
|
|
280
339
|
context.api.logger.debug('Sidebar navigation item added successfully');
|
|
281
340
|
|
|
282
|
-
// Create wrapper component with
|
|
341
|
+
// Create wrapper component with this addon's QueryClient
|
|
283
342
|
const InvestmentFeesTrackerWrapper = () => {
|
|
284
|
-
const
|
|
343
|
+
const addonQueryClient = context.api.query.getClient() as QueryClient;
|
|
285
344
|
return (
|
|
286
|
-
<QueryClientProvider client={
|
|
345
|
+
<QueryClientProvider client={addonQueryClient}>
|
|
287
346
|
<InvestmentFeesTrackerAddon ctx={context} />
|
|
288
347
|
</QueryClientProvider>
|
|
289
348
|
);
|
|
@@ -291,11 +350,9 @@ const enable: AddonEnableFunction = (context) => {
|
|
|
291
350
|
|
|
292
351
|
// Register route
|
|
293
352
|
context.router.add({
|
|
353
|
+
id: 'investment-fees-tracker',
|
|
294
354
|
path: '/addons/investment-fees-tracker',
|
|
295
|
-
|
|
296
|
-
root ??= createRoot(routeRoot);
|
|
297
|
-
root.render(<InvestmentFeesTrackerWrapper />);
|
|
298
|
-
},
|
|
355
|
+
component: InvestmentFeesTrackerWrapper,
|
|
299
356
|
});
|
|
300
357
|
|
|
301
358
|
context.api.logger.debug('Route registered successfully');
|
|
@@ -310,10 +367,6 @@ const enable: AddonEnableFunction = (context) => {
|
|
|
310
367
|
context.onDisable(() => {
|
|
311
368
|
context.api.logger.info('🛑 Investment Fees Tracker addon is being disabled');
|
|
312
369
|
|
|
313
|
-
// Unmount the addon's React root
|
|
314
|
-
root?.unmount();
|
|
315
|
-
root = null;
|
|
316
|
-
|
|
317
370
|
// Remove all sidebar items
|
|
318
371
|
addedItems.forEach(item => {
|
|
319
372
|
try {
|
|
@@ -333,32 +386,28 @@ export default enable;
|
|
|
333
386
|
|
|
334
387
|
### Key Features Demonstrated
|
|
335
388
|
|
|
336
|
-
1. **
|
|
337
|
-
|
|
338
|
-
2. **UI Icons**:
|
|
389
|
+
1. **Addon Query Client**: Uses `context.api.query.getClient()` for local data
|
|
390
|
+
fetching with host invalidation bridging
|
|
391
|
+
2. **UI Icons**: Uses a host-supported icon token for consistent navigation
|
|
339
392
|
3. **Error Handling**: Comprehensive error handling with logging
|
|
340
393
|
4. **Resource Management**: Proper cleanup of sidebar items and event listeners
|
|
341
394
|
5. **TypeScript**: Full type safety with proper imports
|
|
342
|
-
6. **Sandbox Rendering**:
|
|
343
|
-
unmounts on disable
|
|
344
|
-
|
|
345
|
-
````
|
|
395
|
+
6. **Sandbox Rendering**: Lets the sandbox host own and update the React root
|
|
346
396
|
|
|
347
397
|
### Advanced Component Example
|
|
348
398
|
|
|
349
399
|
```typescript
|
|
350
400
|
// components/FeesPage.tsx
|
|
351
|
-
import React, { useEffect
|
|
401
|
+
import React, { useEffect } from 'react';
|
|
352
402
|
import { useQuery } from '@tanstack/react-query';
|
|
353
403
|
import type { AddonContext } from '@wealthfolio/addon-sdk';
|
|
354
|
-
import type { Holding, Account, Activity } from '@wealthfolio/addon-sdk/types';
|
|
355
404
|
|
|
356
405
|
interface FeesPageProps {
|
|
357
406
|
ctx: AddonContext;
|
|
358
407
|
}
|
|
359
408
|
|
|
360
409
|
export function FeesPage({ ctx }: FeesPageProps) {
|
|
361
|
-
// Use React Query for data fetching with
|
|
410
|
+
// Use React Query for data fetching with this addon's client
|
|
362
411
|
const { data: accounts, isLoading: accountsLoading } = useQuery({
|
|
363
412
|
queryKey: ['accounts'],
|
|
364
413
|
queryFn: () => ctx.api.accounts.getAll()
|
|
@@ -386,25 +435,20 @@ export function FeesPage({ ctx }: FeesPageProps) {
|
|
|
386
435
|
|
|
387
436
|
// Calculate total fees from activities
|
|
388
437
|
const totalFees = React.useMemo(() => {
|
|
389
|
-
if (!activities
|
|
438
|
+
if (!activities) return 0;
|
|
390
439
|
|
|
391
|
-
return activities.
|
|
440
|
+
return activities.reduce((total, activity) => {
|
|
392
441
|
// Look for fee-related activities or transaction costs
|
|
393
|
-
const fee = activity.fee
|
|
442
|
+
const fee = Number(activity.fee ?? 0);
|
|
394
443
|
return total + fee;
|
|
395
444
|
}, 0);
|
|
396
445
|
}, [activities]);
|
|
397
446
|
|
|
398
447
|
useEffect(() => {
|
|
399
448
|
if (!isLoading) {
|
|
400
|
-
ctx.api.logger.info(
|
|
401
|
-
accountsCount: accounts?.length,
|
|
402
|
-
holdingsCount: holdings?.length,
|
|
403
|
-
activitiesCount: activities?.data?.length,
|
|
404
|
-
totalFees
|
|
405
|
-
});
|
|
449
|
+
ctx.api.logger.info("Fees data loaded successfully");
|
|
406
450
|
}
|
|
407
|
-
}, [isLoading,
|
|
451
|
+
}, [isLoading, ctx.api.logger]);
|
|
408
452
|
|
|
409
453
|
if (isLoading) {
|
|
410
454
|
return (
|
|
@@ -447,14 +491,16 @@ export function FeesPage({ ctx }: FeesPageProps) {
|
|
|
447
491
|
<div className="bg-white p-6 rounded-lg shadow border">
|
|
448
492
|
<h2 className="text-xl font-semibold mb-4">Recent Fee Activities</h2>
|
|
449
493
|
<div className="space-y-3">
|
|
450
|
-
{activities?.
|
|
494
|
+
{activities?.slice(0, 5).map((activity) => (
|
|
451
495
|
<div key={activity.id} className="flex justify-between items-center py-2 border-b">
|
|
452
496
|
<div>
|
|
453
497
|
<p className="font-medium">{activity.activityType}</p>
|
|
454
|
-
<p className="text-sm text-gray-600">
|
|
498
|
+
<p className="text-sm text-gray-600">
|
|
499
|
+
{activity.date.toLocaleDateString()}
|
|
500
|
+
</p>
|
|
455
501
|
</div>
|
|
456
502
|
<span className="text-red-600 font-medium">
|
|
457
|
-
${(activity.fee
|
|
503
|
+
${Number(activity.fee ?? 0).toFixed(2)}
|
|
458
504
|
</span>
|
|
459
505
|
</div>
|
|
460
506
|
))}
|
|
@@ -499,17 +545,20 @@ export default FeesPage;
|
|
|
499
545
|
}
|
|
500
546
|
|
|
501
547
|
export default AnalyticsDashboard;
|
|
502
|
-
|
|
548
|
+
```
|
|
503
549
|
|
|
504
550
|
### Using Hooks and State Management
|
|
505
551
|
|
|
506
552
|
```typescript
|
|
507
553
|
// hooks/usePortfolioData.ts
|
|
508
554
|
import { useState, useEffect } from 'react';
|
|
509
|
-
import {
|
|
510
|
-
|
|
555
|
+
import type {
|
|
556
|
+
AddonContext,
|
|
557
|
+
Holding,
|
|
558
|
+
PerformanceResult,
|
|
559
|
+
} from '@wealthfolio/addon-sdk';
|
|
511
560
|
|
|
512
|
-
export function usePortfolioData(accountId?: string) {
|
|
561
|
+
export function usePortfolioData(ctx: AddonContext, accountId?: string) {
|
|
513
562
|
const [holdings, setHoldings] = useState<Holding[]>([]);
|
|
514
563
|
const [performance, setPerformance] = useState<PerformanceResult | null>(
|
|
515
564
|
null,
|
|
@@ -523,26 +572,25 @@ export function usePortfolioData(accountId?: string) {
|
|
|
523
572
|
setLoading(true);
|
|
524
573
|
setError(null);
|
|
525
574
|
|
|
526
|
-
|
|
575
|
+
if (!accountId) {
|
|
576
|
+
setHoldings([]);
|
|
577
|
+
setPerformance(null);
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
527
580
|
|
|
528
|
-
const holdingsData = await ctx.api.portfolio.getHoldings(
|
|
529
|
-
accountId || '',
|
|
530
|
-
);
|
|
581
|
+
const holdingsData = await ctx.api.portfolio.getHoldings(accountId);
|
|
531
582
|
setHoldings(holdingsData);
|
|
532
583
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
);
|
|
544
|
-
setPerformance(performanceData);
|
|
545
|
-
}
|
|
584
|
+
const performanceData = await ctx.api.performance.calculateSummary({
|
|
585
|
+
itemType: 'account',
|
|
586
|
+
itemId: accountId,
|
|
587
|
+
});
|
|
588
|
+
console.log(
|
|
589
|
+
performanceData.returns.twr,
|
|
590
|
+
performanceData.returns.irr,
|
|
591
|
+
performanceData.risk.maxDrawdown,
|
|
592
|
+
);
|
|
593
|
+
setPerformance(performanceData);
|
|
546
594
|
} catch (err) {
|
|
547
595
|
setError(err instanceof Error ? err.message : 'Unknown error');
|
|
548
596
|
} finally {
|
|
@@ -551,7 +599,7 @@ export function usePortfolioData(accountId?: string) {
|
|
|
551
599
|
}
|
|
552
600
|
|
|
553
601
|
fetchData();
|
|
554
|
-
}, [accountId]);
|
|
602
|
+
}, [accountId, ctx]);
|
|
555
603
|
|
|
556
604
|
return { holdings, performance, loading, error };
|
|
557
605
|
}
|
|
@@ -565,18 +613,28 @@ cash flows.
|
|
|
565
613
|
|
|
566
614
|
### Permission Categories
|
|
567
615
|
|
|
568
|
-
| Category
|
|
569
|
-
|
|
|
570
|
-
| `
|
|
571
|
-
| `
|
|
572
|
-
| `events`
|
|
573
|
-
| `currency`
|
|
574
|
-
| `
|
|
575
|
-
| `
|
|
576
|
-
| `
|
|
577
|
-
| `
|
|
578
|
-
| `
|
|
579
|
-
| `
|
|
616
|
+
| Category | Risk Level | Description |
|
|
617
|
+
| --------------------- | ---------- | -------------------------------------------- |
|
|
618
|
+
| `market-data` | Low | Search and synchronize market data |
|
|
619
|
+
| `quotes` | Low | Read and update quotes |
|
|
620
|
+
| `events` | Low | Listen to application events |
|
|
621
|
+
| `currency` | Low | Access exchange rates |
|
|
622
|
+
| `assets` | Medium | Read and update financial asset profiles |
|
|
623
|
+
| `performance` | Medium | Calculate portfolio performance |
|
|
624
|
+
| `spending` | Medium | View categories and manage spending rules |
|
|
625
|
+
| `financial-planning` | Medium | Manage goals and allocations |
|
|
626
|
+
| `contribution-limits` | Medium | Manage contribution limits |
|
|
627
|
+
| `files` | Medium | Open host file dialogs |
|
|
628
|
+
| `settings` | Medium | Access application configuration |
|
|
629
|
+
| `portfolio` | High | Access holdings and valuations |
|
|
630
|
+
| `activities` | High | Access and modify transaction history |
|
|
631
|
+
| `accounts` | High | Access and create accounts |
|
|
632
|
+
| `snapshots` | High | Access and modify holdings snapshots |
|
|
633
|
+
| `network` | High | Request declared external HTTPS hosts |
|
|
634
|
+
| `secrets` | High | Store and use secrets through the OS keyring |
|
|
635
|
+
|
|
636
|
+
`ui`, `navigation`, `query`, `toast`, `logger`, and `storage` are baseline
|
|
637
|
+
capabilities and must not be declared as permissions.
|
|
580
638
|
|
|
581
639
|
### Declaring Permissions
|
|
582
640
|
|
|
@@ -585,7 +643,7 @@ cash flows.
|
|
|
585
643
|
"permissions": [
|
|
586
644
|
{
|
|
587
645
|
"category": "portfolio",
|
|
588
|
-
"functions": ["getHoldings", "getHolding"
|
|
646
|
+
"functions": ["getHoldings", "getHolding"],
|
|
589
647
|
"purpose": "Display detailed portfolio analytics and performance metrics"
|
|
590
648
|
},
|
|
591
649
|
{
|
|
@@ -595,8 +653,13 @@ cash flows.
|
|
|
595
653
|
},
|
|
596
654
|
{
|
|
597
655
|
"category": "market-data",
|
|
598
|
-
"functions": ["searchTicker"
|
|
656
|
+
"functions": ["searchTicker"],
|
|
599
657
|
"purpose": "Show price charts and enable ticker search functionality"
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
"category": "spending",
|
|
661
|
+
"functions": ["getCategories", "saveRule"],
|
|
662
|
+
"purpose": "Save categorization rules selected by the user"
|
|
600
663
|
}
|
|
601
664
|
]
|
|
602
665
|
}
|
|
@@ -604,6 +667,20 @@ cash flows.
|
|
|
604
667
|
|
|
605
668
|
## 🛠️ Build Configuration
|
|
606
669
|
|
|
670
|
+
Wealthfolio 3.7 supports Chrome/Edge 107+, Firefox 104+, and Safari 16+. The
|
|
671
|
+
desktop app requires macOS 12+ and the native mobile app requires iOS/iPadOS
|
|
672
|
+
16+. On macOS 12, apply current macOS and Safari updates so the system WKWebView
|
|
673
|
+
meets the Safari 16 floor. Addons run inside the platform system WebView, so
|
|
674
|
+
build against this browser floor rather than relying on the browser used during
|
|
675
|
+
development.
|
|
676
|
+
|
|
677
|
+
Files below `assets/` and `dist/assets/` are private to the addon package. Use
|
|
678
|
+
`ctx.assets.list()`, `ctx.assets.getBlob(path)`, and `ctx.assets.getUrl(path)`
|
|
679
|
+
to access them. Packaged images, fonts, media, CSS, and WebAssembly are
|
|
680
|
+
supported; Worker and service-worker entry points, popups, direct network
|
|
681
|
+
requests, and remote CSS imports are not. Use the host's brokered APIs,
|
|
682
|
+
including `ctx.api.network.request()`, for declared external access.
|
|
683
|
+
|
|
607
684
|
### Vite Configuration
|
|
608
685
|
|
|
609
686
|
Create a `vite.config.ts` for optimal bundling:
|
|
@@ -616,6 +693,7 @@ import { resolve } from 'path';
|
|
|
616
693
|
export default defineConfig({
|
|
617
694
|
plugins: [react()],
|
|
618
695
|
build: {
|
|
696
|
+
target: ['chrome107', 'edge107', 'firefox104', 'safari16'],
|
|
619
697
|
lib: {
|
|
620
698
|
entry: resolve(__dirname, 'src/index.ts'),
|
|
621
699
|
name: 'MyPortfolioAddon',
|
|
@@ -750,7 +828,6 @@ Add an item to the application sidebar.
|
|
|
750
828
|
`chart-bar`, or `calendar-dots`
|
|
751
829
|
- `config.route` (string): Navigation route
|
|
752
830
|
- `config.order` (number): Display order (optional)
|
|
753
|
-
- `config.onClick` (function): Click handler (optional)
|
|
754
831
|
|
|
755
832
|
**Returns:** `SidebarItemHandle` with `remove()` method
|
|
756
833
|
|
|
@@ -761,8 +838,10 @@ Register a new route in the application.
|
|
|
761
838
|
**Parameters:**
|
|
762
839
|
|
|
763
840
|
- `route.path` (string): Route path pattern
|
|
764
|
-
- `route.
|
|
765
|
-
|
|
841
|
+
- `route.component` (component): Preferred; the host mounts the React component
|
|
842
|
+
and passes the current `location`
|
|
843
|
+
- `route.render` (function): Legacy imperative alternative receiving
|
|
844
|
+
`{ root, location }`
|
|
766
845
|
|
|
767
846
|
#### `onDisable(callback)`
|
|
768
847
|
|
|
@@ -777,49 +856,72 @@ Register cleanup callback for addon disable.
|
|
|
777
856
|
All data access is performed through the context's `api` property:
|
|
778
857
|
|
|
779
858
|
```typescript
|
|
780
|
-
|
|
859
|
+
// Use the ctx parameter supplied to enable(ctx), or pass it to this helper.
|
|
781
860
|
|
|
782
861
|
// Portfolio data
|
|
783
862
|
const holdings = await ctx.api.portfolio.getHoldings(accountId);
|
|
784
863
|
const accounts = await ctx.api.accounts.getAll();
|
|
785
864
|
|
|
786
865
|
// Market data
|
|
787
|
-
const
|
|
788
|
-
const
|
|
866
|
+
const symbols = await ctx.api.market.searchTicker('AAPL');
|
|
867
|
+
const quotes = await ctx.api.quotes.getHistory(assetId);
|
|
868
|
+
const profile = await ctx.api.assets.getProfile(assetId);
|
|
789
869
|
|
|
790
870
|
// Financial planning
|
|
791
871
|
const goals = await ctx.api.goals.getAll();
|
|
792
|
-
const limits = await ctx.api.
|
|
872
|
+
const limits = await ctx.api.contributionLimits.getAll();
|
|
873
|
+
|
|
874
|
+
// Historical exchange rates and spend categorization (Wealthfolio 3.8+)
|
|
875
|
+
const rates = await ctx.api.exchangeRates.getRatesForDates([
|
|
876
|
+
{ fromCurrency: 'USD', toCurrency: 'EUR', date: '2026-09-04' },
|
|
877
|
+
]);
|
|
878
|
+
const spendCategories = await ctx.api.spending.getCategories('expense');
|
|
793
879
|
|
|
794
880
|
// Settings
|
|
795
|
-
const settings = await ctx.api.
|
|
881
|
+
const settings = await ctx.api.settings.get();
|
|
796
882
|
|
|
797
883
|
// Logging and debugging
|
|
798
884
|
ctx.api.logger.info('Operation completed successfully');
|
|
799
|
-
ctx.api.logger.error(
|
|
800
|
-
ctx.api.logger.debug(
|
|
885
|
+
ctx.api.logger.error(`Error occurred: ${String(error)}`);
|
|
886
|
+
ctx.api.logger.debug(`Debug info: ${JSON.stringify(debugData)}`);
|
|
801
887
|
```
|
|
802
888
|
|
|
803
889
|
### Available API Methods
|
|
804
890
|
|
|
805
|
-
| Method
|
|
806
|
-
|
|
|
807
|
-
| `portfolio.getHoldings(accountId)`
|
|
808
|
-
| `portfolio.getHolding(accountId, assetId)`
|
|
809
|
-
| `
|
|
810
|
-
| `
|
|
811
|
-
| `accounts.
|
|
812
|
-
| `
|
|
813
|
-
| `activities.
|
|
814
|
-
| `
|
|
815
|
-
| `
|
|
816
|
-
| `
|
|
817
|
-
| `
|
|
818
|
-
| `
|
|
819
|
-
| `
|
|
820
|
-
| `
|
|
821
|
-
| `
|
|
822
|
-
| `
|
|
891
|
+
| Method | Description | Permission Required |
|
|
892
|
+
| ------------------------------------------ | ------------------------------------------- | --------------------- |
|
|
893
|
+
| `portfolio.getHoldings(accountId)` | Get portfolio holdings for an account | `portfolio` |
|
|
894
|
+
| `portfolio.getHolding(accountId, assetId)` | Get a specific holding | `portfolio` |
|
|
895
|
+
| `performance.calculateSummary(params)` | Calculate performance metrics | `performance` |
|
|
896
|
+
| `accounts.getAll()` | Get all account information | `accounts` |
|
|
897
|
+
| `accounts.create(account)` | Create an account | `accounts` |
|
|
898
|
+
| `activities.getAll(accountId?)` | Get activity history | `activities` |
|
|
899
|
+
| `activities.create(activity)` | Create an activity | `activities` |
|
|
900
|
+
| `market.searchTicker(query)` | Search for tickers | `market-data` |
|
|
901
|
+
| `assets.getProfile(assetId)` | Get a financial asset profile | `assets` |
|
|
902
|
+
| `quotes.getHistory(assetId)` | Get historical quotes | `quotes` |
|
|
903
|
+
| `exchangeRates.getRatesForDates(pairs)` | Resolve dated exchange rates | `currency` |
|
|
904
|
+
| `spending.isEnabled()` | Check whether Spending is enabled | `spending` |
|
|
905
|
+
| `spending.getCategories(kind?)` | List expense, income, or savings categories | `spending` |
|
|
906
|
+
| `spending.getRules()` | List this addon's categorization rules | `spending` |
|
|
907
|
+
| `spending.saveRule(rule)` | Create or update an addon-owned rule | `spending` |
|
|
908
|
+
| `spending.deleteRule(ruleKey)` | Delete an addon-owned rule | `spending` |
|
|
909
|
+
| `spending.rerunRules(onlyUncategorized?)` | Re-run categorization rules | `spending` |
|
|
910
|
+
| `goals.getAll()` | Get financial goals | `financial-planning` |
|
|
911
|
+
| `contributionLimits.getAll()` | Get contribution limits | `contribution-limits` |
|
|
912
|
+
| `settings.get()` | Get application settings | `settings` |
|
|
913
|
+
| `query.getClient()` | Get this addon's QueryClient | None |
|
|
914
|
+
|
|
915
|
+
The Spending and dated exchange-rate APIs require Wealthfolio 3.8 or newer. See
|
|
916
|
+
the [complete API reference](../../docs/addons/addon-api-reference.md).
|
|
917
|
+
|
|
918
|
+
### Localization
|
|
919
|
+
|
|
920
|
+
Wealthfolio 3.8 addons can register private translation bundles with
|
|
921
|
+
`registerTranslations()` and read them from React components with
|
|
922
|
+
`useAddonTranslation()`. Addons using these exports must set
|
|
923
|
+
`minWealthfolioVersion` to `3.8.0` or newer. See the
|
|
924
|
+
[localization guide](../../docs/addons/addon-localization.md).
|
|
823
925
|
|
|
824
926
|
> Tip: `activities.getAll` accepts an optional account ID string to scope
|
|
825
927
|
> results to a single account. The SDK normalizes this for both desktop (Tauri)
|
|
@@ -856,51 +958,51 @@ const response = await ctx.api.activities.search(
|
|
|
856
958
|
The SDK provides a comprehensive logging system:
|
|
857
959
|
|
|
858
960
|
```typescript
|
|
859
|
-
|
|
961
|
+
// Use the ctx parameter supplied to enable(ctx), or pass it to this helper.
|
|
860
962
|
|
|
861
|
-
//
|
|
862
|
-
ctx.api.logger.error(
|
|
863
|
-
ctx.api.logger.warn(
|
|
963
|
+
// Each method accepts one string message.
|
|
964
|
+
ctx.api.logger.error(`Critical error occurred: ${String(error)}`);
|
|
965
|
+
ctx.api.logger.warn(`Warning message: ${String(additionalData)}`);
|
|
864
966
|
ctx.api.logger.info('Information message');
|
|
865
|
-
ctx.api.logger.debug(
|
|
866
|
-
|
|
867
|
-
// Set log level (for development)
|
|
868
|
-
ctx.api.logger.setLevel('debug');
|
|
869
|
-
|
|
870
|
-
// Check if logging level is enabled
|
|
871
|
-
if (ctx.api.logger.isLevelEnabled('debug')) {
|
|
872
|
-
ctx.api.logger.debug('Expensive debug operation', expensiveData);
|
|
873
|
-
}
|
|
967
|
+
ctx.api.logger.debug(`Debug information: ${JSON.stringify(debugObject)}`);
|
|
968
|
+
ctx.api.logger.trace('Detailed trace message');
|
|
874
969
|
```
|
|
875
970
|
|
|
876
|
-
###
|
|
971
|
+
### Addon QueryClient Integration
|
|
877
972
|
|
|
878
|
-
The
|
|
879
|
-
|
|
973
|
+
The sandbox provides one React Query client per addon. Its cache is reused
|
|
974
|
+
across that addon's route renders, not shared with the host or other addons.
|
|
975
|
+
Invalidate/refetch operations are mirrored to the host:
|
|
880
976
|
|
|
881
977
|
```typescript
|
|
882
|
-
|
|
883
|
-
|
|
978
|
+
import { QueryClientProvider, useQuery } from '@tanstack/react-query';
|
|
979
|
+
import type { AddonContext, QueryClient } from '@wealthfolio/addon-sdk';
|
|
884
980
|
|
|
885
981
|
// Wrap your components with QueryClientProvider
|
|
886
|
-
const MyAddonWrapper = () => {
|
|
982
|
+
const MyAddonWrapper = ({ ctx }: { ctx: AddonContext }) => {
|
|
983
|
+
const addonQueryClient = ctx.api.query.getClient() as QueryClient;
|
|
984
|
+
|
|
887
985
|
return (
|
|
888
|
-
<QueryClientProvider client={
|
|
889
|
-
<MyAddonComponent />
|
|
986
|
+
<QueryClientProvider client={addonQueryClient}>
|
|
987
|
+
<MyAddonComponent ctx={ctx} />
|
|
890
988
|
</QueryClientProvider>
|
|
891
989
|
);
|
|
892
990
|
};
|
|
893
991
|
|
|
894
992
|
// Use React Query hooks in your components
|
|
895
|
-
function MyAddonComponent() {
|
|
993
|
+
function MyAddonComponent({ ctx }: { ctx: AddonContext }) {
|
|
896
994
|
const { data: accounts, isLoading } = useQuery({
|
|
897
995
|
queryKey: ['accounts'],
|
|
898
996
|
queryFn: () => ctx.api.accounts.getAll()
|
|
899
997
|
});
|
|
900
998
|
|
|
999
|
+
const selectedAccountId = accounts?.[0]?.id;
|
|
901
1000
|
const { data: holdings } = useQuery({
|
|
902
1001
|
queryKey: ['holdings', selectedAccountId],
|
|
903
|
-
queryFn: () =>
|
|
1002
|
+
queryFn: () =>
|
|
1003
|
+
selectedAccountId
|
|
1004
|
+
? ctx.api.portfolio.getHoldings(selectedAccountId)
|
|
1005
|
+
: Promise.resolve([]),
|
|
904
1006
|
enabled: !!selectedAccountId
|
|
905
1007
|
});
|
|
906
1008
|
|
|
@@ -908,15 +1010,24 @@ function MyAddonComponent() {
|
|
|
908
1010
|
}
|
|
909
1011
|
```
|
|
910
1012
|
|
|
911
|
-
**Benefits of
|
|
1013
|
+
**Benefits of the sandbox-scoped QueryClient:**
|
|
912
1014
|
|
|
913
|
-
- **
|
|
914
|
-
- **
|
|
915
|
-
- **
|
|
916
|
-
- **
|
|
1015
|
+
- **Isolation**: Cached financial data and observers do not leak across addons
|
|
1016
|
+
- **Route continuity**: One cache is retained across the addon's pages
|
|
1017
|
+
- **Coordination**: Addon invalidations/refetches are also sent to the host
|
|
1018
|
+
- **Lifecycle cleanup**: The cache is cleared with the addon sandbox
|
|
1019
|
+
|
|
1020
|
+
Host-originated invalidations do not mutate the addon cache automatically. Use
|
|
1021
|
+
the relevant `ctx.api.events` subscription and invalidate locally when the addon
|
|
1022
|
+
must react to changes initiated elsewhere.
|
|
917
1023
|
|
|
918
1024
|
## 🔄 Migration Guide
|
|
919
1025
|
|
|
1026
|
+
For Wealthfolio 3.7, see the
|
|
1027
|
+
[v3.6 to v3.7 migration guide](../../docs/addons/addon-migration-guide-v3.6-to-v3.7.md).
|
|
1028
|
+
It covers backward compatibility, the private asset registry, CSS behavior, and
|
|
1029
|
+
the required development-tools upgrade.
|
|
1030
|
+
|
|
920
1031
|
### From v1.0.0 to v1.1.0
|
|
921
1032
|
|
|
922
1033
|
#### Context Access
|
|
@@ -925,9 +1036,14 @@ function MyAddonComponent() {
|
|
|
925
1036
|
// Before
|
|
926
1037
|
import ctx from '@wealthfolio/addon-sdk';
|
|
927
1038
|
|
|
928
|
-
//
|
|
929
|
-
import {
|
|
930
|
-
|
|
1039
|
+
// Current SDK
|
|
1040
|
+
import type { AddonEnableFunction } from '@wealthfolio/addon-sdk';
|
|
1041
|
+
|
|
1042
|
+
const enable: AddonEnableFunction = (ctx) => {
|
|
1043
|
+
// Pass ctx to components, hooks, and helper functions that need host APIs.
|
|
1044
|
+
};
|
|
1045
|
+
|
|
1046
|
+
export default enable;
|
|
931
1047
|
```
|
|
932
1048
|
|
|
933
1049
|
#### Type Imports
|
|
@@ -1008,6 +1124,7 @@ import { resolve } from 'path';
|
|
|
1008
1124
|
export default defineConfig({
|
|
1009
1125
|
plugins: [react()],
|
|
1010
1126
|
build: {
|
|
1127
|
+
target: ['chrome107', 'edge107', 'firefox104', 'safari16'],
|
|
1011
1128
|
lib: {
|
|
1012
1129
|
entry: resolve(__dirname, 'src/index.ts'),
|
|
1013
1130
|
name: 'MyPortfolioAddon',
|
|
@@ -1153,9 +1270,9 @@ npm publish --tag beta
|
|
|
1153
1270
|
|
|
1154
1271
|
```typescript
|
|
1155
1272
|
// In your addon
|
|
1156
|
-
|
|
1157
|
-
ctx.api.logger.
|
|
1158
|
-
|
|
1273
|
+
function logDebug(ctx: AddonContext, data: unknown) {
|
|
1274
|
+
ctx.api.logger.debug(`Debug information: ${JSON.stringify(data)}`);
|
|
1275
|
+
}
|
|
1159
1276
|
```
|
|
1160
1277
|
|
|
1161
1278
|
#### 2. Development Console
|
|
@@ -1186,11 +1303,9 @@ if (process.env.NODE_ENV === 'development') {
|
|
|
1186
1303
|
#### 1. Error Handling
|
|
1187
1304
|
|
|
1188
1305
|
```typescript
|
|
1189
|
-
import {
|
|
1190
|
-
|
|
1191
|
-
async function fetchPortfolioData() {
|
|
1192
|
-
const ctx = getAddonContext();
|
|
1306
|
+
import type { AddonContext } from '@wealthfolio/addon-sdk';
|
|
1193
1307
|
|
|
1308
|
+
async function fetchPortfolioData(ctx: AddonContext) {
|
|
1194
1309
|
try {
|
|
1195
1310
|
// Get all accounts first, then holdings for each
|
|
1196
1311
|
const accounts = await ctx.api.accounts.getAll();
|
|
@@ -1199,14 +1314,8 @@ async function fetchPortfolioData() {
|
|
|
1199
1314
|
).then((results) => results.flat());
|
|
1200
1315
|
return holdings;
|
|
1201
1316
|
} catch (error) {
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
// Handle different error types
|
|
1205
|
-
if (error.code === 'PERMISSION_DENIED') {
|
|
1206
|
-
// Show permission error to user
|
|
1207
|
-
} else if (error.code === 'NETWORK_ERROR') {
|
|
1208
|
-
// Handle network issues
|
|
1209
|
-
}
|
|
1317
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1318
|
+
ctx.api.logger.error(`Failed to fetch holdings: ${message}`);
|
|
1210
1319
|
|
|
1211
1320
|
throw error;
|
|
1212
1321
|
}
|
|
@@ -1270,17 +1379,15 @@ const HeavyChart = lazy(() => import('./components/HeavyChart'));
|
|
|
1270
1379
|
|
|
1271
1380
|
```typescript
|
|
1272
1381
|
// Use React Query or SWR for caching
|
|
1273
|
-
import { useQuery } from 'react-query';
|
|
1382
|
+
import { useQuery } from '@tanstack/react-query';
|
|
1274
1383
|
|
|
1275
1384
|
function usePortfolioData(accountId: string) {
|
|
1276
|
-
return useQuery(
|
|
1277
|
-
['portfolio', accountId],
|
|
1278
|
-
() => ctx.api.portfolio.getHoldings(accountId),
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
},
|
|
1283
|
-
);
|
|
1385
|
+
return useQuery({
|
|
1386
|
+
queryKey: ['portfolio', accountId],
|
|
1387
|
+
queryFn: () => ctx.api.portfolio.getHoldings(accountId),
|
|
1388
|
+
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
1389
|
+
gcTime: 10 * 60 * 1000, // 10 minutes
|
|
1390
|
+
});
|
|
1284
1391
|
}
|
|
1285
1392
|
```
|
|
1286
1393
|
|
|
@@ -1290,6 +1397,7 @@ function usePortfolioData(accountId: string) {
|
|
|
1290
1397
|
// vite.config.ts - optimize chunks
|
|
1291
1398
|
export default defineConfig({
|
|
1292
1399
|
build: {
|
|
1400
|
+
target: ['chrome107', 'edge107', 'firefox104', 'safari16'],
|
|
1293
1401
|
rollupOptions: {
|
|
1294
1402
|
output: {
|
|
1295
1403
|
manualChunks: {
|
|
@@ -1382,7 +1490,8 @@ We follow [Semantic Versioning](https://semver.org/) (SemVer):
|
|
|
1382
1490
|
|
|
1383
1491
|
| SDK Version | Wealthfolio Version | Node.js | React |
|
|
1384
1492
|
| ----------- | ------------------- | --------- | ------- |
|
|
1385
|
-
|
|
|
1493
|
+
| 3.8.x | >= 3.8.0 | >= 20.0.0 | ^19.2.4 |
|
|
1494
|
+
| 3.7.x | >= 3.7.0 | >= 20.0.0 | ^19.2.4 |
|
|
1386
1495
|
| 0.9.x | >= 0.9.0 | >= 16.0.0 | ^17.0.0 |
|
|
1387
1496
|
|
|
1388
1497
|
### Installation from Registry
|
|
@@ -1394,10 +1503,10 @@ We follow [Semantic Versioning](https://semver.org/) (SemVer):
|
|
|
1394
1503
|
npm install @wealthfolio/addon-sdk
|
|
1395
1504
|
|
|
1396
1505
|
# Specific version
|
|
1397
|
-
npm install @wealthfolio/addon-sdk@
|
|
1506
|
+
npm install @wealthfolio/addon-sdk@3.8.0
|
|
1398
1507
|
|
|
1399
1508
|
# Version range
|
|
1400
|
-
npm install @wealthfolio/addon-sdk@^
|
|
1509
|
+
npm install @wealthfolio/addon-sdk@^3.8.0
|
|
1401
1510
|
```
|
|
1402
1511
|
|
|
1403
1512
|
#### Beta/Preview Releases
|
|
@@ -1633,6 +1742,7 @@ npm list react react-dom
|
|
|
1633
1742
|
// vite.config.ts
|
|
1634
1743
|
export default defineConfig({
|
|
1635
1744
|
build: {
|
|
1745
|
+
target: ['chrome107', 'edge107', 'firefox104', 'safari16'],
|
|
1636
1746
|
rollupOptions: {
|
|
1637
1747
|
external: ['react', 'react-dom', '@wealthfolio/addon-sdk'],
|
|
1638
1748
|
},
|
|
@@ -1652,31 +1762,29 @@ export default defineConfig({
|
|
|
1652
1762
|
"permissions": [
|
|
1653
1763
|
{
|
|
1654
1764
|
"category": "portfolio",
|
|
1655
|
-
"functions": ["
|
|
1765
|
+
"functions": ["getHoldings"],
|
|
1656
1766
|
"purpose": "Access portfolio data for analytics"
|
|
1657
1767
|
}
|
|
1658
1768
|
]
|
|
1659
1769
|
}
|
|
1660
1770
|
```
|
|
1661
1771
|
|
|
1662
|
-
#### 6. Context Not Available
|
|
1772
|
+
#### 6. Context Not Available in a Component or Helper
|
|
1663
1773
|
|
|
1664
|
-
**Error**:
|
|
1774
|
+
**Error**: A component or helper cannot access the addon context.
|
|
1665
1775
|
|
|
1666
1776
|
**Solutions**:
|
|
1667
1777
|
|
|
1668
1778
|
```typescript
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
const ctx = getAddonContext();
|
|
1674
|
-
// ... use context
|
|
1675
|
-
}, []);
|
|
1779
|
+
import type { AddonContext, AddonEnableFunction } from '@wealthfolio/addon-sdk';
|
|
1780
|
+
|
|
1781
|
+
function MyComponent({ ctx }: { ctx: AddonContext }) {
|
|
1782
|
+
return <button onClick={() => ctx.api.toast.success('Ready')}>Test API</button>;
|
|
1676
1783
|
}
|
|
1677
1784
|
|
|
1678
|
-
|
|
1679
|
-
//
|
|
1785
|
+
const enable: AddonEnableFunction = (ctx) => {
|
|
1786
|
+
// Capture ctx for a route wrapper, or pass it directly to helpers/components.
|
|
1787
|
+
};
|
|
1680
1788
|
```
|
|
1681
1789
|
|
|
1682
1790
|
### Development Environment Issues
|
|
@@ -1722,13 +1830,10 @@ ls -la dist/ # Should update when you save files
|
|
|
1722
1830
|
try {
|
|
1723
1831
|
const accounts = await ctx.api.accounts.getAll();
|
|
1724
1832
|
const data = await ctx.api.portfolio.getHoldings(accounts[0]?.id);
|
|
1725
|
-
ctx.api.logger.info(
|
|
1833
|
+
ctx.api.logger.info(`Data loaded successfully (${data.length} holdings)`);
|
|
1726
1834
|
} catch (error) {
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
stack: error.stack,
|
|
1730
|
-
timestamp: new Date().toISOString(),
|
|
1731
|
-
});
|
|
1835
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1836
|
+
ctx.api.logger.error(`API call failed: ${message}`);
|
|
1732
1837
|
}
|
|
1733
1838
|
```
|
|
1734
1839
|
|
|
@@ -1744,6 +1849,7 @@ const HeavyComponent = lazy(() => import('./HeavyComponent'));
|
|
|
1744
1849
|
// vite.config.ts
|
|
1745
1850
|
export default defineConfig({
|
|
1746
1851
|
build: {
|
|
1852
|
+
target: ['chrome107', 'edge107', 'firefox104', 'safari16'],
|
|
1747
1853
|
rollupOptions: {
|
|
1748
1854
|
output: {
|
|
1749
1855
|
manualChunks: {
|