@stackable-labs/mcp-app-extension 0.7.3 → 0.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.js +93 -19
- package/dist/server.js +93 -19
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -43,12 +43,13 @@ export const appStore = createStore<AppState>({
|
|
|
43
43
|
{
|
|
44
44
|
path: "surfaces/Content.tsx",
|
|
45
45
|
title: "Content Surface with Loading State",
|
|
46
|
-
code: `import { ui, useStore, useContextData, Surface } from '@stackable-labs/sdk-extension-react'
|
|
46
|
+
code: `import { ui, useStore, useContextData, useSettings, Surface } from '@stackable-labs/sdk-extension-react'
|
|
47
47
|
import { appStore } from '../store'
|
|
48
48
|
|
|
49
49
|
export function Content() {
|
|
50
50
|
const viewState = useStore(appStore, (s) => s.viewState)
|
|
51
51
|
const { loading } = useContextData()
|
|
52
|
+
const settings = useSettings() // Non-secret settings from settingsSchema
|
|
52
53
|
|
|
53
54
|
if (loading) {
|
|
54
55
|
return (
|
|
@@ -143,19 +144,33 @@ export function createApi(query: QueryFn) {
|
|
|
143
144
|
}
|
|
144
145
|
|
|
145
146
|
// \u2500\u2500 data.fetch wrapper \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
147
|
+
//
|
|
148
|
+
// For API keys and secrets, use {{settings.xxx}} placeholders in headers.
|
|
149
|
+
// The proxy resolves them server-side \u2014 the real secret never enters extension code.
|
|
150
|
+
// Declare secret fields in manifest.json settingsSchema with "secret": true.
|
|
146
151
|
|
|
147
152
|
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL as string
|
|
148
153
|
|
|
149
154
|
export function createFetchApi(fetch: FetchFn) {
|
|
150
155
|
return {
|
|
151
156
|
async getItems(): Promise<unknown[]> {
|
|
152
|
-
const result = await fetch(\`\${API_BASE_URL}/items\`, {
|
|
157
|
+
const result = await fetch(\`\${API_BASE_URL}/items\`, {
|
|
158
|
+
method: 'GET',
|
|
159
|
+
headers: {
|
|
160
|
+
'X-API-Key': '{{settings.apiKey}}',
|
|
161
|
+
},
|
|
162
|
+
})
|
|
153
163
|
if (!result.ok) throw new Error(\`getItems failed: \${result.status}\`)
|
|
154
164
|
return result.data as unknown[]
|
|
155
165
|
},
|
|
156
166
|
|
|
157
167
|
async getItem(itemId: string): Promise<unknown> {
|
|
158
|
-
const result = await fetch(\`\${API_BASE_URL}/items/\${itemId}\`, {
|
|
168
|
+
const result = await fetch(\`\${API_BASE_URL}/items/\${itemId}\`, {
|
|
169
|
+
method: 'GET',
|
|
170
|
+
headers: {
|
|
171
|
+
'X-API-Key': '{{settings.apiKey}}',
|
|
172
|
+
},
|
|
173
|
+
})
|
|
159
174
|
if (!result.ok) throw new Error(\`getItem failed: \${result.status}\`)
|
|
160
175
|
return result.data as unknown
|
|
161
176
|
},
|
|
@@ -182,12 +197,13 @@ export const appStore = createStore<AppState>({
|
|
|
182
197
|
{
|
|
183
198
|
path: "surfaces/Content.tsx",
|
|
184
199
|
title: "Current Content Surface",
|
|
185
|
-
code: `import { ui, useStore, useContextData, Surface } from '@stackable-labs/sdk-extension-react'
|
|
200
|
+
code: `import { ui, useStore, useContextData, useSettings, Surface } from '@stackable-labs/sdk-extension-react'
|
|
186
201
|
import { appStore } from '../store'
|
|
187
202
|
|
|
188
203
|
export function Content() {
|
|
189
204
|
const viewState = useStore(appStore, (s) => s.viewState)
|
|
190
205
|
const { loading } = useContextData()
|
|
206
|
+
const settings = useSettings() // Non-secret settings from settingsSchema
|
|
191
207
|
|
|
192
208
|
if (loading) {
|
|
193
209
|
return (
|
|
@@ -251,19 +267,33 @@ export function createApi(query: QueryFn) {
|
|
|
251
267
|
}
|
|
252
268
|
|
|
253
269
|
// \u2500\u2500 data.fetch wrapper \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
270
|
+
//
|
|
271
|
+
// For API keys and secrets, use {{settings.xxx}} placeholders in headers.
|
|
272
|
+
// The proxy resolves them server-side \u2014 the real secret never enters extension code.
|
|
273
|
+
// Declare secret fields in manifest.json settingsSchema with "secret": true.
|
|
254
274
|
|
|
255
275
|
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL as string
|
|
256
276
|
|
|
257
277
|
export function createFetchApi(fetch: FetchFn) {
|
|
258
278
|
return {
|
|
259
279
|
async getItems(): Promise<unknown[]> {
|
|
260
|
-
const result = await fetch(\`\${API_BASE_URL}/items\`, {
|
|
280
|
+
const result = await fetch(\`\${API_BASE_URL}/items\`, {
|
|
281
|
+
method: 'GET',
|
|
282
|
+
headers: {
|
|
283
|
+
'X-API-Key': '{{settings.apiKey}}',
|
|
284
|
+
},
|
|
285
|
+
})
|
|
261
286
|
if (!result.ok) throw new Error(\`getItems failed: \${result.status}\`)
|
|
262
287
|
return result.data as unknown[]
|
|
263
288
|
},
|
|
264
289
|
|
|
265
290
|
async getItem(itemId: string): Promise<unknown> {
|
|
266
|
-
const result = await fetch(\`\${API_BASE_URL}/items/\${itemId}\`, {
|
|
291
|
+
const result = await fetch(\`\${API_BASE_URL}/items/\${itemId}\`, {
|
|
292
|
+
method: 'GET',
|
|
293
|
+
headers: {
|
|
294
|
+
'X-API-Key': '{{settings.apiKey}}',
|
|
295
|
+
},
|
|
296
|
+
})
|
|
267
297
|
if (!result.ok) throw new Error(\`getItem failed: \${result.status}\`)
|
|
268
298
|
return result.data as unknown
|
|
269
299
|
},
|
|
@@ -463,8 +493,8 @@ const result = await capabilities.data.query<Customer>({
|
|
|
463
493
|
})
|
|
464
494
|
\`\`\`
|
|
465
495
|
|
|
466
|
-
## data.fetch \u2014
|
|
467
|
-
|
|
496
|
+
## data.fetch \u2014 HTTP Requests to External APIs
|
|
497
|
+
Make HTTP requests to external APIs. Domain must be in \`allowedDomains\` in manifest. Requests are proxied through the Stackable platform server.
|
|
468
498
|
- **Permission required:** \`data:fetch\`
|
|
469
499
|
- **Usage:** \`capabilities.data.fetch(url: string, init?: FetchRequestInit): Promise<FetchResponse>\`
|
|
470
500
|
- **FetchRequestInit:** \`{ method?: 'GET'|'POST'|'PUT'|'PATCH'|'DELETE', headers?: Record<string,string>, body?: unknown }\`
|
|
@@ -480,21 +510,52 @@ if (!result.ok) throw new Error(\`Request failed: \${result.status}\`)
|
|
|
480
510
|
const data = result.data as MyType
|
|
481
511
|
\`\`\`
|
|
482
512
|
|
|
513
|
+
### Secret injection via \`{{settings.xxx}}\` placeholders
|
|
514
|
+
For API keys and tokens stored as \`secret: true\` fields in \`settingsSchema\`, use template placeholders in header values. The proxy resolves them server-side \u2014 **the real secret never enters extension code**.
|
|
515
|
+
|
|
516
|
+
\`\`\`tsx
|
|
517
|
+
const result = await capabilities.data.fetch('https://api.example.com/orders', {
|
|
518
|
+
method: 'GET',
|
|
519
|
+
headers: {
|
|
520
|
+
'X-API-Key': '{{settings.apiKey}}',
|
|
521
|
+
'Authorization': 'Bearer {{settings.token}}',
|
|
522
|
+
},
|
|
523
|
+
})
|
|
524
|
+
\`\`\`
|
|
525
|
+
|
|
526
|
+
- Placeholders are only allowed in **header values** (not URLs, header names, or body)
|
|
527
|
+
- For \`required: true\` secret fields, the proxy returns 400 if the value is not configured
|
|
528
|
+
- For optional secret fields, the entire header is omitted if the value is not configured
|
|
529
|
+
- Declare secret fields in your \`manifest.json\` \`settingsSchema\` with \`"secret": true\`
|
|
530
|
+
|
|
483
531
|
## context.read \u2014 Read Host Context
|
|
484
|
-
Read host-provided context (customer ID, email, etc.).
|
|
532
|
+
Read host-provided context (customer ID, email, extension settings, etc.).
|
|
485
533
|
- **Permission required:** \`context:read\`
|
|
486
534
|
- **Usage:** \`capabilities.context.read(): Promise<ContextData>\`
|
|
487
|
-
- **ContextData shape:** \`{ customerId?: string, customerEmail?: string, [key: string]: unknown }\`
|
|
488
|
-
- **Convenience
|
|
535
|
+
- **ContextData shape:** \`{ customerId?: string, customerEmail?: string, settings?: Record<string, unknown>, [key: string]: unknown }\`
|
|
536
|
+
- **Convenience hooks:**
|
|
537
|
+
- \`useContextData()\` returns \`ContextData & { loading: boolean }\`
|
|
538
|
+
- \`useSettings()\` returns \`Record<string, unknown>\` \u2014 shorthand for \`contextData.settings ?? {}\`
|
|
489
539
|
|
|
490
540
|
\`\`\`tsx
|
|
491
|
-
//
|
|
492
|
-
const { loading, customerId, customerEmail } = useContextData()
|
|
541
|
+
// Read all context (customer + settings)
|
|
542
|
+
const { loading, customerId, customerEmail, settings } = useContextData()
|
|
543
|
+
|
|
544
|
+
// Read only extension settings (convenience)
|
|
545
|
+
const settings = useSettings()
|
|
546
|
+
const apiBaseUrl = settings.baseUrl as string
|
|
493
547
|
|
|
494
548
|
// Alternative: use the capability directly
|
|
495
549
|
const context = await capabilities.context.read()
|
|
496
550
|
\`\`\`
|
|
497
551
|
|
|
552
|
+
### Extension settings in context
|
|
553
|
+
Non-secret settings declared in \`settingsSchema\` are automatically available via \`contextData.settings\`. Values are scoped to the calling extension on the current instance \u2014 an extension never sees other extensions' settings.
|
|
554
|
+
|
|
555
|
+
- **Secret fields are never included** in context \u2014 use \`{{settings.xxx}}\` placeholders in \`data.fetch\` headers instead
|
|
556
|
+
- Settings propagate on **page load** \u2014 changes made in the admin dashboard take effect on the next page reload, not mid-session
|
|
557
|
+
- No new permission needed \u2014 \`context:read\` is the only gate
|
|
558
|
+
|
|
498
559
|
## actions.toast \u2014 Show Toast Notifications
|
|
499
560
|
Display a toast notification in the host UI.
|
|
500
561
|
- **Permission required:** \`actions:toast\`
|
|
@@ -977,9 +1038,16 @@ const viewState = useStore(appStore, (s) => s.viewState)
|
|
|
977
1038
|
\`\`\`
|
|
978
1039
|
|
|
979
1040
|
## useContextData()
|
|
980
|
-
Reads host-provided context. Returns \`{ loading, customerId, customerEmail, ... }\`.
|
|
1041
|
+
Reads host-provided context including extension settings. Returns \`{ loading, customerId, customerEmail, settings, ... }\`.
|
|
1042
|
+
\`\`\`tsx
|
|
1043
|
+
const { loading, customerId, customerEmail, settings } = useContextData()
|
|
1044
|
+
\`\`\`
|
|
1045
|
+
|
|
1046
|
+
## useSettings()
|
|
1047
|
+
Convenience hook for reading extension settings. Returns non-secret settings scoped to this extension on this instance.
|
|
981
1048
|
\`\`\`tsx
|
|
982
|
-
const
|
|
1049
|
+
const settings = useSettings()
|
|
1050
|
+
const apiBaseUrl = settings.baseUrl as string
|
|
983
1051
|
\`\`\`
|
|
984
1052
|
|
|
985
1053
|
## useSurfaceContext()
|
|
@@ -3254,8 +3322,11 @@ const capabilities = useCapabilities()
|
|
|
3254
3322
|
const response = await capabilities.data.fetch('https://api.example.com/endpoint')
|
|
3255
3323
|
`,
|
|
3256
3324
|
"context.read": `
|
|
3257
|
-
|
|
3258
|
-
const
|
|
3325
|
+
// Read host context + extension settings
|
|
3326
|
+
const { customerId, settings } = useContextData()
|
|
3327
|
+
|
|
3328
|
+
// Or use the convenience hook for settings only
|
|
3329
|
+
const settings = useSettings()
|
|
3259
3330
|
`,
|
|
3260
3331
|
"actions.toast": `
|
|
3261
3332
|
const capabilities = useCapabilities()
|
|
@@ -3641,8 +3712,11 @@ var CAPABILITY_SNIPPETS2 = {
|
|
|
3641
3712
|
const result = await capabilities.data.query({ path: '/your-endpoint', method: 'GET' })`,
|
|
3642
3713
|
"data.fetch": `const capabilities = useCapabilities()
|
|
3643
3714
|
const response = await capabilities.data.fetch('https://api.example.com/endpoint')`,
|
|
3644
|
-
"context.read":
|
|
3645
|
-
const
|
|
3715
|
+
"context.read": `// Read host context + extension settings
|
|
3716
|
+
const { customerId, settings } = useContextData()
|
|
3717
|
+
|
|
3718
|
+
// Or use the convenience hook for settings only
|
|
3719
|
+
const settings = useSettings()`,
|
|
3646
3720
|
"actions.toast": `const capabilities = useCapabilities()
|
|
3647
3721
|
capabilities.actions.toast({ type: 'success', message: 'Done!' })`,
|
|
3648
3722
|
"actions.invoke": `const capabilities = useCapabilities()
|
package/dist/server.js
CHANGED
|
@@ -41,12 +41,13 @@ export const appStore = createStore<AppState>({
|
|
|
41
41
|
{
|
|
42
42
|
path: "surfaces/Content.tsx",
|
|
43
43
|
title: "Content Surface with Loading State",
|
|
44
|
-
code: `import { ui, useStore, useContextData, Surface } from '@stackable-labs/sdk-extension-react'
|
|
44
|
+
code: `import { ui, useStore, useContextData, useSettings, Surface } from '@stackable-labs/sdk-extension-react'
|
|
45
45
|
import { appStore } from '../store'
|
|
46
46
|
|
|
47
47
|
export function Content() {
|
|
48
48
|
const viewState = useStore(appStore, (s) => s.viewState)
|
|
49
49
|
const { loading } = useContextData()
|
|
50
|
+
const settings = useSettings() // Non-secret settings from settingsSchema
|
|
50
51
|
|
|
51
52
|
if (loading) {
|
|
52
53
|
return (
|
|
@@ -141,19 +142,33 @@ export function createApi(query: QueryFn) {
|
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
// \u2500\u2500 data.fetch wrapper \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
145
|
+
//
|
|
146
|
+
// For API keys and secrets, use {{settings.xxx}} placeholders in headers.
|
|
147
|
+
// The proxy resolves them server-side \u2014 the real secret never enters extension code.
|
|
148
|
+
// Declare secret fields in manifest.json settingsSchema with "secret": true.
|
|
144
149
|
|
|
145
150
|
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL as string
|
|
146
151
|
|
|
147
152
|
export function createFetchApi(fetch: FetchFn) {
|
|
148
153
|
return {
|
|
149
154
|
async getItems(): Promise<unknown[]> {
|
|
150
|
-
const result = await fetch(\`\${API_BASE_URL}/items\`, {
|
|
155
|
+
const result = await fetch(\`\${API_BASE_URL}/items\`, {
|
|
156
|
+
method: 'GET',
|
|
157
|
+
headers: {
|
|
158
|
+
'X-API-Key': '{{settings.apiKey}}',
|
|
159
|
+
},
|
|
160
|
+
})
|
|
151
161
|
if (!result.ok) throw new Error(\`getItems failed: \${result.status}\`)
|
|
152
162
|
return result.data as unknown[]
|
|
153
163
|
},
|
|
154
164
|
|
|
155
165
|
async getItem(itemId: string): Promise<unknown> {
|
|
156
|
-
const result = await fetch(\`\${API_BASE_URL}/items/\${itemId}\`, {
|
|
166
|
+
const result = await fetch(\`\${API_BASE_URL}/items/\${itemId}\`, {
|
|
167
|
+
method: 'GET',
|
|
168
|
+
headers: {
|
|
169
|
+
'X-API-Key': '{{settings.apiKey}}',
|
|
170
|
+
},
|
|
171
|
+
})
|
|
157
172
|
if (!result.ok) throw new Error(\`getItem failed: \${result.status}\`)
|
|
158
173
|
return result.data as unknown
|
|
159
174
|
},
|
|
@@ -180,12 +195,13 @@ export const appStore = createStore<AppState>({
|
|
|
180
195
|
{
|
|
181
196
|
path: "surfaces/Content.tsx",
|
|
182
197
|
title: "Current Content Surface",
|
|
183
|
-
code: `import { ui, useStore, useContextData, Surface } from '@stackable-labs/sdk-extension-react'
|
|
198
|
+
code: `import { ui, useStore, useContextData, useSettings, Surface } from '@stackable-labs/sdk-extension-react'
|
|
184
199
|
import { appStore } from '../store'
|
|
185
200
|
|
|
186
201
|
export function Content() {
|
|
187
202
|
const viewState = useStore(appStore, (s) => s.viewState)
|
|
188
203
|
const { loading } = useContextData()
|
|
204
|
+
const settings = useSettings() // Non-secret settings from settingsSchema
|
|
189
205
|
|
|
190
206
|
if (loading) {
|
|
191
207
|
return (
|
|
@@ -249,19 +265,33 @@ export function createApi(query: QueryFn) {
|
|
|
249
265
|
}
|
|
250
266
|
|
|
251
267
|
// \u2500\u2500 data.fetch wrapper \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
268
|
+
//
|
|
269
|
+
// For API keys and secrets, use {{settings.xxx}} placeholders in headers.
|
|
270
|
+
// The proxy resolves them server-side \u2014 the real secret never enters extension code.
|
|
271
|
+
// Declare secret fields in manifest.json settingsSchema with "secret": true.
|
|
252
272
|
|
|
253
273
|
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL as string
|
|
254
274
|
|
|
255
275
|
export function createFetchApi(fetch: FetchFn) {
|
|
256
276
|
return {
|
|
257
277
|
async getItems(): Promise<unknown[]> {
|
|
258
|
-
const result = await fetch(\`\${API_BASE_URL}/items\`, {
|
|
278
|
+
const result = await fetch(\`\${API_BASE_URL}/items\`, {
|
|
279
|
+
method: 'GET',
|
|
280
|
+
headers: {
|
|
281
|
+
'X-API-Key': '{{settings.apiKey}}',
|
|
282
|
+
},
|
|
283
|
+
})
|
|
259
284
|
if (!result.ok) throw new Error(\`getItems failed: \${result.status}\`)
|
|
260
285
|
return result.data as unknown[]
|
|
261
286
|
},
|
|
262
287
|
|
|
263
288
|
async getItem(itemId: string): Promise<unknown> {
|
|
264
|
-
const result = await fetch(\`\${API_BASE_URL}/items/\${itemId}\`, {
|
|
289
|
+
const result = await fetch(\`\${API_BASE_URL}/items/\${itemId}\`, {
|
|
290
|
+
method: 'GET',
|
|
291
|
+
headers: {
|
|
292
|
+
'X-API-Key': '{{settings.apiKey}}',
|
|
293
|
+
},
|
|
294
|
+
})
|
|
265
295
|
if (!result.ok) throw new Error(\`getItem failed: \${result.status}\`)
|
|
266
296
|
return result.data as unknown
|
|
267
297
|
},
|
|
@@ -461,8 +491,8 @@ const result = await capabilities.data.query<Customer>({
|
|
|
461
491
|
})
|
|
462
492
|
\`\`\`
|
|
463
493
|
|
|
464
|
-
## data.fetch \u2014
|
|
465
|
-
|
|
494
|
+
## data.fetch \u2014 HTTP Requests to External APIs
|
|
495
|
+
Make HTTP requests to external APIs. Domain must be in \`allowedDomains\` in manifest. Requests are proxied through the Stackable platform server.
|
|
466
496
|
- **Permission required:** \`data:fetch\`
|
|
467
497
|
- **Usage:** \`capabilities.data.fetch(url: string, init?: FetchRequestInit): Promise<FetchResponse>\`
|
|
468
498
|
- **FetchRequestInit:** \`{ method?: 'GET'|'POST'|'PUT'|'PATCH'|'DELETE', headers?: Record<string,string>, body?: unknown }\`
|
|
@@ -478,21 +508,52 @@ if (!result.ok) throw new Error(\`Request failed: \${result.status}\`)
|
|
|
478
508
|
const data = result.data as MyType
|
|
479
509
|
\`\`\`
|
|
480
510
|
|
|
511
|
+
### Secret injection via \`{{settings.xxx}}\` placeholders
|
|
512
|
+
For API keys and tokens stored as \`secret: true\` fields in \`settingsSchema\`, use template placeholders in header values. The proxy resolves them server-side \u2014 **the real secret never enters extension code**.
|
|
513
|
+
|
|
514
|
+
\`\`\`tsx
|
|
515
|
+
const result = await capabilities.data.fetch('https://api.example.com/orders', {
|
|
516
|
+
method: 'GET',
|
|
517
|
+
headers: {
|
|
518
|
+
'X-API-Key': '{{settings.apiKey}}',
|
|
519
|
+
'Authorization': 'Bearer {{settings.token}}',
|
|
520
|
+
},
|
|
521
|
+
})
|
|
522
|
+
\`\`\`
|
|
523
|
+
|
|
524
|
+
- Placeholders are only allowed in **header values** (not URLs, header names, or body)
|
|
525
|
+
- For \`required: true\` secret fields, the proxy returns 400 if the value is not configured
|
|
526
|
+
- For optional secret fields, the entire header is omitted if the value is not configured
|
|
527
|
+
- Declare secret fields in your \`manifest.json\` \`settingsSchema\` with \`"secret": true\`
|
|
528
|
+
|
|
481
529
|
## context.read \u2014 Read Host Context
|
|
482
|
-
Read host-provided context (customer ID, email, etc.).
|
|
530
|
+
Read host-provided context (customer ID, email, extension settings, etc.).
|
|
483
531
|
- **Permission required:** \`context:read\`
|
|
484
532
|
- **Usage:** \`capabilities.context.read(): Promise<ContextData>\`
|
|
485
|
-
- **ContextData shape:** \`{ customerId?: string, customerEmail?: string, [key: string]: unknown }\`
|
|
486
|
-
- **Convenience
|
|
533
|
+
- **ContextData shape:** \`{ customerId?: string, customerEmail?: string, settings?: Record<string, unknown>, [key: string]: unknown }\`
|
|
534
|
+
- **Convenience hooks:**
|
|
535
|
+
- \`useContextData()\` returns \`ContextData & { loading: boolean }\`
|
|
536
|
+
- \`useSettings()\` returns \`Record<string, unknown>\` \u2014 shorthand for \`contextData.settings ?? {}\`
|
|
487
537
|
|
|
488
538
|
\`\`\`tsx
|
|
489
|
-
//
|
|
490
|
-
const { loading, customerId, customerEmail } = useContextData()
|
|
539
|
+
// Read all context (customer + settings)
|
|
540
|
+
const { loading, customerId, customerEmail, settings } = useContextData()
|
|
541
|
+
|
|
542
|
+
// Read only extension settings (convenience)
|
|
543
|
+
const settings = useSettings()
|
|
544
|
+
const apiBaseUrl = settings.baseUrl as string
|
|
491
545
|
|
|
492
546
|
// Alternative: use the capability directly
|
|
493
547
|
const context = await capabilities.context.read()
|
|
494
548
|
\`\`\`
|
|
495
549
|
|
|
550
|
+
### Extension settings in context
|
|
551
|
+
Non-secret settings declared in \`settingsSchema\` are automatically available via \`contextData.settings\`. Values are scoped to the calling extension on the current instance \u2014 an extension never sees other extensions' settings.
|
|
552
|
+
|
|
553
|
+
- **Secret fields are never included** in context \u2014 use \`{{settings.xxx}}\` placeholders in \`data.fetch\` headers instead
|
|
554
|
+
- Settings propagate on **page load** \u2014 changes made in the admin dashboard take effect on the next page reload, not mid-session
|
|
555
|
+
- No new permission needed \u2014 \`context:read\` is the only gate
|
|
556
|
+
|
|
496
557
|
## actions.toast \u2014 Show Toast Notifications
|
|
497
558
|
Display a toast notification in the host UI.
|
|
498
559
|
- **Permission required:** \`actions:toast\`
|
|
@@ -975,9 +1036,16 @@ const viewState = useStore(appStore, (s) => s.viewState)
|
|
|
975
1036
|
\`\`\`
|
|
976
1037
|
|
|
977
1038
|
## useContextData()
|
|
978
|
-
Reads host-provided context. Returns \`{ loading, customerId, customerEmail, ... }\`.
|
|
1039
|
+
Reads host-provided context including extension settings. Returns \`{ loading, customerId, customerEmail, settings, ... }\`.
|
|
1040
|
+
\`\`\`tsx
|
|
1041
|
+
const { loading, customerId, customerEmail, settings } = useContextData()
|
|
1042
|
+
\`\`\`
|
|
1043
|
+
|
|
1044
|
+
## useSettings()
|
|
1045
|
+
Convenience hook for reading extension settings. Returns non-secret settings scoped to this extension on this instance.
|
|
979
1046
|
\`\`\`tsx
|
|
980
|
-
const
|
|
1047
|
+
const settings = useSettings()
|
|
1048
|
+
const apiBaseUrl = settings.baseUrl as string
|
|
981
1049
|
\`\`\`
|
|
982
1050
|
|
|
983
1051
|
## useSurfaceContext()
|
|
@@ -3252,8 +3320,11 @@ const capabilities = useCapabilities()
|
|
|
3252
3320
|
const response = await capabilities.data.fetch('https://api.example.com/endpoint')
|
|
3253
3321
|
`,
|
|
3254
3322
|
"context.read": `
|
|
3255
|
-
|
|
3256
|
-
const
|
|
3323
|
+
// Read host context + extension settings
|
|
3324
|
+
const { customerId, settings } = useContextData()
|
|
3325
|
+
|
|
3326
|
+
// Or use the convenience hook for settings only
|
|
3327
|
+
const settings = useSettings()
|
|
3257
3328
|
`,
|
|
3258
3329
|
"actions.toast": `
|
|
3259
3330
|
const capabilities = useCapabilities()
|
|
@@ -3639,8 +3710,11 @@ var CAPABILITY_SNIPPETS2 = {
|
|
|
3639
3710
|
const result = await capabilities.data.query({ path: '/your-endpoint', method: 'GET' })`,
|
|
3640
3711
|
"data.fetch": `const capabilities = useCapabilities()
|
|
3641
3712
|
const response = await capabilities.data.fetch('https://api.example.com/endpoint')`,
|
|
3642
|
-
"context.read":
|
|
3643
|
-
const
|
|
3713
|
+
"context.read": `// Read host context + extension settings
|
|
3714
|
+
const { customerId, settings } = useContextData()
|
|
3715
|
+
|
|
3716
|
+
// Or use the convenience hook for settings only
|
|
3717
|
+
const settings = useSettings()`,
|
|
3644
3718
|
"actions.toast": `const capabilities = useCapabilities()
|
|
3645
3719
|
capabilities.actions.toast({ type: 'success', message: 'Done!' })`,
|
|
3646
3720
|
"actions.invoke": `const capabilities = useCapabilities()
|