fix: persist settings (API key) via main-thread RPC — clientStorage is unavailable in UI iframe, so direct save/load silently failed; bump 0.1.19

This commit is contained in:
Hermes Agent committed 2026-09-24 11:22:43 +02:00
1 parent 2fe3132e03
commit 79e9ec1128
4 files changed
+50 -23

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "figma-ai-plugin",
"version": "0.1.18",
"version": "0.1.19",
"private": true,
"dependencies": {
"@create-figma-plugin/ui": "^4.0.3",
+12
View File
@@ -572,6 +572,18 @@ export default function () {
}
})
// Settings persistence RPC: clientStorage is only available here (main thread), not in the UI iframe.
on('LOAD_SETTINGS', async function (payload: { key: string }) {
let stored: any = null
try { stored = await figma.clientStorage.getAsync(payload.key) } catch (e) { stored = null }
emit('SETTINGS_LOADED', { settings: stored })
})
on('SAVE_SETTINGS', async function (payload: { key: string, settings: any }) {
try { await figma.clientStorage.setAsync(payload.key, payload.settings) } catch (e) { /* ignore */ }
emit('SAVE_SETTINGS_DONE', {})
})
showUI({ width: 380, height: 480, themeColors: true, title: 'AI assistant (MVP)' })
// On open, proactively probe connectivity so the user sees network status without clicking Fetch.
+34 -20
View File
@@ -1,8 +1,11 @@
// Local settings model + persistence (figma.clientStorage).
//
// No server auth yet — everything lives on the Figma file locally. Backup/restore
// is just a JSON export/import of this same document. (Server auth comes later.)
// IMPORTANT: figma.clientStorage is ONLY available in the plugin MAIN thread (main.ts),
// NOT in the UI iframe. So load/save must round-trip through main via emit/on.
// These helpers only build/parse the data; the actual storage calls live in main.ts
// (SAVE_SETTINGS / LOAD_SETTINGS RPC). Backup/restore is a JSON export/import of this same model.
import { emit, on } from '@create-figma-plugin/utilities'
import { APINEX_MODELS } from './apinex-models'
export type ProviderType = 'apinex'
@@ -53,26 +56,37 @@ export function createApinexProvider (): ProviderConfig {
}
}
export async function loadSettings (): Promise<Settings> {
const defaults = defaultSettings()
try {
const stored = await figma.clientStorage.getAsync(SETTINGS_KEY)
if (stored == null || stored.version !== 1) return defaults
const merged: Settings = { ...defaults, ...(stored as Settings) }
if (!Array.isArray(merged.providers) || merged.providers.length === 0) {
merged.providers = defaults.providers
merged.activeProviderId = defaults.providers[0].id
}
return merged
} catch (e) {
return defaults
}
// Request main thread to load persisted settings; resolves with merged Settings.
export function loadSettings (): Promise<Settings> {
return new Promise(function (resolve) {
const dispose = on('SETTINGS_LOADED', function (payload: { settings: Settings | null }) {
dispose()
const stored = payload.settings
if (stored == null || stored.version !== 1) {
resolve(defaultSettings())
return
}
const defaults = defaultSettings()
const merged: Settings = { ...defaults, ...stored }
if (!Array.isArray(merged.providers) || merged.providers.length === 0) {
merged.providers = defaults.providers
merged.activeProviderId = defaults.providers[0].id
}
resolve(merged)
})
emit('LOAD_SETTINGS', { key: SETTINGS_KEY })
})
}
export async function saveSettings (settings: Settings): Promise<void> {
try {
await figma.clientStorage.setAsync(SETTINGS_KEY, settings)
} catch (e) { /* storage unavailable — ignore */ }
// Request main thread to persist settings.
export function saveSettings (settings: Settings): Promise<void> {
return new Promise(function (resolve) {
const dispose = on('SAVE_SETTINGS_DONE', function () {
dispose()
resolve()
})
emit('SAVE_SETTINGS', { key: SETTINGS_KEY, settings: settings })
})
}
export function settingsToJSON (settings: Settings): string {
+3 -2
View File
@@ -30,7 +30,7 @@ const MIN_WINDOW_SIZE = { width: 320, height: 200 }
// very high — actual clamp is done by main via clampWindowSize (bounds*zoom).
// Full plugin version shown in the footer status bar. Keep in sync with package.json "version".
const PLUGIN_VERSION = '0.1.18'
const PLUGIN_VERSION = '0.1.19'
const fieldStyle = {
display: 'flex',
@@ -164,6 +164,7 @@ function SettingsPanel (props: { settings: Settings, setSettings: (s: Settings)
function save () {
saveSettings(settings).then(function () {
setSavedNote(true)
logEvent('Settings saved (manual)')
setTimeout(function () { setSavedNote(false) }, 1500)
})
}
@@ -286,7 +287,7 @@ function Plugin () {
// Gated on `loaded` so we don't overwrite stored settings with defaults before load completes.
useEffect(function () {
if (!loaded) return
const t = setTimeout(function () { saveSettings(settings) }, 400)
const t = setTimeout(function () { saveSettings(settings).then(function () { logEvent('Settings saved') }) }, 400)
return function () { clearTimeout(t) }
}, [settings, loaded])
useEffect(function () { try { logEvent('Plugin loaded') } catch (e) {} }, [])