大家好。
今天来学习和研究Figma的登录功能,毕竟这是APP所有功能的基础。
当作为前端我们研究某个网页的时候,下意识的就是打开chrome devtool,Figma也是保留了这个快捷键,MacOS环境下,command + option + i
就可以唤起。
加载远端登录页
打开Figma客户端,一阵“白屏”之后,加载出来简单的登录页面。切到Network快捷键command + shift + R
强制刷新下页面。Doc加载的是https://www.figma.com/login?locale=en&is_not_gen_0=true
的资源,果然加载的远端资源,一生要强的前端er们,时常和白屏做斗争。这里登录页面猜测都在Web项目维护,做了拆包,查看整体Network资源加载,总共3M多。
白屏
即使只有3M多资源大小,对于现在国内网络环境,如果有插着网线的机器,更是微不足道。但是个人认为,这个页面更新频率不高,可以被划分到本地的渲染进程里,独有的登录页。随安装包分发到本地,通过loadFile加载更为合适。
也可能出于项目管理上的考虑,这么小的内容不值得单独管理,并再为之引入埋点、数据分析监控。
再回到Network,看到除了加载js文件,还加载了登录状态对应提示信息的JSON、两种数据分析监控,甚至还连接了两个ws。
发起登录请求
不管点击login还是create account,获取该次登录的hash。
https://www.figma.com/api/session/app_auth
{
"meta": {
"id": "auth_code_1",
"app_type": "desktop"
},
}
Web授权
这时候会唤起默认浏览器,打开授权页面,如: https://www.figma.com/app_auth/{{ auth_code_1 }}/grant?desktop_protocol=figma&locale=en
。
打开的地址会携带重新唤起APP的协议头desktop_protocol=figma
。
electron唤起默认浏览器打开URL
框架默认支持功能。
const { shell } = require('electron');
// 在默认浏览器中打开URL
shell.openExternal('https://www.example.com').then(() => {
console.log('网页已在浏览器中打开!');
}).catch(err => {
console.error('打开浏览器失败:', err);
});
跨平台实现
在Windows上,可以使用ShellExecute或ShellExecuteEx函数。MacOS则是NSWorkspace的openURL方法,Linux有xdg-open。印象中几个平台都支持,只是在Linux反向唤起APP的时候,设置比较麻烦。
授权登录
到这里完全就托管给web端去实现登录了,不管是未注册、未登录还是直接授权,能都够复用现有的Web能力了。为什么要这么做呢?直接将登录页面嵌入客户端,不更好吗?如果登录与后续绑定倒是挺合适的,但是如果涉及到三方、单点登录这些场景,就不这么好处理了。
举个例子,假设需要接入第三方内部OA系统的授权登录,出于安全考虑,不允许在非安全客户端环境下登录,这样则需要接入该三方提供的SDK,或者将客户端环境,设置成为可以被校验安全的环境(比如固定的UA字段)。不管是修改环境还是接入SDK,都需要对客户端做出兼容修改。一旦接入的客户量足够多,这种方案根本管理不过来,无法维护。况且,Web端也需要进行相应的兼容。
这里跳转到浏览器,使用Web的登录状态,不管叫中介、代理还是什么模式都好,就将客户端和三方隔离开了。
再结合Figma面向团队、组织这类ToB模式的产品,就更好理解客户端为什么选择了这种登录方式了。
同意授权
回到Figma登录流程,在web端有登录状态之后,点击授权,发起请求验证登录。
https://www.figma.com/api/session/app_auth/{{ auth_code_1 }}/grant
{
"error": false,
"status": 200,
"meta": {
"id": "auth_code_1",
"app_type": "desktop",
"g_secret": "auth_code_2"
},
"i18n": null
}
URL Scheme,浏览器唤起electron客户端
获得g_secret信息后,流程就需要重新唤起客户端APP了。不管是桌面端还是移动端,外部唤起APP,都需要再URL Scheme表注册内容,在协议请求的时候,会去校验这个表,如存在对应协议,则会获取协议的启动路径,执行APP的启动文件。
electron 可以通过protocol模块设置URL Scheme。
const { app, protocol, net } = require('electron')
app.whenReady().then(() => {
protocol.handle('atom', (request) =>
net.fetch('file://' + request.url.slice('atom://'.length)))
})
或者通过构建工具如electron builder配置。不太建议自行管理,需要兼容多平台。另外需要注意杀毒软件,可能会导致注册失败.
接着我们将Network过滤状态调整为过滤刚才请求头的协议figma://
。很明显,将二次验证的g_secret在唤起APP的时候传递过去了。
figma://app_auth/redeem?g_secret=auth_code_2
登录
流程回到客户端,在授权之前把Network网络调成3G,方便我们观察数据。直接的就能看到,客户端使用g_secret
请求接口,换取用户id。
https://www.figma.com/api/session/app_auth/redeem
{
g_secret: "auth_code_2"
}
{
"error": false,
"status": 200,
"meta": {
"workspace": {
"userId": "userId",
"orgId": null
}
},
"i18n": null
}
最终时序图如下
对着这完整的流程,为什么要设计这么多授权相关的code呢?直接将web端的登录状态保持信息(Cookie)给到客户端不就可以了吗?
首先在产品形态上,客户端和Web不应该共用Cookie,也不能在URL通过明文传输(前端之间加密可以说是加寂寞),所以设计的g_secret
,回看唤起客户端的URL,这个值其实也很不安全,按道理客户端需要保有用来处理g_secret
值的信息,并且在获取用户信息的时候同时提供。换言之,第一步的id和g_secret对应不上也是不允许的。这里Figma使用了个临时的回话,Cookie里的figma.session来做这件事情,再进一步释放了客户端的复杂度。这里没做g_secret
存活时间、在另外机器使用的验证,原理都有做时效和使用次数的限制的。
在这整个流程中,目前还没有认识到会是哪种具名的授权方式。大家如果知道,还望告知。
最后
最后总结下,本文主要拆解了Figma客户端的登录整个流程,并且对Electron唤起浏览器、浏览器唤起Electron做了个简单的介绍。
学习和研究Figma的登录功能,就先到这里了,希望对您有帮助。