Figma插件开发(三)

1,597 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情

书接前文 《Figma插件开发(二)》。本文介绍了插件登录鉴权、设计稿内容导出等相关内容。

插件内登录鉴权

Web中的身份认证

我们知道,HTTP 协议是无状态协议,服务端如何知道请求者的用户身份呢?这个过程我们叫做身份认证(Authentication)。Web系统中常用的身份认证可以分成两类:

  • 基于 Session 的认证

  • 基于 Token 的认证

我们常说的认证方式有:Cookie + SessionCookie + TokenJWT等等,其实都可以归结为上述两种。

基于 Session 的认证,服务端会维护每个用户的 Session(会话)信息,客户端需要保存一个session-id,接口请求的时候带给服务端。通常会把session-id 保存在 cookie 里面,因为浏览器会自动把 cookie 放到 HTTP 请求的 header 中。这就是最经典、最常用的 Cookie + Session认证。

基于Token的认证,服务端不需要维护会话信息,而是根据用户信息生成一个Token 下发给客户端。客户端每次请求必须带上这个 Token,服务端收到Token 后会校验Token的合法性并解析出用户信息。客户端的Token可以保存在Cookie中,请求时会自动带上(这就是Cookie + Token)。也可以保存在 LocalStorage 中,请求时取出Token 并放到HTTP Header 中。通常 JWT 认证会选用后者。

可见无论选择哪种认证方式,都需要客户端存储一个认证信息,并请求时带给服务端。

Figma插件中的身份认证

前文已经讲过,Figma插件运行在一个Data URLs的 iframe中,无法使用 Cookie 和 LocalStorage。Figma提供了APIfigma.clientStorage 来替代LocalStorage。由于这些限制,JWT是比较合适的选择。

JWT全称JSON Web Token,也就是通过JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程中还可以完成数据加密签名等相关处理。

关于 JWT 认证的细节就不展开多说了,读者可自行了解。实际上其他认证方式也可以,受限的只是存储认证因子的方式和携带认证因子的方式。客户端需要做的就是登录的时候获取一个 Token 并保存到本地,每次请求的时候放到 Header中。

JWT 为例,Figma插件的身份认证流程如下:

blog-figma-plugin-2_dmrn8pYQmGTztKT74rEGSk.png

OAuth 认证

前面的JWT 例子用的是用户名&密码的认证方式,实际中我们常用的是OAuth 认证,比如QQ、微信登录。

在浏览器中使用OAuth认证,通常是跳转到第三方页面,认证完成后再跳转回来,并携带上认证Token。但是在Figma插件中无法进行URL跳转,无法访问第三方页面,导致认证流程变得更加复杂。

Figma中发起登录请求后,打开外部浏览器进行认证,同时Figma轮询请求服务端获得认证结果,待浏览器中认证成功,服务就可以将认证结果返回给Figma。流程如下:

oauth_uMGiaYCDTuVb26VQQ8uSPE.png

也可以参考官方文档中的 《OAuth with Plugins》

设计稿导出

我们来实现一个功能:将选中的设计稿节点导出为png格式的图片。

整体流程为:

  1. 监听选中事件,获取当前节点id;

  2. 根据节点id,导出设计稿的buffer数据;

  3. 将buffer转换为图片格式,可以保存到本地或者上传到云端;

监听选中事件

选中设计稿会触发主线程的 selectionchange 事件,我们要在UI线程监听这个事件的话,需要传递一个回调。我们用前面讲到的rpct 来封装接口。

主线程注册方法:

// worker/plugin-methods.ts
type SelectionChangeCallback = (nodeIds: string[]) => void;

export class PluginMethods {
  listenSelectionChange(onSelectionChange: SelectionChangeCallback) {
    figma.on('selectionchange', () => {
      const selection = figma.currentPage.selection;
      return selection.map(x => x.id);
    });
  }
  // ...
}

在插件中可以这样使用(vue组件代码):

const currentSelectId = ref<string | null>(null);
// 通过rpct直接调用主线程方法
pluginApi.listenSelectionChange(bindCallback(async (nodeIds) => {
  const nodeId = nodeIds?.[0];
  if (!nodeId) return;
  currentSelectId.value = nodeId;
}));

导出buffer数据

figma节点有通用的方法,可以导出 UintuArray 格式的数据。同样可以定义在 PluginMethods 中,UI线程可以直接调用。

// worker/plugin-methods.ts
export class PluginMethods {
  // ...
  async getNodeImageBuffer(nodeId: string): Promise<Uint8Array> {
    const node = figma.getNodeById(nodeId) as SceneNode;
    const buffer = await node.exportAsync();
    return buffer;
  }
}

转换为图片

如果要在插件中展示所选的设计稿,那么可以将buffer转换成base64 格式,然后使用 img 标签展示。

export async function arraybufferToBase64(data: ArrayBuffer) {
  // Use a FileReader to generate a base64 data URI
  const base64url = await new Promise<string>((r) => {
    const reader = new FileReader();
    reader.onload = () => r(reader.result as unknown as string);
    reader.readAsDataURL(new Blob([data]));
  });

  return base64url;
}

<script lang=ts sfc>
// ...

const currentSelectId = ref<string | null>(null);
const currentSelectBase64 = ref<string>('');
pluginApi.listenSelectionChange(bindCallback(async (nodeIds) => {
  const nodeId = nodeIds?.[0];
  if (!nodeId) return;
  currentSelectId.value = nodeId;
  const buffer = await pluginApi.getNodeImageBuffer(currentSelectId.value);
  currentSelectBase64.value = await arraybufferToBase64(buffer);
}));
</script>

<template>
    <!-- 展示当前选择 -->
      <img :src="currentSelectBase64">
    <!-- ... -->
</template>


上传到云端

假设我们已经有一个后端写好的上传接口了。

// 上传设计稿
export async function uploadImage(fileName: string, buffer: Uint8Array): Promise<string> {
  const formData = new FormData();
  formData.append('file', new Blob([buffer.buffer], { type: 'image/png' }), fileName);
  const rsp = await http.request({
    method: 'POST',
    url: '/figma/image/upload',
    data: formData,
  });
  return rsp.data?.data.url;
}

保存到本地

有了图片的ArrayBuffer,我们可以浏览器的下载API保存图片。

// 下载设计稿
export async function downloadImage(fileName: string, buffer: Uint8Array): Promise<string> {
  const blob = new Blob([buffer.buffer], { type: 'image/png' });
  const url = window.URL.createObjectURL(blob);
  const a = ducument.createElement('a');
  a.href = url;
  a.download = fileName;
  a.click();
}

结语

本文介绍了开发Figma 插件用到的一些常用功能,示例代码可以参考figma-plugin-vue3 ——这是一个用Vue3搭建的编写Figma插件的模板项目。如果你在开发Figma插件中有遇到问题,欢迎一起交流讨论。