sentry-electron icon indicating copy to clipboard operation
sentry-electron copied to clipboard

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

Open svallory opened this issue 2 years ago • 19 comments

Is there an existing issue for this?

  • [X] I have checked for existing issues https://github.com/getsentry/sentry-javascript/issues
  • [X] I have reviewed the documentation https://docs.sentry.io/
  • [X] I am using the latest SDK release https://github.com/getsentry/sentry-javascript/releases

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

svallory avatar Jun 05 '23 19:06 svallory

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?

timfish avatar Jun 05 '23 19:06 timfish

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 avatar Jun 05 '23 22:06 svallory

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)

svallory avatar Jun 05 '23 23:06 svallory

@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.

AbhiPrasad avatar Jul 27 '23 16:07 AbhiPrasad

@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

svallory avatar Nov 02 '23 14:11 svallory

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 avatar Nov 02 '23 14:11 AbhiPrasad

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

AbhiPrasad avatar Nov 02 '23 15:11 AbhiPrasad

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

svallory avatar Nov 02 '23 16:11 svallory

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.

AbhiPrasad avatar Nov 02 '23 16:11 AbhiPrasad

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!

iamtekeste avatar Nov 12 '23 01:11 iamtekeste

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

svallory avatar Nov 14 '23 04:11 svallory

@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!

iamtekeste avatar Nov 15 '23 00:11 iamtekeste

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!

AbhiPrasad avatar Nov 20 '23 16:11 AbhiPrasad

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

cameronmcefee avatar Nov 28 '23 19:11 cameronmcefee

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 avatar Nov 29 '23 22:11 svallory

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

svallory avatar Nov 30 '23 04:11 svallory

@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.

iamtekeste avatar Nov 30 '23 15:11 iamtekeste

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

AbhiPrasad avatar Nov 30 '23 17:11 AbhiPrasad

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.

cameronmcefee avatar Dec 11 '23 23:12 cameronmcefee