penpot/frontend/src/app/plugins.cljs
Andrey Antukh a8d0c18c1b
🐛 Fix race condition between MCP initialization and plugin runtime (#10137)
* 🐛 Fix race condition between MCP init and plugin runtime

Add promise-based synchronization to ensure MCP initialization waits
for plugin runtime to be ready before calling global.ɵloadPlugin.

- Add runtime-ready-promise in app.plugins that resolves when
  init-plugins-runtime completes
- Add wait-for-runtime function for other modules to await readiness
- MCP init now waits for runtime via rx/from before starting plugin
- Add defensive guards in start-plugin!, load-plugin!, close-plugin!
  to check if plugin APIs exist before calling
- Rename init-plugins-runtime! to init-plugins-runtime

Fixes: global.ɵloadPlugin is not a function error when MCP plugin
starts before async plugin runtime initialization completes.

* 📎 Add 'create-pr' opencode skill
2026-06-12 11:40:02 +02:00

56 lines
1.7 KiB
Clojure

;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC Sucursal en España SL
(ns app.plugins
"RPC for plugins runtime."
(:require
["@penpot/plugins-runtime" :as runtime]
[app.main.errors :as errors]
[app.main.features :as features]
[app.main.store :as st]
[app.plugins.api :as api]
[app.plugins.flex :as flex]
[app.plugins.format :as format]
[app.plugins.grid :as grid]
[app.plugins.library :as library]
[app.plugins.public-utils]
[app.plugins.register :as preg]
[app.plugins.ruler-guides :as rg]
[app.plugins.shape :as shape]
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))
(defn init-plugins-runtime
[]
(runtime/initPluginsRuntime (fn [plugin-id] (api/create-context plugin-id)))
;; Signal that runtime is ready
(preg/signal-runtime-ready))
(defn initialize
[]
(ptk/reify ::initialize
ptk/WatchEvent
(watch [_ _ stream]
(set! errors/is-plugin-error? runtime/isPluginError)
(->> stream
(rx/filter (ptk/type? ::features/initialize))
(rx/observe-on :async)
(rx/filter #(features/active-feature? @st/state "plugins/runtime"))
(rx/take 1)
(rx/tap init-plugins-runtime)
(rx/ignore)))))
;; Prevent circular dependency
(set! flex/shape-proxy? shape/shape-proxy?)
(set! grid/shape-proxy? shape/shape-proxy?)
(set! format/shape-proxy shape/shape-proxy)
(set! rg/shape-proxy shape/shape-proxy)
(set! rg/shape-proxy? shape/shape-proxy?)
(set! shape/lib-typography-proxy? library/lib-typography-proxy?)
(set! shape/lib-component-proxy library/lib-component-proxy)