Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't configure Sentry for a Figma plugin code (running inside an Electron isolated context) #680

Open
3 tasks done
svallory opened this issue Jun 5, 2023 · 19 comments
Open
3 tasks done

Comments

@svallory
Copy link

svallory commented Jun 5, 2023

Is there an existing issue for this?

How do you use Sentry?

Sentry Saas (sentry.io)

Electron SDK Version

Unknown, I'm using Figma 116.9.6

Electron Version

22.3.5

What platform are you using?

MacOS

Link to Sentry event

No response

Steps to Reproduce

I'm not that familiar with Electron, but simply importing a sentry package on the code side of my Figma plugin breaks Figma itself.

This documentation page has an explanation on how plugins run inside of Figma.

I had no problems setting up Sentry on the UI part of the plugin which runs inside an iframe.

Here's my rollup config that builds the code that runs on the ssandbox:

import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import typescript from "@rollup/plugin-typescript";
import { sentryRollupPlugin } from "@sentry/rollup-plugin";

export default [
  // CODE.JS
  // The part that communicates with Figma directly
  // Runs in a sandboxed environment (isolated context in electron)
  // with no access to Node, the DOM, or any of the node/browser APIs
  {
    input: "src/code/init.ts",
    output: {
      file: "dist/code.js",
      format: "iife",
      name: "code",
      sourcemap: true,
    },
    plugins: [
      typescript(),
      resolve({
        browser: true,
        moduleDirectories: ["node_modules"],
      }),
      commonjs({ transformMixedEsModules: true, typescript: true }),
      // production && terser(),
      // Put the Sentry rollup plugin after all other plugins
      sentryRollupPlugin({
        org: "toki-labs",
        project: "cva-code",

        // Auth tokens can be obtained from https://sentry.io/settings/account/api/auth-tokens/
        // and need `project:releases` and `org:read` scopes
        authToken: "xxxxxxxxxxxx",
      })
    ],
  }
]

Expected Result

Figma does not crash and errors are sent to Sentry

Actual Result

Here's the error I'm getting:

00:24:17.803 figma_app.min.js.br:5 Error: Syntax error on line 2380: Unexpected token ...
    at sHo (figma_app.min.js.br:1628:98)
    at async figma_app.min.js.br:1758:6222
(anonymous) @ figma_app.min.js.br:5
vef @ figma_app.min.js.br:1758
await in vef (async)
(anonymous) @ figma_app.min.js.br:1758
(anonymous) @ figma_app.min.js.br:1758
runLastPlugin @ figma_app.min.js.br:2409
_FigmaApp_runLastPlugin @ compiled_wasm.js.br:2
$wasm-function[12721] @ compiled_wasm.wasm.br:0xa5d952
$wasm-function[19997] @ compiled_wasm.wasm.br:0xc1b6a8
$wasm-function[1022] @ compiled_wasm.wasm.br:0x21a75
$wasm-function[18831] @ compiled_wasm.wasm.br:0xbfea0a
$wasm-function[8628] @ compiled_wasm.wasm.br:0x662bca
$wasm-function[29734] @ compiled_wasm.wasm.br:0xfd09e1
$wasm-function[3412] @ compiled_wasm.wasm.br:0x1a2cc7
$wasm-function[26663] @ compiled_wasm.wasm.br:0xd6f638
(anonymous) @ compiled_wasm.js.br:2
wrappedCallback @ compiled_wasm.js.br:2
r @ figma_app.min.js.br:16
@timfish
Copy link
Collaborator

timfish commented Jun 5, 2023

The Electron SDK is designed to work in Electron apps where you control the full app code. Figma is an Electron app but it looks like Figma plugins have their own sandboxed runtime which is nothing like that of plain Electron.

For example, the Electron SDK uses Electrons IPC to communicate between the browser and main process code. Figma supports something similar via it's own API's but this SDK was never designed to use them. It looks like there are other restrictions which might limit many other features of the SDK.

Have you tried using the Sentry browser SDK in your front end and the Node SDK in your backend?

@svallory
Copy link
Author

svallory commented Jun 5, 2023

Have you tried using the Sentry browser SDK in your front end and the Node SDK in your backend?

I did and it didn't work either. I checked the API docs to see if I could call the API directly to create events but didn't an endpoint for that. Is there documentation on how to manually talk to Sentry using plain javascript?

Here's everything I have access to from the plugin main thread in the sandbox:

{
    "clearInterval": "ƒ innerRealmSafeFunction(...innerRealmArgs)",
    "clearTimeout": "ƒ innerRealmSafeFunction(...innerRealmArgs)",
    "console": "{log: ƒ, error: ƒ, assert: ƒ, info: ƒ, warn: ƒ, …}",
    "fetch": "ƒ innerRealmSafeFunction(...innerRealmArgs)",
    "figma": "{apiVersion: '1.0.0', getStyleById: ƒ, moveLocalPaintStyleAfter: ƒ, moveLocalTextStyleAfter: ƒ, moveLocalGridStyleAfter: ƒ, …}",
    "setInterval": "ƒ innerRealmSafeFunction(...innerRealmArgs)",
    "setTimeout": "ƒ innerRealmSafeFunction(...innerRealmArgs)",
    "Infinity": "Infinity",
    "Array": "ƒ Array()",
    "ArrayBuffer": "ƒ ArrayBuffer()",
    "Boolean": "ƒ Boolean()",
    "DataView": "ƒ DataView()",
    "Date": "ƒ Date()",
    "Error": "ƒ Error()",
    "EvalError": "ƒ EvalError()",
    "Float32Array": "ƒ Float32Array()",
    "Float64Array": "ƒ Float64Array()",
    "Function": "ƒ ()",
    "Int8Array": "ƒ Int8Array()",
    "Int16Array": "ƒ Int16Array()",
    "Int32Array": "ƒ Int32Array()",
    "Intl": "Intl {getCanonicalLocales: ƒ, supportedValuesOf: ƒ, DateTimeFormat: ƒ, NumberFormat: ƒ, Collator: ƒ, …}",
    "JSON": "JSON {Symbol(Symbol.toStringTag): 'JSON', parse: ƒ, stringify: ƒ}",
    "Map": "ƒ Map()",
    "Math": "Math {abs: ƒ, acos: ƒ, acosh: ƒ, asin: ƒ, asinh: ƒ, …}",
    "NaN": "NaN",
    "Number": "ƒ Number()",
    "Object": "ƒ Object()",
    "Promise": "ƒ Promise()",
    "Proxy": "ƒ Proxy()",
    "RangeError": "ƒ RangeError()",
    "ReferenceError": "ƒ ReferenceError()",
    "Reflect": "Reflect {defineProperty: ƒ, deleteProperty: ƒ, apply: ƒ, construct: ƒ, get: ƒ, …}",
    "RegExp": "ƒ RegExp()",
    "Set": "ƒ Set()",
    "String": "ƒ String()",
    "Symbol": "ƒ Symbol()",
    "SyntaxError": "ƒ SyntaxError()",
    "TypeError": "ƒ TypeError()",
    "URIError": "ƒ URIError()",
    "Uint8Array": "ƒ Uint8Array()",
    "Uint8ClampedArray": "ƒ Uint8ClampedArray()",
    "Uint16Array": "ƒ Uint16Array()",
    "Uint32Array": "ƒ Uint32Array()",
    "WeakMap": "ƒ WeakMap()",
    "WeakSet": "ƒ WeakSet()",
    "alert": "ƒ innerRealmSafeFunction(...innerRealmArgs)",
    "decodeURI": "ƒ decodeURI()",
    "decodeURIComponent": "ƒ decodeURIComponent()",
    "encodeURI": "ƒ encodeURI()",
    "encodeURIComponent": "ƒ encodeURIComponent()",
    "escape": "ƒ escape()",
    "eval": "ƒ eval()",
    "isFinite": "ƒ isFinite()",
    "isNaN": "ƒ isNaN()",
    "parseFloat": "ƒ parseFloat()",
    "parseInt": "ƒ parseInt()",
    "undefined": "undefined",
    "unescape": "ƒ unescape()"
}

@svallory
Copy link
Author

svallory commented Jun 5, 2023

One way to make it work I guess is to send the error in a message (via Figma's messaging API) and have the UI forward it to Sentry. If that's the way to go, I would love some guidance on how to neither lose nor mess up event information (especially regarding error location)

@AbhiPrasad
Copy link
Member

@svallory you might unfortunately have to directly construct event objects, serialize them across the wire and then call Sentry.captureEvent on the event in your backend to send them to Sentry.

@svallory
Copy link
Author

svallory commented Nov 2, 2023

@AbhiPrasad this got sent way down in our priority list back then. But now, with the official launch coming up, we'll soon start working on a Figma plugin SDK which will include several packages. We've already created a test runner, RPC, and typed message bus. We'll just need to extract them into publishable packages. All written in pure ECMAScript and able to run anywhere.

I would love to include a figma-sentry package to the mix! Is that something that could interest Sentry? I mean to the point of allocating a developer to work with me on this. I'm pretty much an expert on Figma plugin dev now, after all these months and with someone that knows the Sentry API and either sentry-electron or (probably more appropriate) sentry-javascript package, I think we could create that package pretty fast. Also, I can do most of the coding, mostly what I need is a guide through the codebase, so I don't have to study all of it.

I think that's good marketing for Sentry. In fact, Figma's plugin development experience is in desperate need of some tools to improve DX. Specially for complex plugins. Also, if it runs on Figma, it'll probably run on any other javascript plugin env out there, like Coda plugins and Notion integrations

@AbhiPrasad
Copy link
Member

Hey @svallory! A separate package sounds like a great idea!

Ping me on the Sentry discord @AbhiPrasad and I'm happy to hop on a call to walk through things! Interested in your architecture and the constraints of the figma environment.

@AbhiPrasad
Copy link
Member

@svallory I'm curious if you were able to handle the stacktrace issues reported here getsentry/sentry-javascript#8860?

@svallory
Copy link
Author

svallory commented Nov 2, 2023

Hey @svallory! A separate package sounds like a great idea!

Ping me on the Sentry discord @AbhiPrasad and I'm happy to hop on a call to walk through things! Interested in your architecture and the constraints of the figma environment.

Awesome! I'll definitely do that. :)

@svallory I'm curious if you were able to handle the stacktrace issues reported here getsentry/sentry-javascript#8860?

I haven't had that issue since we did not add Sentry to our product yet. But from a quick look, during development, enabling Figma's "Developer VM" should fix it. I'm not a 100% sure that is fixable for stacks captured in production, but if it is, there are only two options:

  1. Embed the source maps in the JS files
  2. Store each deployed version alongside the source maps, use the exact Figma version to load the plugin, grab the Figma generated JS file for the plugin, replace anonymous by the filename and run the source mapper

In fact, I don't remember if I ever got source maps to work without embedding them

@AbhiPrasad
Copy link
Member

Store each deployed version alongside the source maps, use the exact Figma version to load the plugin, grab the Figma generated JS file for the plugin, replace anonymous by the filename and run the source mapper

I have a feeling that this might be the only way - but might have to run this in a worker for performance reasons.

@iamtekeste
Copy link

I am also looking into how to debug figma plugin JS error in production and not having luck! Here is what I have tried so far.
Let's say this is the error message being shown on the figma devtools

Uncaught Error: failing
    at o (<anonymous>:1:14147)
    at e (<anonymous>:1:14176)
    at Object.__ (<anonymous>:1:14196)
    at X (<anonymous>:1:11332)
    at Array.forEach (<anonymous>)
    at Xo (<anonymous>:1:11019)

I renamed anonymous to the source file name

Uncaught Error: failing
    at o (<ui.js>:1:14147)
    at e (<ui.js>:1:14176)
    at Object.__ (<ui.js>:1:14196)
    at X (<ui.js>:1:11332)
    at Array.forEach (<ui.js>)
    at Xo (<ui.js>:1:11019)

I then run it through stacktracify which spits out this

Uncaught Error: failing
    at [unknown] (../src/ui.tsx:1:0)
    at render (../src/ui.tsx:21:15)
    at [unknown] (<stdin>:2:21)
    at [unknown] (../node_modules/preact/hooks/src/index.js:507:1)
    at Array.forEach
    at clearTimeout (../node_modules/preact/hooks/src/index.js:443:2)

So it got the file name correct the original file name was indeed called ui.tsx but the rest is wrong or unhelpful.

Here is what ui.tsx looks like
CleanShot 2023-11-11 at 19 59 36@2x

import { render } from "@create-figma-plugin/ui";
import { h } from "preact";
import { useEffect } from "preact/hooks";

function Plugin() {
  const mysecondfunc = () => {
    throw new Error("failing");
  };

  const myfirstfunc = () => {
    mysecondfunc();
  };

  useEffect(() => {
    myfirstfunc();
  }, []);

  return <h1>hello</h1>;
}

export default render(Plugin);

I could use any help or other approaches you can recommend!

@svallory
Copy link
Author

I could use any help or other approaches you can recommend!

Hey @iamtekeste,

This is a wild guess, but looking at the result you are getting from stacktracify, I think you are missing source maps. Here's why:

Method e was correctly unminified to render, so the source maps for your code is there. But methods from packages were not. You may want to check what's in your source map using the source-map package or by modifying the stacktracify code to log some stuff, e.g. by uncommenting this line to show the source content.

There are too many variables to list. E.g. what bundler are you using, are you using IIFE, are the source maps embedded in the files, etc. So, if you want (and can) to create a repo and share your files (or some demo files) all take a look at it. I've already created a Figma test runner and an RPC lib that will be part of @figma-plugin-sdk, and I think adding production debug helpers would be an awesome addition.

Btw, If Sentry wants to sponsor it, I can make a sentry-figma-plugin package instead of a generic one XD

@iamtekeste
Copy link

@svallory thank you for the offer!
Here is a repo to reproduce this issue. It was created with create-figma-plugin which uses esbuild behind the scenes.

@figma-plugin-sdk sounds interesting! As someone who builds lots of figma plugins I am always looking to improve my workflow!

@AbhiPrasad
Copy link
Member

We should try to see if we can get someone from figma to help here. @iamtekeste I'm fairly certain the issue is also with sourcemaps. Sentry has a guide for verifying if sourcemaps are working, you can follow this part of the Sentry docs for it.

@svallory were you able to get the discord going okay? still available there to chat!

As an aside:

We do offer sponsorships for open source projects (gave away 500k recently)!

Happy to ask to get your name/GH org added to our yearly fund for the extra help here!

@cameronmcefee
Copy link

Subscribing to this thread, as I'd also like to see a sentry package for Figma plugins.

@svallory
Copy link
Author

Hey, @iamtekeste! I'm so sorry. I don't know how I missed your response. Are you still having issues with the sourcemaps? I'll take a look at your repo now

@AbhiPrasad I never pinged you because the monitoring got sent back to backlog and launching v2 was more important. We'll launch it this week though, so I'll ping you now

@svallory
Copy link
Author

svallory commented Nov 30, 2023

Here you go @iamtekeste

what was missing:

  • esbuild option sourcemap should be set to "inline"
  • added the keepNames: true to show original function names when minifying (optional)

Some extras (just for fun):

  • Added a --show-config option you can pass to build-figma-plugin to show build config
  • esbuild.base.config to share common config options among main and ui
  • esbuild.mergeConfigs.js to merge all config objects (CFP <|-- base.config <|-- build-figma-plugin.(main|ui).js)
  • added support for --esb-* CLI options so any esbuild option in the root level can be overridden via command line
  • configured npm scripts for each scenario: build, build:with-maps, build:show-config, and watch

Remember to enable the Developer VM!

Here's the PR with the changes

And if you want to debug in VS Code navigating through the original code, read this article I wrote a while ago

╭─ ~/projects/fun/figma-sourcemaps-issue on main !3 ?4 
╰─❯ pnpm run build:with-maps --show-config

> @ build:with-maps /Users/svallory/projects/fun/figma-sourcemaps-issue
> build-figma-plugin --typecheck --minify --esb-keep-names --esb-sourcemap=inline "--show-config"

info Typechecking...
success Typechecked in 1.677s
info Building...

///////////////////////////////////////////////////////////////
    esbuild configuration for main.js
===============================================================

{
  bundle: true,
  logLevel: 'silent',
  minify: true,
  outfile: '/Users/svallory/projects/fun/figma-sourcemaps-issue/build/main.js',
  platform: 'neutral',
  plugins: [],
  // ...
  target: 'es2017',
  sourcemap: 'inline',
  keepNames: true
}

///////////////////////////////////////////////////////////////


///////////////////////////////////////////////////////////////
    esbuild configuration for ui.js
===============================================================

{
  bundle: true,
  jsxFactory: 'h',
  jsxFragment: 'Fragment',
  loader: {
    '.gif': 'dataurl',
    '.jpg': 'dataurl',
    '.png': 'dataurl',
    '.svg': 'dataurl'
  },
  logLevel: 'silent',
  minify: true,
  outfile: '/Users/svallory/projects/fun/figma-sourcemaps-issue/build/ui.js',
  plugins: [
    { name: 'preact-compat', setup: [Function: setup] },
    { name: 'css-modules', setup: [Function: setup] }
  ],
  // ...
  target: 'chrome58',
  sourcemap: 'inline',
  keepNames: true
}

///////////////////////////////////////////////////////////////

success Built in 0.674s

@iamtekeste
Copy link

@svallory amazing work, this going to improve my debugging workflow by a lot! Hit me up whenever you setup your sponsorship page so I can contribute a little bit.

@AbhiPrasad
Copy link
Member

We're continuing the chat about this on discord: https://discord.com/channels/621778831602221064/1179816276009439302

@cameronmcefee
Copy link

This is technically off topic for the thread here, but looking at the original error you posted:

00:24:17.803 figma_app.min.js.br:5 Error: Syntax error on line 2380: Unexpected token ...

I've run into this myself, the cause being that Sentry seems to be compiled for es6. Figma doesn't support the spread operator, so it's failing to parse the Sentry code. Using Sentry with Figma seems to also require some additional configuration to transpile it so it can even be used.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants