@puredesktop/puredesktop-ui-bridge 2.1.8 → 2.1.9
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/package.json +35 -5
- package/src/agents/mcpServerPresets.test.ts +105 -0
- package/src/agents/mcpServerPresets.ts +380 -0
- package/src/agents/runtime/index.ts +7 -0
- package/src/agents/runtime/mapAgentSnapshot.ts +11 -8
- package/src/agents/runtime/pendingAsks.test.ts +117 -0
- package/src/agents/runtime/pendingAsks.ts +110 -0
- package/src/bridge/agentScopeDefaults.ts +65 -0
- package/src/bridge/agentTypes.ts +36 -2
- package/src/bridge/agents.d.mts +6 -0
- package/src/bridge/agents.mjs +10 -0
- package/src/bridge/agents.ts +4 -0
- package/src/bridge/credentials.d.mts +62 -0
- package/src/bridge/credentials.mjs +53 -0
- package/src/bridge/events.d.mts +1 -0
- package/src/bridge/events.mjs +1 -0
- package/src/bridge/googleAuth.ts +7 -152
- package/src/bridge/mcpClient.d.mts +74 -0
- package/src/bridge/mcpClient.mjs +79 -0
- package/src/bridge/methods.d.mts +22 -0
- package/src/bridge/methods.mjs +23 -0
- package/src/bridge/notifications.d.mts +12 -0
- package/src/bridge/notifications.mjs +36 -0
- package/src/bridge/operations.d.mts +44 -0
- package/src/bridge/operations.mjs +40 -0
- package/src/bridge/react/usePlatformAgentSessionDrive.test.tsx +29 -10
- package/src/bridge/react/usePlatformAgentSessionDrive.tsx +10 -81
- package/src/bridge/react/usePlatformAppSettings.test.tsx +105 -105
- package/src/bridge/react/usePlatformAppSettings.tsx +104 -104
- package/src/bridge/react/usePlatformJsonStore.test.tsx +152 -152
- package/src/bridge/react/usePlatformJsonStore.tsx +149 -149
- package/src/bridge/react/usePlatformOperations.tsx +75 -0
- package/src/bridge/storage.d.mts +17 -4
- package/src/bridge/storage.mjs +6 -3
- package/src/components/agents/AgentComposer.tsx +10 -6
- package/src/components/agents/AgentDrawerPanel.test.tsx +2 -205
- package/src/components/agents/AgentDrawerPanel.tsx +23 -64
- package/src/components/agents/AgentMessageBubble.tsx +59 -33
- package/src/components/agents/AgentMessageList.tsx +4 -9
- package/src/components/agents/AgentQuestionPromptList.tsx +34 -212
- package/src/components/agents/AgentToolApprovalActions.tsx +13 -11
- package/src/components/agents/AgentToolPendingList.tsx +6 -6
- package/src/components/agents/AgentToolPendingReview.tsx +6 -6
- package/src/components/agents/QuestionRequestList.tsx +278 -0
- package/src/components/agents/agentPanelStyles.ts +9 -33
- package/src/components/agents/agentTypes.ts +25 -43
- package/src/components/agents/index.ts +4 -0
- package/src/components/assets/asset-library/sidebar/AssetLibraryList.tsx +10 -4
- package/src/components/chrome/WorkspaceTabStrip.tsx +4 -3
- package/src/components/common/chat/ChatBox.tsx +9 -9
- package/src/components/common/chat/ChatModelMenu.tsx +34 -4
- package/src/components/common/chat/ChatThread.tsx +23 -7
- package/src/components/common/connections/ProviderConnection.test.tsx +208 -0
- package/src/components/common/connections/ProviderConnection.tsx +277 -0
- package/src/components/common/connections/index.ts +5 -0
- package/src/components/common/containers/AppFrame.tsx +26 -19
- package/src/components/common/containers/AppHeader.tsx +20 -20
- package/src/components/common/documents/DocumentSwitcher.tsx +63 -58
- package/src/components/common/dropdown/MenuDropdownItem.tsx +14 -2
- package/src/components/common/dropdown/MenuPortal.tsx +3 -1
- package/src/components/common/dropdown/menuViewportPosition.test.ts +20 -10
- package/src/components/common/dropdown/menuViewportPosition.ts +35 -27
- package/src/components/common/overlays/Modal.tsx +13 -2
- package/src/components/editor/EditorLinkPromptDialog.tsx +1 -1
- package/src/ics/generateIcs.test.ts +153 -0
- package/src/ics/generateIcs.ts +197 -0
- package/src/index.ts +3 -0
- package/src/net/httpRetry.test.ts +117 -0
- package/src/net/httpRetry.ts +111 -0
- package/src/theme/appAccents.ts +6 -0
- package/src/theme/appIdentityCss.mjs +11 -0
- package/src/theme/appIdentityCss.ts +11 -0
- package/src/theme/themes/dark.ts +3 -1
- package/src/theme/themes/light.ts +3 -1
- package/src/bridge/googleProviderConfig.ts +0 -73
|
@@ -31,10 +31,7 @@ import {
|
|
|
31
31
|
isAgentAutoModelId,
|
|
32
32
|
DEFAULT_AGENT_HIGH_LEVEL_MODEL_ID,
|
|
33
33
|
} from '../agentModelTiers.js'
|
|
34
|
-
import {
|
|
35
|
-
getPlatformPreferences,
|
|
36
|
-
patchPlatformPreferences,
|
|
37
|
-
} from '../preferences.mjs'
|
|
34
|
+
import { patchAgentSessionScopeDefaults } from '../agentScopeDefaults.js'
|
|
38
35
|
import {
|
|
39
36
|
advanceAgentSessionPending,
|
|
40
37
|
approveAgentSessionTools,
|
|
@@ -95,10 +92,6 @@ export interface UsePlatformAgentSessionDrive {
|
|
|
95
92
|
/** Send the composer value, or an explicit message (programmatic). Returns the final snapshot. */
|
|
96
93
|
sendMessage: (message?: string) => Promise<PlatformAgentRunSnapshot | null>
|
|
97
94
|
approveTools: (toolCallIds?: readonly string[]) => Promise<void>
|
|
98
|
-
approveToolAlwaysForSession: (
|
|
99
|
-
qualifiedName: string,
|
|
100
|
-
toolCallId: string,
|
|
101
|
-
) => Promise<void>
|
|
102
95
|
rejectTools: (toolCallIds?: readonly string[]) => Promise<void>
|
|
103
96
|
selectSession: (sessionId: string) => Promise<void>
|
|
104
97
|
createNewSession: () => Promise<void>
|
|
@@ -121,9 +114,9 @@ interface DriveResult {
|
|
|
121
114
|
}
|
|
122
115
|
|
|
123
116
|
/** Display snapshot from a persisted session record (before any live run this turn). */
|
|
124
|
-
function recordSnapshot(
|
|
125
|
-
record: PlatformAgentSessionRecord | null,
|
|
126
|
-
): PlatformAgentRunSnapshot | null {
|
|
117
|
+
function recordSnapshot(
|
|
118
|
+
record: PlatformAgentSessionRecord | null,
|
|
119
|
+
): PlatformAgentRunSnapshot | null {
|
|
127
120
|
if (!record) return null
|
|
128
121
|
return {
|
|
129
122
|
runId: record.runId ?? '',
|
|
@@ -132,28 +125,10 @@ function recordSnapshot(
|
|
|
132
125
|
messages: record.snapshot.messages,
|
|
133
126
|
error: record.snapshot.error,
|
|
134
127
|
interruptedReason: record.snapshot.interruptedReason,
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function
|
|
139
|
-
base: PlatformAgentSessionConfigPatch | undefined,
|
|
140
|
-
patch: PlatformAgentSessionConfigPatch,
|
|
141
|
-
): PlatformAgentSessionConfigPatch {
|
|
142
|
-
const toolPolicy =
|
|
143
|
-
patch.toolPolicy || base?.toolPolicy
|
|
144
|
-
? {
|
|
145
|
-
...base?.toolPolicy,
|
|
146
|
-
...patch.toolPolicy,
|
|
147
|
-
approvalOverrides: {
|
|
148
|
-
...base?.toolPolicy?.approvalOverrides,
|
|
149
|
-
...patch.toolPolicy?.approvalOverrides,
|
|
150
|
-
},
|
|
151
|
-
}
|
|
152
|
-
: undefined
|
|
153
|
-
return { ...base, ...patch, ...(toolPolicy ? { toolPolicy } : {}) }
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export function usePlatformAgentSessionDrive({
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function usePlatformAgentSessionDrive({
|
|
157
132
|
appSlug,
|
|
158
133
|
scope: initialScope = 'app',
|
|
159
134
|
enabled = true,
|
|
@@ -404,7 +379,7 @@ export function usePlatformAgentSessionDrive({
|
|
|
404
379
|
: 'This model is not available. Check API keys or local runtime in Settings.'
|
|
405
380
|
}, [modelOptions, selectedModelId])
|
|
406
381
|
|
|
407
|
-
const toolPolicy = policy
|
|
382
|
+
const toolPolicy = policy
|
|
408
383
|
const acceptedPendingToolCallIds = useMemo(
|
|
409
384
|
() =>
|
|
410
385
|
sessionRecord?.runId && sessionRecord.runId === threadSnapshot?.runId
|
|
@@ -607,28 +582,7 @@ export function usePlatformAgentSessionDrive({
|
|
|
607
582
|
const persistScopeDefault = useCallback(
|
|
608
583
|
async (patch: PlatformAgentSessionConfigPatch) => {
|
|
609
584
|
try {
|
|
610
|
-
|
|
611
|
-
agentSessionDefaults?: {
|
|
612
|
-
global?: PlatformAgentSessionConfigPatch
|
|
613
|
-
apps?: Record<string, PlatformAgentSessionConfigPatch>
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
const stored = prefs.agentSessionDefaults ?? {}
|
|
617
|
-
if (scope === 'global') {
|
|
618
|
-
await patchPlatformPreferences({
|
|
619
|
-
agentSessionDefaults: {
|
|
620
|
-
...stored,
|
|
621
|
-
global: mergeScopeDefaultPatch(stored.global, patch),
|
|
622
|
-
},
|
|
623
|
-
})
|
|
624
|
-
return
|
|
625
|
-
}
|
|
626
|
-
if (!scopeAppSlug) return
|
|
627
|
-
const apps = { ...(stored.apps ?? {}) }
|
|
628
|
-
apps[scopeAppSlug] = mergeScopeDefaultPatch(apps[scopeAppSlug], patch)
|
|
629
|
-
await patchPlatformPreferences({
|
|
630
|
-
agentSessionDefaults: { ...stored, apps },
|
|
631
|
-
})
|
|
585
|
+
await patchAgentSessionScopeDefaults(scope, scopeAppSlug, patch)
|
|
632
586
|
} catch {
|
|
633
587
|
/* scope defaults are best-effort */
|
|
634
588
|
}
|
|
@@ -696,30 +650,6 @@ export function usePlatformAgentSessionDrive({
|
|
|
696
650
|
[persistScopeDefault, sessionQuery, sessionRecord],
|
|
697
651
|
)
|
|
698
652
|
|
|
699
|
-
const approveToolAlwaysForSession = useCallback(
|
|
700
|
-
async (qualifiedName: string, toolCallId: string) => {
|
|
701
|
-
if (!sessionRecord) return
|
|
702
|
-
await drive(async () => {
|
|
703
|
-
const overrides = {
|
|
704
|
-
...sessionRecord.config.toolPolicy?.approvalOverrides,
|
|
705
|
-
[qualifiedName]: false,
|
|
706
|
-
}
|
|
707
|
-
const updated = await updateAgentSession({
|
|
708
|
-
query: sessionQuery(sessionRecord.sessionId),
|
|
709
|
-
patch: { config: { toolPolicy: { approvalOverrides: overrides } } },
|
|
710
|
-
})
|
|
711
|
-
await persistScopeDefault({
|
|
712
|
-
toolPolicy: { approvalOverrides: overrides },
|
|
713
|
-
})
|
|
714
|
-
return approveAgentSessionTools({
|
|
715
|
-
session: sessionQuery(updated.sessionId),
|
|
716
|
-
toolCallIds: [toolCallId],
|
|
717
|
-
})
|
|
718
|
-
})
|
|
719
|
-
},
|
|
720
|
-
[drive, persistScopeDefault, sessionQuery, sessionRecord],
|
|
721
|
-
)
|
|
722
|
-
|
|
723
653
|
return {
|
|
724
654
|
scope,
|
|
725
655
|
setScope,
|
|
@@ -744,7 +674,6 @@ export function usePlatformAgentSessionDrive({
|
|
|
744
674
|
sendMessage,
|
|
745
675
|
interruptRun,
|
|
746
676
|
approveTools,
|
|
747
|
-
approveToolAlwaysForSession,
|
|
748
677
|
rejectTools,
|
|
749
678
|
selectSession,
|
|
750
679
|
createNewSession,
|
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
// @vitest-environment happy-dom
|
|
2
|
-
import { act } from 'react'
|
|
3
|
-
import { createRoot, type Root } from 'react-dom/client'
|
|
4
|
-
import {
|
|
5
|
-
afterEach,
|
|
6
|
-
beforeAll,
|
|
7
|
-
beforeEach,
|
|
8
|
-
describe,
|
|
9
|
-
expect,
|
|
10
|
-
it,
|
|
11
|
-
vi,
|
|
12
|
-
} from 'vitest'
|
|
13
|
-
import { usePlatformAppSettings } from './usePlatformAppSettings.js'
|
|
14
|
-
|
|
15
|
-
type ReactActGlobal = typeof globalThis & {
|
|
16
|
-
IS_REACT_ACT_ENVIRONMENT?: boolean
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
type Settings = Record<string, unknown> & {
|
|
20
|
-
view: 'list' | 'board'
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const appSettingsMock = vi.hoisted(() => ({
|
|
24
|
-
getPlatformAppSettings: vi.fn(),
|
|
25
|
-
updatePlatformAppSettings: vi.fn(),
|
|
26
|
-
}))
|
|
27
|
-
|
|
28
|
-
vi.mock('../appSettings.mjs', () => appSettingsMock)
|
|
29
|
-
|
|
30
|
-
let latest:
|
|
31
|
-
| ReturnType<typeof usePlatformAppSettings<Settings>>
|
|
32
|
-
| null = null
|
|
33
|
-
|
|
34
|
-
function parseSettings(raw: Record<string, unknown>): Settings {
|
|
35
|
-
return {
|
|
36
|
-
view: raw.view === 'board' ? 'board' : 'list',
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function Probe(): React.ReactElement {
|
|
41
|
-
latest = usePlatformAppSettings<Settings>({
|
|
42
|
-
appSlug: 'tasks',
|
|
43
|
-
parse: parseSettings,
|
|
44
|
-
initialSettings: { view: 'list' },
|
|
45
|
-
})
|
|
46
|
-
return <div />
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async function flushEffects(): Promise<void> {
|
|
50
|
-
await act(async () => {
|
|
51
|
-
await Promise.resolve()
|
|
52
|
-
await Promise.resolve()
|
|
53
|
-
})
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
describe('usePlatformAppSettings', () => {
|
|
57
|
-
let host: HTMLDivElement
|
|
58
|
-
let root: Root
|
|
59
|
-
|
|
60
|
-
beforeAll(() => {
|
|
61
|
-
;(globalThis as ReactActGlobal).IS_REACT_ACT_ENVIRONMENT = true
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
beforeEach(() => {
|
|
65
|
-
latest = null
|
|
66
|
-
host = document.createElement('div')
|
|
67
|
-
document.body.appendChild(host)
|
|
68
|
-
root = createRoot(host)
|
|
69
|
-
appSettingsMock.getPlatformAppSettings.mockResolvedValue({ view: 'board' })
|
|
70
|
-
appSettingsMock.updatePlatformAppSettings.mockResolvedValue({ view: 'list' })
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
afterEach(() => {
|
|
74
|
-
act(() => root.unmount())
|
|
75
|
-
host.remove()
|
|
76
|
-
vi.clearAllMocks()
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
it('loads and normalizes app settings for one app slug', async () => {
|
|
80
|
-
act(() => {
|
|
81
|
-
root.render(<Probe />)
|
|
82
|
-
})
|
|
83
|
-
await flushEffects()
|
|
84
|
-
|
|
85
|
-
expect(appSettingsMock.getPlatformAppSettings).toHaveBeenCalledWith('tasks')
|
|
86
|
-
expect(latest?.settings).toEqual({ view: 'board' })
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
it('patches app settings through settings.app.update', async () => {
|
|
90
|
-
act(() => {
|
|
91
|
-
root.render(<Probe />)
|
|
92
|
-
})
|
|
93
|
-
await flushEffects()
|
|
94
|
-
|
|
95
|
-
await act(async () => {
|
|
96
|
-
await latest?.patchSettings({ view: 'list' })
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
expect(appSettingsMock.updatePlatformAppSettings).toHaveBeenCalledWith({
|
|
100
|
-
appSlug: 'tasks',
|
|
101
|
-
patch: { view: 'list' },
|
|
102
|
-
})
|
|
103
|
-
expect(latest?.settings).toEqual({ view: 'list' })
|
|
104
|
-
})
|
|
105
|
-
})
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import { act } from 'react'
|
|
3
|
+
import { createRoot, type Root } from 'react-dom/client'
|
|
4
|
+
import {
|
|
5
|
+
afterEach,
|
|
6
|
+
beforeAll,
|
|
7
|
+
beforeEach,
|
|
8
|
+
describe,
|
|
9
|
+
expect,
|
|
10
|
+
it,
|
|
11
|
+
vi,
|
|
12
|
+
} from 'vitest'
|
|
13
|
+
import { usePlatformAppSettings } from './usePlatformAppSettings.js'
|
|
14
|
+
|
|
15
|
+
type ReactActGlobal = typeof globalThis & {
|
|
16
|
+
IS_REACT_ACT_ENVIRONMENT?: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type Settings = Record<string, unknown> & {
|
|
20
|
+
view: 'list' | 'board'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const appSettingsMock = vi.hoisted(() => ({
|
|
24
|
+
getPlatformAppSettings: vi.fn(),
|
|
25
|
+
updatePlatformAppSettings: vi.fn(),
|
|
26
|
+
}))
|
|
27
|
+
|
|
28
|
+
vi.mock('../appSettings.mjs', () => appSettingsMock)
|
|
29
|
+
|
|
30
|
+
let latest:
|
|
31
|
+
| ReturnType<typeof usePlatformAppSettings<Settings>>
|
|
32
|
+
| null = null
|
|
33
|
+
|
|
34
|
+
function parseSettings(raw: Record<string, unknown>): Settings {
|
|
35
|
+
return {
|
|
36
|
+
view: raw.view === 'board' ? 'board' : 'list',
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function Probe(): React.ReactElement {
|
|
41
|
+
latest = usePlatformAppSettings<Settings>({
|
|
42
|
+
appSlug: 'tasks',
|
|
43
|
+
parse: parseSettings,
|
|
44
|
+
initialSettings: { view: 'list' },
|
|
45
|
+
})
|
|
46
|
+
return <div />
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function flushEffects(): Promise<void> {
|
|
50
|
+
await act(async () => {
|
|
51
|
+
await Promise.resolve()
|
|
52
|
+
await Promise.resolve()
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
describe('usePlatformAppSettings', () => {
|
|
57
|
+
let host: HTMLDivElement
|
|
58
|
+
let root: Root
|
|
59
|
+
|
|
60
|
+
beforeAll(() => {
|
|
61
|
+
;(globalThis as ReactActGlobal).IS_REACT_ACT_ENVIRONMENT = true
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
latest = null
|
|
66
|
+
host = document.createElement('div')
|
|
67
|
+
document.body.appendChild(host)
|
|
68
|
+
root = createRoot(host)
|
|
69
|
+
appSettingsMock.getPlatformAppSettings.mockResolvedValue({ view: 'board' })
|
|
70
|
+
appSettingsMock.updatePlatformAppSettings.mockResolvedValue({ view: 'list' })
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
afterEach(() => {
|
|
74
|
+
act(() => root.unmount())
|
|
75
|
+
host.remove()
|
|
76
|
+
vi.clearAllMocks()
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('loads and normalizes app settings for one app slug', async () => {
|
|
80
|
+
act(() => {
|
|
81
|
+
root.render(<Probe />)
|
|
82
|
+
})
|
|
83
|
+
await flushEffects()
|
|
84
|
+
|
|
85
|
+
expect(appSettingsMock.getPlatformAppSettings).toHaveBeenCalledWith('tasks')
|
|
86
|
+
expect(latest?.settings).toEqual({ view: 'board' })
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('patches app settings through settings.app.update', async () => {
|
|
90
|
+
act(() => {
|
|
91
|
+
root.render(<Probe />)
|
|
92
|
+
})
|
|
93
|
+
await flushEffects()
|
|
94
|
+
|
|
95
|
+
await act(async () => {
|
|
96
|
+
await latest?.patchSettings({ view: 'list' })
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
expect(appSettingsMock.updatePlatformAppSettings).toHaveBeenCalledWith({
|
|
100
|
+
appSlug: 'tasks',
|
|
101
|
+
patch: { view: 'list' },
|
|
102
|
+
})
|
|
103
|
+
expect(latest?.settings).toEqual({ view: 'list' })
|
|
104
|
+
})
|
|
105
|
+
})
|
|
@@ -1,104 +1,104 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState } from 'react'
|
|
2
|
-
import {
|
|
3
|
-
getPlatformAppSettings,
|
|
4
|
-
updatePlatformAppSettings,
|
|
5
|
-
} from '../appSettings.mjs'
|
|
6
|
-
|
|
7
|
-
export interface UsePlatformAppSettingsOptions<
|
|
8
|
-
TSettings extends Record<string, unknown> = Record<string, unknown>,
|
|
9
|
-
> {
|
|
10
|
-
appSlug: string | null
|
|
11
|
-
/** Wait for bridge ready before loading (default: true when appSlug exists). */
|
|
12
|
-
enabled?: boolean
|
|
13
|
-
/** Normalize stored settings into the app-owned settings shape. */
|
|
14
|
-
parse?: (value: Record<string, unknown>) => TSettings
|
|
15
|
-
initialSettings?: TSettings
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface UsePlatformAppSettingsResult<
|
|
19
|
-
TSettings extends Record<string, unknown> = Record<string, unknown>,
|
|
20
|
-
> {
|
|
21
|
-
settings: TSettings
|
|
22
|
-
loading: boolean
|
|
23
|
-
error: Error | null
|
|
24
|
-
/** Shallow-merge a patch through shell `settings.app.update`. */
|
|
25
|
-
patchSettings: (patch: Record<string, unknown>) => Promise<TSettings>
|
|
26
|
-
reload: () => void
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function defaultParse<TSettings extends Record<string, unknown>>(
|
|
30
|
-
value: Record<string, unknown>,
|
|
31
|
-
): TSettings {
|
|
32
|
-
return value as TSettings
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Load and update the current app's settings through `settings.app.*`.
|
|
37
|
-
* App settings are small per-app preferences, not user-visible documents.
|
|
38
|
-
*/
|
|
39
|
-
export function usePlatformAppSettings<
|
|
40
|
-
TSettings extends Record<string, unknown> = Record<string, unknown>,
|
|
41
|
-
>({
|
|
42
|
-
appSlug,
|
|
43
|
-
enabled = Boolean(appSlug),
|
|
44
|
-
parse = defaultParse<TSettings>,
|
|
45
|
-
initialSettings = {} as TSettings,
|
|
46
|
-
}: UsePlatformAppSettingsOptions<TSettings>): UsePlatformAppSettingsResult<TSettings> {
|
|
47
|
-
const [settings, setSettings] = useState<TSettings>(initialSettings)
|
|
48
|
-
const [loading, setLoading] = useState(false)
|
|
49
|
-
const [error, setError] = useState<Error | null>(null)
|
|
50
|
-
const [reloadToken, setReloadToken] = useState(0)
|
|
51
|
-
|
|
52
|
-
const reload = useCallback(() => {
|
|
53
|
-
setReloadToken(token => token + 1)
|
|
54
|
-
}, [])
|
|
55
|
-
|
|
56
|
-
useEffect(() => {
|
|
57
|
-
if (!enabled || !appSlug) return
|
|
58
|
-
|
|
59
|
-
let cancelled = false
|
|
60
|
-
setLoading(true)
|
|
61
|
-
setError(null)
|
|
62
|
-
|
|
63
|
-
void getPlatformAppSettings(appSlug)
|
|
64
|
-
.then(raw => {
|
|
65
|
-
if (!cancelled) setSettings(parse(raw))
|
|
66
|
-
})
|
|
67
|
-
.catch((cause: unknown) => {
|
|
68
|
-
if (!cancelled) {
|
|
69
|
-
setError(cause instanceof Error ? cause : new Error(String(cause)))
|
|
70
|
-
}
|
|
71
|
-
})
|
|
72
|
-
.finally(() => {
|
|
73
|
-
if (!cancelled) setLoading(false)
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
return () => {
|
|
77
|
-
cancelled = true
|
|
78
|
-
}
|
|
79
|
-
}, [appSlug, enabled, parse, reloadToken])
|
|
80
|
-
|
|
81
|
-
const patchSettings = useCallback(
|
|
82
|
-
async (patch: Record<string, unknown>): Promise<TSettings> => {
|
|
83
|
-
if (!appSlug) throw new Error('appSlug is required to update app settings')
|
|
84
|
-
try {
|
|
85
|
-
const next = parse(
|
|
86
|
-
await updatePlatformAppSettings({
|
|
87
|
-
appSlug,
|
|
88
|
-
patch,
|
|
89
|
-
}),
|
|
90
|
-
)
|
|
91
|
-
setSettings(next)
|
|
92
|
-
setError(null)
|
|
93
|
-
return next
|
|
94
|
-
} catch (cause) {
|
|
95
|
-
const error = cause instanceof Error ? cause : new Error(String(cause))
|
|
96
|
-
setError(error)
|
|
97
|
-
throw error
|
|
98
|
-
}
|
|
99
|
-
},
|
|
100
|
-
[appSlug, parse],
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
return { settings, loading, error, patchSettings, reload }
|
|
104
|
-
}
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
getPlatformAppSettings,
|
|
4
|
+
updatePlatformAppSettings,
|
|
5
|
+
} from '../appSettings.mjs'
|
|
6
|
+
|
|
7
|
+
export interface UsePlatformAppSettingsOptions<
|
|
8
|
+
TSettings extends Record<string, unknown> = Record<string, unknown>,
|
|
9
|
+
> {
|
|
10
|
+
appSlug: string | null
|
|
11
|
+
/** Wait for bridge ready before loading (default: true when appSlug exists). */
|
|
12
|
+
enabled?: boolean
|
|
13
|
+
/** Normalize stored settings into the app-owned settings shape. */
|
|
14
|
+
parse?: (value: Record<string, unknown>) => TSettings
|
|
15
|
+
initialSettings?: TSettings
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface UsePlatformAppSettingsResult<
|
|
19
|
+
TSettings extends Record<string, unknown> = Record<string, unknown>,
|
|
20
|
+
> {
|
|
21
|
+
settings: TSettings
|
|
22
|
+
loading: boolean
|
|
23
|
+
error: Error | null
|
|
24
|
+
/** Shallow-merge a patch through shell `settings.app.update`. */
|
|
25
|
+
patchSettings: (patch: Record<string, unknown>) => Promise<TSettings>
|
|
26
|
+
reload: () => void
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function defaultParse<TSettings extends Record<string, unknown>>(
|
|
30
|
+
value: Record<string, unknown>,
|
|
31
|
+
): TSettings {
|
|
32
|
+
return value as TSettings
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Load and update the current app's settings through `settings.app.*`.
|
|
37
|
+
* App settings are small per-app preferences, not user-visible documents.
|
|
38
|
+
*/
|
|
39
|
+
export function usePlatformAppSettings<
|
|
40
|
+
TSettings extends Record<string, unknown> = Record<string, unknown>,
|
|
41
|
+
>({
|
|
42
|
+
appSlug,
|
|
43
|
+
enabled = Boolean(appSlug),
|
|
44
|
+
parse = defaultParse<TSettings>,
|
|
45
|
+
initialSettings = {} as TSettings,
|
|
46
|
+
}: UsePlatformAppSettingsOptions<TSettings>): UsePlatformAppSettingsResult<TSettings> {
|
|
47
|
+
const [settings, setSettings] = useState<TSettings>(initialSettings)
|
|
48
|
+
const [loading, setLoading] = useState(false)
|
|
49
|
+
const [error, setError] = useState<Error | null>(null)
|
|
50
|
+
const [reloadToken, setReloadToken] = useState(0)
|
|
51
|
+
|
|
52
|
+
const reload = useCallback(() => {
|
|
53
|
+
setReloadToken(token => token + 1)
|
|
54
|
+
}, [])
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (!enabled || !appSlug) return
|
|
58
|
+
|
|
59
|
+
let cancelled = false
|
|
60
|
+
setLoading(true)
|
|
61
|
+
setError(null)
|
|
62
|
+
|
|
63
|
+
void getPlatformAppSettings(appSlug)
|
|
64
|
+
.then(raw => {
|
|
65
|
+
if (!cancelled) setSettings(parse(raw))
|
|
66
|
+
})
|
|
67
|
+
.catch((cause: unknown) => {
|
|
68
|
+
if (!cancelled) {
|
|
69
|
+
setError(cause instanceof Error ? cause : new Error(String(cause)))
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
.finally(() => {
|
|
73
|
+
if (!cancelled) setLoading(false)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
return () => {
|
|
77
|
+
cancelled = true
|
|
78
|
+
}
|
|
79
|
+
}, [appSlug, enabled, parse, reloadToken])
|
|
80
|
+
|
|
81
|
+
const patchSettings = useCallback(
|
|
82
|
+
async (patch: Record<string, unknown>): Promise<TSettings> => {
|
|
83
|
+
if (!appSlug) throw new Error('appSlug is required to update app settings')
|
|
84
|
+
try {
|
|
85
|
+
const next = parse(
|
|
86
|
+
await updatePlatformAppSettings({
|
|
87
|
+
appSlug,
|
|
88
|
+
patch,
|
|
89
|
+
}),
|
|
90
|
+
)
|
|
91
|
+
setSettings(next)
|
|
92
|
+
setError(null)
|
|
93
|
+
return next
|
|
94
|
+
} catch (cause) {
|
|
95
|
+
const error = cause instanceof Error ? cause : new Error(String(cause))
|
|
96
|
+
setError(error)
|
|
97
|
+
throw error
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
[appSlug, parse],
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return { settings, loading, error, patchSettings, reload }
|
|
104
|
+
}
|