@managespace/sdk 0.1.158-extensions → 0.1.159
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/README.md +2 -723
- package/dist/extensibility/functions/project/billing.d.ts +7 -0
- package/dist/extensibility/functions/project/billing.d.ts.map +1 -1
- package/dist/extensibility/functions/project/billing.js +5 -0
- package/dist/extensibility/types/control.d.ts +1 -1
- package/dist/extensibility/types/control.d.ts.map +1 -1
- package/dist/generated/apis/default-api.d.ts +9 -1
- package/dist/generated/apis/default-api.d.ts.map +1 -1
- package/dist/generated/apis/default-api.js +28 -0
- package/dist/generated/apis/index.d.ts +0 -1
- package/dist/generated/apis/index.d.ts.map +1 -1
- package/dist/generated/apis/index.js +0 -1
- package/dist/generated/models/index.d.ts +1 -2
- package/dist/generated/models/index.d.ts.map +1 -1
- package/dist/generated/models/index.js +1 -2
- package/dist/generated/models/payment-gateway-client-token-response.d.ts +47 -0
- package/dist/generated/models/payment-gateway-client-token-response.d.ts.map +1 -0
- package/dist/generated/models/payment-gateway-client-token-response.js +63 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/package.deploy.json +7 -3
- package/package.json +2 -5
- package/src/extensibility/functions/project/billing.ts +6 -0
- package/src/extensibility/types/control.ts +1 -0
- package/src/generated/.openapi-generator/FILES +1 -3
- package/src/generated/apis/default-api.ts +37 -0
- package/src/generated/apis/index.ts +0 -1
- package/src/generated/models/index.ts +1 -2
- package/src/generated/models/payment-gateway-client-token-response.ts +87 -0
- package/src/index.ts +0 -1
- package/dist/extensions/host-bridge.d.ts +0 -166
- package/dist/extensions/host-bridge.d.ts.map +0 -1
- package/dist/extensions/host-bridge.js +0 -259
- package/dist/extensions/index.d.ts +0 -40
- package/dist/extensions/index.d.ts.map +0 -1
- package/dist/extensions/index.js +0 -55
- package/dist/extensions/types.d.ts +0 -111
- package/dist/extensions/types.d.ts.map +0 -1
- package/dist/extensions/types.js +0 -2
- package/dist/generated/apis/extensions-api.d.ts +0 -98
- package/dist/generated/apis/extensions-api.d.ts.map +0 -1
- package/dist/generated/apis/extensions-api.js +0 -295
- package/dist/generated/models/extension-org.d.ts +0 -64
- package/dist/generated/models/extension-org.d.ts.map +0 -1
- package/dist/generated/models/extension-org.js +0 -70
- package/dist/generated/models/extension.d.ts +0 -106
- package/dist/generated/models/extension.d.ts.map +0 -1
- package/dist/generated/models/extension.js +0 -98
- package/src/extensions/host-bridge.ts +0 -272
- package/src/extensions/index.ts +0 -40
- package/src/extensions/types.ts +0 -120
- package/src/generated/apis/extensions-api.ts +0 -362
- package/src/generated/models/extension-org.ts +0 -119
- package/src/generated/models/extension.ts +0 -182
package/README.md
CHANGED
|
@@ -1,727 +1,6 @@
|
|
|
1
1
|
# @managespace/sdk
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install @managespace/sdk
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
# Extension Development Guide
|
|
14
|
-
|
|
15
|
-
This guide explains how to build extensions that run within the ManageSpace platform.
|
|
16
|
-
|
|
17
|
-
## Overview
|
|
18
|
-
|
|
19
|
-
ManageSpace extensions are standalone web applications that run in an iframe within the ManageSpace UI. They have access to:
|
|
20
|
-
|
|
21
|
-
- **Authentication context** - User's auth token, org/site IDs
|
|
22
|
-
- **ManageSpace APIs** - Full access to the REST API
|
|
23
|
-
- **Host navigation** - Ability to navigate the main application
|
|
24
|
-
- **Toast notifications** - Display feedback to users
|
|
25
|
-
- **Entity events** - React to changes in ManageSpace data
|
|
26
|
-
|
|
27
|
-
## Quick Start
|
|
28
|
-
|
|
29
|
-
### 1. Create Project Structure
|
|
30
|
-
|
|
31
|
-
```
|
|
32
|
-
my-extension/
|
|
33
|
-
├── package.json
|
|
34
|
-
├── tsconfig.json
|
|
35
|
-
├── vite.config.ts
|
|
36
|
-
├── manifest.json
|
|
37
|
-
├── index.html
|
|
38
|
-
└── src/
|
|
39
|
-
├── main.tsx
|
|
40
|
-
├── App.tsx
|
|
41
|
-
└── styles.css
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
### 2. package.json
|
|
45
|
-
|
|
46
|
-
```json
|
|
47
|
-
{
|
|
48
|
-
"name": "my-extension",
|
|
49
|
-
"version": "1.0.0",
|
|
50
|
-
"private": true,
|
|
51
|
-
"type": "module",
|
|
52
|
-
"scripts": {
|
|
53
|
-
"dev": "vite",
|
|
54
|
-
"build": "tsc && vite build",
|
|
55
|
-
"bundle": "bun run build && cp manifest.json dist/ && cd dist && zip -r ../bundle.zip ."
|
|
56
|
-
},
|
|
57
|
-
"dependencies": {
|
|
58
|
-
"@managespace/sdk": "^0.1.0",
|
|
59
|
-
"react": "^18.3.0",
|
|
60
|
-
"react-dom": "^18.3.0"
|
|
61
|
-
},
|
|
62
|
-
"devDependencies": {
|
|
63
|
-
"@types/react": "^18.3.0",
|
|
64
|
-
"@types/react-dom": "^18.3.0",
|
|
65
|
-
"@vitejs/plugin-react": "^4.3.0",
|
|
66
|
-
"typescript": "^5.6.0",
|
|
67
|
-
"vite": "^5.4.0"
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
### 3. tsconfig.json
|
|
73
|
-
|
|
74
|
-
```json
|
|
75
|
-
{
|
|
76
|
-
"compilerOptions": {
|
|
77
|
-
"target": "ES2020",
|
|
78
|
-
"useDefineForClassFields": true,
|
|
79
|
-
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
80
|
-
"module": "ESNext",
|
|
81
|
-
"skipLibCheck": true,
|
|
82
|
-
"moduleResolution": "bundler",
|
|
83
|
-
"allowImportingTsExtensions": true,
|
|
84
|
-
"resolveJsonModule": true,
|
|
85
|
-
"isolatedModules": true,
|
|
86
|
-
"noEmit": true,
|
|
87
|
-
"jsx": "react-jsx",
|
|
88
|
-
"strict": true,
|
|
89
|
-
"noUnusedLocals": true,
|
|
90
|
-
"noUnusedParameters": true,
|
|
91
|
-
"noFallthroughCasesInSwitch": true
|
|
92
|
-
},
|
|
93
|
-
"include": ["src"]
|
|
94
|
-
}
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
### 4. vite.config.ts
|
|
98
|
-
|
|
99
|
-
```typescript
|
|
100
|
-
import react from '@vitejs/plugin-react';
|
|
101
|
-
import { defineConfig } from 'vite';
|
|
102
|
-
|
|
103
|
-
export default defineConfig({
|
|
104
|
-
plugins: [react()],
|
|
105
|
-
build: {
|
|
106
|
-
outDir: 'dist',
|
|
107
|
-
assetsDir: 'assets',
|
|
108
|
-
rollupOptions: {
|
|
109
|
-
output: {
|
|
110
|
-
entryFileNames: 'assets/[name].js',
|
|
111
|
-
chunkFileNames: 'assets/[name].js',
|
|
112
|
-
assetFileNames: 'assets/[name].[ext]',
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
});
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
### 5. manifest.json
|
|
120
|
-
|
|
121
|
-
```json
|
|
122
|
-
{
|
|
123
|
-
"name": "My Extension",
|
|
124
|
-
"version": "1.0.0",
|
|
125
|
-
"author": "Your Name",
|
|
126
|
-
"description": "Description of what your extension does",
|
|
127
|
-
"navLabel": "My Extension",
|
|
128
|
-
"navIcon": "Layout",
|
|
129
|
-
"navRoute": "/extensions/my-extension",
|
|
130
|
-
"entryPoint": "index.html"
|
|
131
|
-
}
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
**Manifest Fields:**
|
|
135
|
-
|
|
136
|
-
| Field | Required | Description |
|
|
137
|
-
|-------|----------|-------------|
|
|
138
|
-
| `name` | Yes | Display name of the extension |
|
|
139
|
-
| `version` | Yes | Semantic version (e.g., "1.0.0") |
|
|
140
|
-
| `author` | Yes | Author or company name |
|
|
141
|
-
| `description` | No | Short description |
|
|
142
|
-
| `navLabel` | Yes | Label shown in navigation menu |
|
|
143
|
-
| `navIcon` | Yes | Lucide icon name (e.g., "Users", "Settings", "BarChart") |
|
|
144
|
-
| `navRoute` | Yes | Route path, must start with "/extensions/" |
|
|
145
|
-
| `entryPoint` | Yes | Entry HTML file (usually "index.html") |
|
|
146
|
-
| `bff.url` | No | URL of your Backend for Frontend server |
|
|
147
|
-
|
|
148
|
-
### 6. index.html
|
|
149
|
-
|
|
150
|
-
```html
|
|
151
|
-
<!DOCTYPE html>
|
|
152
|
-
<html lang="en">
|
|
153
|
-
<head>
|
|
154
|
-
<meta charset="UTF-8" />
|
|
155
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
156
|
-
<title>My Extension</title>
|
|
157
|
-
</head>
|
|
158
|
-
<body>
|
|
159
|
-
<div id="root"></div>
|
|
160
|
-
<script type="module" src="/src/main.tsx"></script>
|
|
161
|
-
</body>
|
|
162
|
-
</html>
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
### 7. src/main.tsx
|
|
166
|
-
|
|
167
|
-
```typescript
|
|
168
|
-
import React from 'react';
|
|
169
|
-
import ReactDOM from 'react-dom/client';
|
|
170
|
-
import App from './App';
|
|
171
|
-
import { signalReady } from '@managespace/sdk/extensions';
|
|
172
|
-
import './styles.css';
|
|
173
|
-
|
|
174
|
-
// Signal to host that extension is ready to receive context
|
|
175
|
-
signalReady();
|
|
176
|
-
|
|
177
|
-
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
178
|
-
<React.StrictMode>
|
|
179
|
-
<App />
|
|
180
|
-
</React.StrictMode>
|
|
181
|
-
);
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
### 8. src/App.tsx
|
|
185
|
-
|
|
186
|
-
```typescript
|
|
187
|
-
import { useEffect, useState } from 'react';
|
|
188
|
-
import {
|
|
189
|
-
getContext,
|
|
190
|
-
createApiFetch,
|
|
191
|
-
navigate,
|
|
192
|
-
showToast,
|
|
193
|
-
type ExtensionContext,
|
|
194
|
-
} from '@managespace/sdk/extensions';
|
|
195
|
-
|
|
196
|
-
export default function App() {
|
|
197
|
-
const [context, setContext] = useState<ExtensionContext | null>(null);
|
|
198
|
-
const [loading, setLoading] = useState(true);
|
|
199
|
-
|
|
200
|
-
useEffect(() => {
|
|
201
|
-
getContext()
|
|
202
|
-
.then(setContext)
|
|
203
|
-
.finally(() => setLoading(false));
|
|
204
|
-
}, []);
|
|
205
|
-
|
|
206
|
-
if (loading) {
|
|
207
|
-
return <div>Loading...</div>;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (!context) {
|
|
211
|
-
return <div>Failed to receive context from host</div>;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return (
|
|
215
|
-
<div>
|
|
216
|
-
<h1>My Extension</h1>
|
|
217
|
-
<p>Organisation: {context.orgId}</p>
|
|
218
|
-
<p>User: {context.userId}</p>
|
|
219
|
-
{/* Your extension UI here */}
|
|
220
|
-
</div>
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
---
|
|
226
|
-
|
|
227
|
-
## Extension SDK API
|
|
228
|
-
|
|
229
|
-
Import from `@managespace/sdk/extensions`:
|
|
230
|
-
|
|
231
|
-
### getContext()
|
|
232
|
-
|
|
233
|
-
Get the extension context from the ManageSpace host.
|
|
234
|
-
|
|
235
|
-
```typescript
|
|
236
|
-
import { getContext } from '@managespace/sdk/extensions';
|
|
237
|
-
|
|
238
|
-
const context = await getContext();
|
|
239
|
-
// context.orgId - Current organisation ID
|
|
240
|
-
// context.userId - Current user ID
|
|
241
|
-
// context.siteId - Current site ID
|
|
242
|
-
// context.apiBaseUrl - Base URL for API calls
|
|
243
|
-
// context.bffUrl - Your BFF URL (if configured)
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
### signalReady()
|
|
247
|
-
|
|
248
|
-
Signal to the host that your extension is ready to receive context. Call this early in your extension's lifecycle.
|
|
249
|
-
|
|
250
|
-
```typescript
|
|
251
|
-
import { signalReady } from '@managespace/sdk/extensions';
|
|
252
|
-
|
|
253
|
-
signalReady();
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
### navigate(path)
|
|
257
|
-
|
|
258
|
-
Navigate the ManageSpace host to a specific path.
|
|
259
|
-
|
|
260
|
-
```typescript
|
|
261
|
-
import { navigate } from '@managespace/sdk/extensions';
|
|
262
|
-
|
|
263
|
-
// Navigate to a customer profile
|
|
264
|
-
navigate('/customer/abc-123');
|
|
265
|
-
|
|
266
|
-
// Navigate to the assets page
|
|
267
|
-
navigate('/assets');
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
### showToast(message, variant?)
|
|
271
|
-
|
|
272
|
-
Show a toast notification in the host application.
|
|
273
|
-
|
|
274
|
-
```typescript
|
|
275
|
-
import { showToast } from '@managespace/sdk/extensions';
|
|
276
|
-
|
|
277
|
-
showToast('Operation successful');
|
|
278
|
-
showToast('Something went wrong', 'error');
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
### createApiFetch(context)
|
|
282
|
-
|
|
283
|
-
Create a fetch function configured for ManageSpace API calls.
|
|
284
|
-
|
|
285
|
-
```typescript
|
|
286
|
-
import { getContext, createApiFetch } from '@managespace/sdk/extensions';
|
|
287
|
-
|
|
288
|
-
const context = await getContext();
|
|
289
|
-
const apiFetch = createApiFetch(context);
|
|
290
|
-
|
|
291
|
-
const response = await apiFetch('/api/crm/customers/queries', {
|
|
292
|
-
method: 'POST',
|
|
293
|
-
body: JSON.stringify({
|
|
294
|
-
pageOptions: { offset: 0, limit: 20 }
|
|
295
|
-
})
|
|
296
|
-
});
|
|
297
|
-
const data = await response.json();
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
### createBffFetch(context)
|
|
301
|
-
|
|
302
|
-
Create a fetch function for calling your extension's Backend for Frontend.
|
|
303
|
-
|
|
304
|
-
```typescript
|
|
305
|
-
import { getContext, createBffFetch } from '@managespace/sdk/extensions';
|
|
306
|
-
|
|
307
|
-
const context = await getContext();
|
|
308
|
-
const bffFetch = createBffFetch(context);
|
|
309
|
-
|
|
310
|
-
if (bffFetch) {
|
|
311
|
-
const response = await bffFetch('/api/my-endpoint');
|
|
312
|
-
const data = await response.json();
|
|
313
|
-
}
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
### onEntityEvent(handler)
|
|
317
|
-
|
|
318
|
-
Subscribe to entity events from the ManageSpace host.
|
|
319
|
-
|
|
320
|
-
```typescript
|
|
321
|
-
import { onEntityEvent } from '@managespace/sdk/extensions';
|
|
322
|
-
|
|
323
|
-
const unsubscribe = onEntityEvent((event) => {
|
|
324
|
-
console.log(`${event.entityType} ${event.entityId} was ${event.action}`);
|
|
325
|
-
// event.entityType: 'customer', 'asset', 'subscription', etc.
|
|
326
|
-
// event.action: 'created', 'updated', 'deleted'
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
// To stop listening:
|
|
330
|
-
unsubscribe();
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
---
|
|
334
|
-
|
|
335
|
-
## ManageSpace API Reference
|
|
336
|
-
|
|
337
|
-
Use `createApiFetch(context)` to call these endpoints. All requests include authentication automatically.
|
|
338
|
-
|
|
339
|
-
### Customers
|
|
340
|
-
|
|
341
|
-
#### List/Query Customers
|
|
342
|
-
|
|
343
|
-
```typescript
|
|
344
|
-
const response = await apiFetch('/api/crm/customers/queries', {
|
|
345
|
-
method: 'POST',
|
|
346
|
-
body: JSON.stringify({
|
|
347
|
-
pageOptions: { offset: 0, limit: 20 },
|
|
348
|
-
filters: [
|
|
349
|
-
{ field: 'name', operator: 'contains', value: 'Smith' }
|
|
350
|
-
]
|
|
351
|
-
})
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
// Response: { results: Customer[], total: number }
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
**Customer Type:**
|
|
358
|
-
```typescript
|
|
359
|
-
interface Customer {
|
|
360
|
-
id: string;
|
|
361
|
-
name: string;
|
|
362
|
-
description: string | null;
|
|
363
|
-
orgId: string;
|
|
364
|
-
createdAt: Date;
|
|
365
|
-
updatedAt: Date | null;
|
|
366
|
-
customerStatusId: string;
|
|
367
|
-
commercial: boolean;
|
|
368
|
-
contacts?: Contact[];
|
|
369
|
-
// ... additional fields
|
|
370
|
-
}
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
#### Get Customer by ID
|
|
374
|
-
|
|
375
|
-
```typescript
|
|
376
|
-
const response = await apiFetch(`/api/crm/customers/${customerId}`);
|
|
377
|
-
const customer = await response.json();
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
### Assets
|
|
381
|
-
|
|
382
|
-
#### List Assets
|
|
383
|
-
|
|
384
|
-
```typescript
|
|
385
|
-
const response = await apiFetch('/api/assets');
|
|
386
|
-
// Response: { results: Asset[], total: number }
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
#### Get Asset by ID
|
|
390
|
-
|
|
391
|
-
```typescript
|
|
392
|
-
const response = await apiFetch(`/api/assets/${assetId}`);
|
|
393
|
-
const asset = await response.json();
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
**Asset Type:**
|
|
397
|
-
```typescript
|
|
398
|
-
interface Asset {
|
|
399
|
-
id: string;
|
|
400
|
-
name: string;
|
|
401
|
-
siteId: string;
|
|
402
|
-
assetClassId: string;
|
|
403
|
-
status: string;
|
|
404
|
-
width: number | null;
|
|
405
|
-
height: number | null;
|
|
406
|
-
depth: number | null;
|
|
407
|
-
// ... additional fields
|
|
408
|
-
}
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
### Subscriptions
|
|
412
|
-
|
|
413
|
-
#### List Subscriptions
|
|
414
|
-
|
|
415
|
-
```typescript
|
|
416
|
-
const response = await apiFetch('/api/subscriptions/queries', {
|
|
417
|
-
method: 'POST',
|
|
418
|
-
body: JSON.stringify({
|
|
419
|
-
pageOptions: { offset: 0, limit: 20 }
|
|
420
|
-
})
|
|
421
|
-
});
|
|
422
|
-
// Response: { results: Subscription[], total: number }
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
**Subscription Type:**
|
|
426
|
-
```typescript
|
|
427
|
-
interface Subscription {
|
|
428
|
-
id: string;
|
|
429
|
-
customerId: string;
|
|
430
|
-
assetId: string;
|
|
431
|
-
planId: string;
|
|
432
|
-
status: string;
|
|
433
|
-
startDate: Date;
|
|
434
|
-
endDate: Date | null;
|
|
435
|
-
// ... additional fields
|
|
436
|
-
}
|
|
437
|
-
```
|
|
438
|
-
|
|
439
|
-
### Sites
|
|
440
|
-
|
|
441
|
-
#### List Sites
|
|
442
|
-
|
|
443
|
-
```typescript
|
|
444
|
-
const response = await apiFetch('/api/sites');
|
|
445
|
-
// Response: { results: Site[], total: number }
|
|
446
|
-
```
|
|
447
|
-
|
|
448
|
-
### Invoices
|
|
449
|
-
|
|
450
|
-
#### List Invoices
|
|
451
|
-
|
|
452
|
-
```typescript
|
|
453
|
-
const response = await apiFetch('/api/billing/invoices/queries', {
|
|
454
|
-
method: 'POST',
|
|
455
|
-
body: JSON.stringify({
|
|
456
|
-
pageOptions: { offset: 0, limit: 20 }
|
|
457
|
-
})
|
|
458
|
-
});
|
|
459
|
-
// Response: { results: Invoice[], total: number }
|
|
460
|
-
```
|
|
461
|
-
|
|
462
|
-
### Payments
|
|
463
|
-
|
|
464
|
-
#### List Payments
|
|
465
|
-
|
|
466
|
-
```typescript
|
|
467
|
-
const response = await apiFetch('/api/billing/payments/queries', {
|
|
468
|
-
method: 'POST',
|
|
469
|
-
body: JSON.stringify({
|
|
470
|
-
pageOptions: { offset: 0, limit: 20 }
|
|
471
|
-
})
|
|
472
|
-
});
|
|
473
|
-
// Response: { results: Payment[], total: number }
|
|
474
|
-
```
|
|
475
|
-
|
|
476
|
-
---
|
|
477
|
-
|
|
478
|
-
## Building a Backend for Frontend (BFF)
|
|
479
|
-
|
|
480
|
-
If your extension needs to call external APIs, perform complex data processing, or keep secrets secure, you can create a BFF.
|
|
481
|
-
|
|
482
|
-
### 1. Update manifest.json
|
|
483
|
-
|
|
484
|
-
```json
|
|
485
|
-
{
|
|
486
|
-
"name": "My Extension",
|
|
487
|
-
"version": "1.0.0",
|
|
488
|
-
"author": "Your Name",
|
|
489
|
-
"navLabel": "My Extension",
|
|
490
|
-
"navIcon": "Layout",
|
|
491
|
-
"navRoute": "/extensions/my-extension",
|
|
492
|
-
"entryPoint": "index.html",
|
|
493
|
-
"bff": {
|
|
494
|
-
"url": "https://my-bff.example.com"
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
```
|
|
498
|
-
|
|
499
|
-
### 2. Create BFF Server
|
|
500
|
-
|
|
501
|
-
Using Hono (recommended):
|
|
502
|
-
|
|
503
|
-
```typescript
|
|
504
|
-
// server.ts
|
|
505
|
-
import { Hono } from 'hono';
|
|
506
|
-
import { cors } from 'hono/cors';
|
|
507
|
-
|
|
508
|
-
const app = new Hono();
|
|
509
|
-
|
|
510
|
-
// Enable CORS for your ManageSpace instance
|
|
511
|
-
app.use('*', cors({
|
|
512
|
-
origin: ['https://your-managespace-instance.com'],
|
|
513
|
-
credentials: true,
|
|
514
|
-
}));
|
|
515
|
-
|
|
516
|
-
app.get('/api/my-endpoint', async (c) => {
|
|
517
|
-
// Get auth cookie forwarded from extension
|
|
518
|
-
const cookie = c.req.header('Cookie');
|
|
519
|
-
|
|
520
|
-
if (!cookie) {
|
|
521
|
-
return c.json({ error: 'Not authenticated' }, 401);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
// Call ManageSpace API with forwarded credentials
|
|
525
|
-
const response = await fetch('https://your-instance/gateway/api/crm/customers/queries', {
|
|
526
|
-
method: 'POST',
|
|
527
|
-
headers: {
|
|
528
|
-
Cookie: cookie,
|
|
529
|
-
'Content-Type': 'application/json',
|
|
530
|
-
},
|
|
531
|
-
body: JSON.stringify({ pageOptions: { offset: 0, limit: 20 } }),
|
|
532
|
-
});
|
|
533
|
-
|
|
534
|
-
const data = await response.json();
|
|
535
|
-
|
|
536
|
-
// Enrich or transform data
|
|
537
|
-
const enrichedData = data.results.map(customer => ({
|
|
538
|
-
...customer,
|
|
539
|
-
customField: 'added by BFF',
|
|
540
|
-
}));
|
|
541
|
-
|
|
542
|
-
return c.json({ customers: enrichedData });
|
|
543
|
-
});
|
|
544
|
-
|
|
545
|
-
export default {
|
|
546
|
-
port: 4000,
|
|
547
|
-
fetch: app.fetch,
|
|
548
|
-
};
|
|
549
|
-
```
|
|
550
|
-
|
|
551
|
-
### 3. Call BFF from Extension
|
|
552
|
-
|
|
553
|
-
```typescript
|
|
554
|
-
import { getContext, createBffFetch } from '@managespace/sdk/extensions';
|
|
555
|
-
|
|
556
|
-
const context = await getContext();
|
|
557
|
-
const bffFetch = createBffFetch(context);
|
|
558
|
-
|
|
559
|
-
if (bffFetch) {
|
|
560
|
-
const response = await bffFetch('/api/my-endpoint');
|
|
561
|
-
const data = await response.json();
|
|
562
|
-
}
|
|
563
|
-
```
|
|
564
|
-
|
|
565
|
-
---
|
|
566
|
-
|
|
567
|
-
## Building and Packaging
|
|
568
|
-
|
|
569
|
-
### Development
|
|
570
|
-
|
|
571
|
-
```bash
|
|
572
|
-
npm run dev
|
|
573
|
-
```
|
|
574
|
-
|
|
575
|
-
This starts a local dev server. During development, the extension runs standalone and waits for context from the host.
|
|
576
|
-
|
|
577
|
-
### Build
|
|
578
|
-
|
|
579
|
-
```bash
|
|
580
|
-
npm run build
|
|
581
|
-
```
|
|
582
|
-
|
|
583
|
-
Creates a production build in `dist/`.
|
|
584
|
-
|
|
585
|
-
### Package for Upload
|
|
586
|
-
|
|
587
|
-
```bash
|
|
588
|
-
npm run bundle
|
|
589
|
-
```
|
|
590
|
-
|
|
591
|
-
Creates `bundle.zip` containing:
|
|
592
|
-
- `index.html`
|
|
593
|
-
- `assets/` (JS, CSS)
|
|
594
|
-
- `manifest.json`
|
|
595
|
-
|
|
596
|
-
Upload this ZIP file through the ManageSpace Admin > Extensions page.
|
|
597
|
-
|
|
598
|
-
---
|
|
599
|
-
|
|
600
|
-
## Available Lucide Icons
|
|
601
|
-
|
|
602
|
-
For `navIcon` in manifest.json, use any Lucide icon name:
|
|
603
|
-
|
|
604
|
-
**Common icons:**
|
|
605
|
-
- `Users` - People/customers
|
|
606
|
-
- `Building` - Properties/sites
|
|
607
|
-
- `Box` - Assets/inventory
|
|
608
|
-
- `CreditCard` - Payments/billing
|
|
609
|
-
- `FileText` - Documents/invoices
|
|
610
|
-
- `Settings` - Configuration
|
|
611
|
-
- `BarChart` - Analytics/reports
|
|
612
|
-
- `Calendar` - Scheduling
|
|
613
|
-
- `Bell` - Notifications
|
|
614
|
-
- `Search` - Search functionality
|
|
615
|
-
- `Layout` - Dashboard/overview
|
|
616
|
-
- `Truck` - Delivery/logistics
|
|
617
|
-
- `Key` - Access/security
|
|
618
|
-
- `Mail` - Communications
|
|
619
|
-
|
|
620
|
-
See https://lucide.dev/icons for the full list.
|
|
621
|
-
|
|
622
|
-
---
|
|
623
|
-
|
|
624
|
-
## TypeScript Types
|
|
625
|
-
|
|
626
|
-
All types are exported from `@managespace/sdk/extensions`:
|
|
627
|
-
|
|
628
|
-
```typescript
|
|
629
|
-
import type {
|
|
630
|
-
ExtensionContext,
|
|
631
|
-
ExtensionManifest,
|
|
632
|
-
HostMessage,
|
|
633
|
-
ExtensionMessage,
|
|
634
|
-
EntityEvent,
|
|
635
|
-
EntityEventHandler,
|
|
636
|
-
} from '@managespace/sdk/extensions';
|
|
637
|
-
```
|
|
638
|
-
|
|
639
|
-
Entity types are available from the main SDK:
|
|
640
|
-
|
|
641
|
-
```typescript
|
|
642
|
-
import type {
|
|
643
|
-
Customer,
|
|
644
|
-
Asset,
|
|
645
|
-
Subscription,
|
|
646
|
-
Site,
|
|
647
|
-
Invoice,
|
|
648
|
-
Payment,
|
|
649
|
-
Contact,
|
|
650
|
-
} from '@managespace/sdk';
|
|
651
|
-
```
|
|
652
|
-
|
|
653
|
-
---
|
|
654
|
-
|
|
655
|
-
## Example: Customer List Extension
|
|
656
|
-
|
|
657
|
-
Complete example showing customers with click-to-navigate:
|
|
658
|
-
|
|
659
|
-
```typescript
|
|
660
|
-
// src/App.tsx
|
|
661
|
-
import { useEffect, useState } from 'react';
|
|
662
|
-
import {
|
|
663
|
-
getContext,
|
|
664
|
-
createApiFetch,
|
|
665
|
-
navigate,
|
|
666
|
-
showToast,
|
|
667
|
-
type ExtensionContext,
|
|
668
|
-
} from '@managespace/sdk/extensions';
|
|
669
|
-
import type { Customer } from '@managespace/sdk';
|
|
670
|
-
|
|
671
|
-
export default function App() {
|
|
672
|
-
const [context, setContext] = useState<ExtensionContext | null>(null);
|
|
673
|
-
const [customers, setCustomers] = useState<Customer[]>([]);
|
|
674
|
-
const [loading, setLoading] = useState(true);
|
|
675
|
-
|
|
676
|
-
useEffect(() => {
|
|
677
|
-
getContext().then(setContext);
|
|
678
|
-
}, []);
|
|
679
|
-
|
|
680
|
-
useEffect(() => {
|
|
681
|
-
if (!context) return;
|
|
682
|
-
|
|
683
|
-
const apiFetch = createApiFetch(context);
|
|
684
|
-
|
|
685
|
-
apiFetch('/api/crm/customers/queries', {
|
|
686
|
-
method: 'POST',
|
|
687
|
-
body: JSON.stringify({
|
|
688
|
-
pageOptions: { offset: 0, limit: 20 }
|
|
689
|
-
})
|
|
690
|
-
})
|
|
691
|
-
.then(res => res.json())
|
|
692
|
-
.then(data => setCustomers(data.results || []))
|
|
693
|
-
.catch(() => showToast('Failed to load customers', 'error'))
|
|
694
|
-
.finally(() => setLoading(false));
|
|
695
|
-
}, [context]);
|
|
696
|
-
|
|
697
|
-
if (loading) return <div>Loading...</div>;
|
|
698
|
-
|
|
699
|
-
return (
|
|
700
|
-
<div>
|
|
701
|
-
<h1>Customers</h1>
|
|
702
|
-
<ul>
|
|
703
|
-
{customers.map(customer => (
|
|
704
|
-
<li
|
|
705
|
-
key={customer.id}
|
|
706
|
-
onClick={() => navigate(`/customer/${customer.id}`)}
|
|
707
|
-
style={{ cursor: 'pointer' }}
|
|
708
|
-
>
|
|
709
|
-
{customer.name}
|
|
710
|
-
</li>
|
|
711
|
-
))}
|
|
712
|
-
</ul>
|
|
713
|
-
</div>
|
|
714
|
-
);
|
|
715
|
-
}
|
|
716
|
-
```
|
|
717
|
-
|
|
718
|
-
---
|
|
719
|
-
|
|
720
|
-
## Publishing the SDK
|
|
721
|
-
|
|
722
|
-
```bash
|
|
723
|
-
# Update version in package.json
|
|
724
|
-
npm install
|
|
3
|
+
update version number in package.json
|
|
4
|
+
npm i
|
|
725
5
|
npm run build
|
|
726
6
|
npm publish
|
|
727
|
-
```
|