Add better approach for handling plugin iframe url

Ensure params are passed correctly to plugins declared to be version
2 and are prepared to run in a subpath.
This commit is contained in:
Andrey Antukh
2026-02-10 18:35:29 +01:00
parent a0fef67c16
commit 6049fa1c96
7 changed files with 46 additions and 22 deletions

View File

@@ -66,22 +66,22 @@
(update-in state [:workspace-local :open-plugins] (fnil disj #{}) id))))
(defn- load-plugin!
[{:keys [plugin-id name description host code icon permissions]}]
[{:keys [plugin-id name version description host code icon permissions]}]
(try
(st/emit! (save-current-plugin plugin-id)
(reset-plugin-flags plugin-id))
(.ɵloadPlugin
^js ug/global
#js {:pluginId plugin-id
:name name
:description description
:host host
:code code
:icon icon
:permissions (apply array permissions)}
(fn []
(st/emit! (remove-current-plugin plugin-id))))
(.ɵloadPlugin ^js ug/global
#js {:pluginId plugin-id
:name name
:description description
:version version
:host host
:code code
:icon icon
:permissions (apply array permissions)}
(fn []
(st/emit! (remove-current-plugin plugin-id))))
(catch :default e
(st/emit! (remove-current-plugin plugin-id))

View File

@@ -78,6 +78,7 @@
(d/without-nils
{:plugin-id plugin-id
:url (str plugin-url)
:version vers
:name name
:description desc
:host origin

View File

@@ -6,6 +6,7 @@ export const manifestSchema = z.object({
host: z.string().url(),
code: z.string(),
icon: z.string().optional(),
version: z.number().optional(),
description: z.string().max(200).optional(),
permissions: z.array(
z.enum([

View File

@@ -2,5 +2,5 @@ import { z } from 'zod';
export const openUISchema = z.object({
width: z.number().positive(),
height: z.number().positive(),
height: z.number().positive()
});

View File

@@ -1,8 +1,28 @@
import { Manifest } from './models/manifest.model.js';
import { manifestSchema } from './models/manifest.schema.js';
export function getValidUrl(host: string, path: string): string {
return new URL(path, host).toString();
export function getValidUrl(host: string, path: string): URL {
return new URL(path, host);
}
export function prepareUrl(manifest: Manifest, url: string, params: Object): string {
const result = getValidUrl(manifest.host, url);
for (let [k, v] of Object.entries(params)) {
if (!result.searchParams.has(k)) {
result.searchParams.set(k, v);
}
}
if (manifest.version === undefined || manifest.version === 1) {
return result.toString();
} else if (manifest.version === 2) {
const queryString = result.searchParams.toString();
result.search = "";
result.hash = `/?${queryString}`;
return result.toString();
} else {
throw new Error("invalid manifest version");
}
}
export function loadManifest(url: string): Promise<Manifest> {

View File

@@ -1,6 +1,6 @@
import { describe, it, vi, expect, beforeEach, afterEach } from 'vitest';
import { createPluginManager } from './plugin-manager';
import { loadManifestCode, getValidUrl } from './parse-manifest.js';
import { loadManifestCode, getValidUrl, prepareUrl } from './parse-manifest.js';
import { PluginModalElement } from './modal/plugin-modal.js';
import { openUIApi } from './api/openUI.api.js';
import type { Context, Theme } from '@penpot/plugin-types';
@@ -9,6 +9,7 @@ import type { Manifest } from './models/manifest.model.js';
vi.mock('./parse-manifest.js', () => ({
loadManifestCode: vi.fn(),
getValidUrl: vi.fn(),
prepareUrl: vi.fn(),
}));
vi.mock('./api/openUI.api.js', () => ({
@@ -71,7 +72,8 @@ describe('createPluginManager', () => {
vi.mocked(loadManifestCode).mockResolvedValue(
'console.log("Plugin loaded");',
);
vi.mocked(getValidUrl).mockReturnValue('https://example.com/plugin');
vi.mocked(getValidUrl).mockReturnValue(new URL('https://example.com/plugin'));
vi.mocked(prepareUrl).mockReturnValue('https://example.com/plugin');
});
afterEach(() => {
@@ -110,7 +112,7 @@ describe('createPluginManager', () => {
height: 300,
});
expect(getValidUrl).toHaveBeenCalledWith(manifest.host, '/test-url');
expect(prepareUrl).toHaveBeenCalledWith(manifest, '/test-url', {theme: "light"});
expect(openUIApi).toHaveBeenCalledWith(
'Test Modal',
'https://example.com/plugin',

View File

@@ -1,6 +1,6 @@
import type { Context, Theme } from '@penpot/plugin-types';
import { getValidUrl, loadManifestCode } from './parse-manifest.js';
import { prepareUrl, loadManifestCode } from './parse-manifest.js';
import { Manifest } from './models/manifest.model.js';
import { PluginModalElement } from './modal/plugin-modal.js';
import { openUIApi } from './api/openUI.api.js';
@@ -8,6 +8,7 @@ import { OpenUIOptions } from './models/open-ui-options.model.js';
import { RegisterListener } from './models/plugin.model.js';
import { openUISchema } from './models/open-ui-options.schema.js';
export async function createPluginManager(
context: Context,
manifest: Manifest,
@@ -80,9 +81,8 @@ export async function createPluginManager(
};
const openModal = (name: string, url: string, options?: OpenUIOptions) => {
const theme = context.theme as 'light' | 'dark';
const modalUrl = getValidUrl(manifest.host, url);
const theme = context.theme as Theme;
const modalUrl = prepareUrl(manifest, url, {theme});
if (modal?.getAttribute('iframe-src') === modalUrl) {
return;