/** * This is a magic file that allows us to run code in the Figma plugin context * from the iframe. It does this by getting the code as a string, and sending it * to the plugin via postMessage. The plugin then evals the code and sends the * result back to the iframe. There are a few caveats: * 1. The code cannot reference any variables outside of the function. This is * because the code is stringified and sent to the plugin, and the plugin * evals it. The plugin has no access to the variables in the iframe. * 2. The return value of the function must be JSON serializable. This is * because the result is sent back to the iframe via postMessage, which only * supports JSON. * * You can get around these limitations by passing in the variables you need * as parameters to the function. * * @example * ```ts * const result = await figmaAPI.run((figma, {nodeId}) => { * return figma.getNodeById(nodeId)?.name; * }, {nodeId: "0:2"}); * * console.log(result); // "Page 1" * ``` */ class FigmaAPI { private id = 0 /** * Run a function in the Figma plugin context. The function cannot reference * any variables outside of itself, and the return value must be JSON * serializable. If you need to pass in variables, you can do so by passing * them as the second parameter. */ run(fn: (figma: PluginAPI, params: U) => Promise | T, params?: U): Promise { return new Promise((resolve, reject) => { const id = this.id++ const cb = (event: MessageEvent) => { if (event.origin !== 'https://www.figma.com' && event.origin !== 'https://staging.figma.com') { return } if (event.data.pluginMessage?.type === 'EVAL_RESULT') { if (event.data.pluginMessage.id === id) { window.removeEventListener('message', cb) resolve(event.data.pluginMessage.result) } } if (event.data.pluginMessage?.type === 'EVAL_REJECT') { if (event.data.pluginMessage.id === id) { window.removeEventListener('message', cb) const message = event.data.pluginMessage.error reject(new Error(typeof message === 'string' ? message : 'An error occurred in FigmaAPI.run()')) } } } window.addEventListener('message', cb) const msg = { pluginMessage: { type: 'EVAL', code: fn.toString(), id, params, }, pluginId: '*', } ;['https://www.figma.com', 'https://staging.figma.com'].forEach((origin) => { try { parent.postMessage(msg, origin) } catch (e) { console.error(e) } }) }) } } export const figmaAPI = new FigmaAPI()