原文:http://www.zcfy.cc/article/396
我每天都使用 Facebook,也许太着迷了,以至于我总想潜入他们内部。我说的"潜入"指的是研究他们用来实现许多用户界面和交互的 JavaScript。
几个月之前,我开始思考在聊天中,当对方正在打字时,出现的那个正在输入的指示符。这只是一个 UI 的小细节,但是能透露出你的交谈者许多信息。如果正在输入的指示符反复出现和消失,说明对方在犹豫什么。如果正在输入的指示符是持续出现的,说明他们与你的谈话可能比较轻松。有时候看到它们出现然后又消失,对方却最终没有说话,没有比这更折磨人的了。
这动画看起来像是张自以为是的脸,我觉得它在嘲弄我!
首先得弄明白显示指示符所需要的数据是否都被发送给了所有交谈者,不论他们是否打开了消息窗口。我的第一步是打开调试工具的 Network 选项卡,开始寻找当有人正在对我输入信息时的活动请求。
最终我找到了!typ
事件被通过全局的 Facebook 长轮询 /pull
请求发送。一些小的实验可以证实实际上每个交谈都会发送这个事件,甚至包括那些没有被我开着的聊天窗口,和从来没和我聊过天的人。
尽管我们可以止步于此,但是我觉得我有义务用这个发现来做一些东西。
因此我向你介绍我的"恐怖"发明:Facebook 第六感。一个可以在有人开始和结束对你输入消息时给你警告信息的 Chrome 插件。
这篇文章的剩余内容会详细解释我如何使用 Facebook 自己的私有 API 来构建出这个插件的。
如今,大多数应用倾向于通过工具压缩线上的 JavaScript,常用的压缩工具例如 UglifyJS 或者 Google Closure Compiler 就是用来做这个的。然而压缩后的代码不是字节码,这些工具在不改变应用程序预期行为的基础上能够有如此高的压缩比让人印象深刻。压缩之后用户能够在较短的时间内加载页面,公司也能节省带宽成本。双赢不是吗?
除了我们这件事。
压缩后源代码的可读性降低了很多。局部变量名字被替换成单个字母,失去了它们在程序中的实际含义,一些非关键的冗余语法被去除了或者替换成了短一些的替代方案。空白符也被取出了,语法结构变得难懂。
我们迷失在数以万字符计的单行混淆的代码中
别慌!我们可以通过增加一些换行和缩进来恢复一些条理。事实上,Chrome 开发者工具甚至为我们提供了这个功能。
现在至少看起来有条理一些
好了,我们可以开始阅读一些代码了!
JavaScript 依然没有很好地支持模块化。一个标准已经被提出,但还没有一个浏览器实现了它。与此同时,一些临时解决方案被提出,例如 Asynchronous Module Definition 和 CommonJS。
Facebook 自己实现了 AMD 模块管理。模块通过 __d(name, dependencies, factory)
来定义,通过 require
和 requireLazy
来加载。
我会告诉你自己在控制台输入一些代码试试看,而 Fackbook 在控制台里面输出了一个非常明显的警告让你不要拷贝和粘贴不确定的代码到控制台里。这是一个好主意,别动控制台,好吗?
这个 Facebook 的家伙一定是个严肃认真的家伙。最好遵守他所说的
我是在开玩笑,操家伙上吧!
> require("React")
// Object {Children: Object, PropTypes: Object, DOM: Object, version: "16.0.0-alpha"}
是的,Facebook 总是运行 React 的主干版本。这非常大胆,但是很酷。
你可以在开发者工具的 Sources 选项卡中搜索 __d
找到所有可以被加载的模块。使用 Alt+Shift+F 来查找所有的源代码文件。如果你去找了,你会发现模块有一大堆。
才 3000 个模块?图样图森破!这只是主页面。
如今你可能已经知道 React。这是一个 Facebook 开发的非常酷的库,它能让你使用 JavaScript 语法来声明式定义你的 view 组件。我没打算做 React 的广告,但是如果你还对它很陌生,先去弄懂它。
正如我们所见,Facebook 在他们主要网站使用 React,使用得非常广泛。在 2015 年 10 月,它们已经拥有超过 15,000 个 React 组件了!
另一个关于 React 组件的好消息是,只要代码写得正确,模块之间是有非常明确地分工的。也就是说,它们的依赖是明确的,大部分情况下每个模块只负责做并做好一件事。而这个性质对我们接下来进一步做的事来说十分有用。
别忘了,我们是来尝试拦截给我们发消息的人的输入通知的。让我们从聊天的消息框开始研究。
我发誓,我有真正的朋友。
还记得关于 React 组件吗?聊天消息框是其中一个组件。有一个非常好的技巧来检查它的细节:使用 React 开发工具。
点击聊天消息框,选择 Inspect
,然后到开发者工具的 React
选项卡。搞定!现在你来到 React 的领地了!
检查 React。
看这个,多酷啊!
别迷失在 Facebook React 组件的桃花源里。我们要寻找一个特别的东西:聊天输入指示符,一种最常被发现在聊天室底部的令人难以捉摸的生物。
就是这个,强大的聊天输入提示符
如果你在界面上找不到,你依然可以在 <ChatTabComposerContainer />
和 <MercuryLastMessageIndicator />
标签之间找到它。
好,先在我们有个更好的主意了,让我们进一步深入了解它。
在 React 代码库里查找 __d('ChatTyping
得到两个模块,ChatTypingIndicator.react.js
, 和 ChatTypingIndicators.react.js
,这是我们要找的。注意 .react 前缀表示模块是一个 React 组件。
如果你找不到 ChatTypingIndicators.react.js
模块,你需要打开一个聊天窗口去再查找一次。一些模块只有当用到它时才会被加载。
React 组件在它的生命周期中会触发一些 hooks。例如,当它在 DOM 中载入之后,componentDidMount
方法会被执行。
这个方法是订阅事件和异步加载数据的首选地方,所以这里是我们首先要看的。
好,看起来是正确的。
以下是代码全貌:
function() {
var k = c("MercuryThreadInformer").getForFBID(this.props.viewer)
, l = c("MercuryTypingReceiver").getForFBID(this.props.viewer);
this._subscriptions = new (c("SubscriptionsHandler"))();
this._subscriptions.addSubscriptions(
l.addRetroactiveListener(
"state-changed",
this.typingStateChanged
),
k.subscribe(
"messages-received",
this.messagesReceived
)
);
},
看到 c('MercuryTypingReceiver')
调用了吗? 这是压缩版的 require('MercuryTypingReceiver')
。所以我们在检索一个非常明确的函数 MercuryTypingReceiver
,它调用 typingStateChanged
方法,只要它的内部状态有所改变。我们小模块开始看起来很像一个 Flux Store 了。但是我们并不需要真正去弄明白它是如何运作的,这个 addRetroactiveListener()
看起来像是我们要找的。
窥视 typingStateChanged
方法,我们可以获得 MercuryTypingReceiver
存储在 store 的数据结构。它像是一个字典一样将聊天的消息线程 id 对应到一个 user id 的数组里。我们一会而再回来处理它,让我们先尝试使用这个 MercuryTypingReceiver
。
使用我们的新朋友 require
函数,我们可以在控制台里面检查 MercuryTypingReceiver
。让我们来看一下。
> const MercuryTypingReceiver = require("MercuryTypingReceiver");
// undefined
> MercuryTypingReceiver
// function j(k){/* bunch of gibberish */}
看起来不妙。如我们在前面所见过的,MercuryTypingReceiver
调用一个 getForFBID
静态方法。然而,Chrome 开发者工具不允许我们直接查看一个函数里的属性。为了绕开这个限制,我们需要将它包装成一个对象:
看起来好多了
(新技能 Get,控制台里查看一个函数上的属性,可以用 {function(){}}
---- 译者注)
现在让我们来看一下它的两个静态方法,get
和 getForFBID
:
> MercuryTypingReceiver.getForFBID
// function (i){var j=this._getInstances();if(!j[i])j[i]=new this(i);return j[i];}
> MercuryTypingReceiver.get
// function (){return this.getForFBID(c("CurrentUser").getID());}
由此,我们可以断定 MercuryTypingReceiver.getForFBID(fbid)
从一个 FBID
字符串中查找一个 MercuryTypingReceiver
实例,而 MercuryTypingReceiver.get()
是一个通用的函数为当前用户查找 MercuryTypingReceiver
实例。我们可以丢掉 getForFBID()
只要使用 get()
就可以了。
你应该还记得我们的 React 组件调用了 MercuryTypingReceiver
实例上的 addRetroactiveListener(eventName, listener)
方法。它使用起来非常简单:
const inst = MercuryTypingReceiver.get();
inst.addRetroactiveListener("state-changed", state => {
console.log(state);
});
现在,如果你收到了一些输入状态改变的通知,你已经可以在控制台上看到一些状态对象被打印出来了。一个简单的测试方法是自己输入要发送的消息。你可以使用你手机上的 Messenger app 来发送输入事件。
好了,我们可以庆幸我们之前猜对了,MercuryTypingReceiver
存储状态是一个字典,它将消息线程 id 对应到所有正在输入字符的 user id。
这是正我们之前寻找的,但是没有办法将消息线程 id 和 thread's name 关联起来,也没有办法将 user id 和 user's full name 关联起来,它真的没有告诉我们很多有用的信息。
在代码中做了更多的尝试之后,我找到了两个非常有用的模块:
MercuryThreads
允许我们通过它的 id 检索一个消息线程里的所有消息。ShortProfiles
做同样的事情, 但是检索的是用户配置。我们甚至可以在同一时间批量处理多个线程和多个用户的信息!
我打算再进一步深入关于上面两个 API 的实现细节,因为我想下面的代码本身已经非常能说明问题了。
一共 40 多行代码
function getUserId(fbid) {
return fbid.split(":")[1];
}
requireLazy(
["MercuryTypingReceiver", "MercuryThreads", "ShortProfiles"],
(MercuryTypingReceiver, MercuryThreads, ShortProfiles) => {
MercuryTypingReceiver
.get()
.addRetroactiveListener("state-changed", onStateChanged);
// Called every time a user starts or stops typing in a thread
function onStateChanged(state) {
// State is a dictionary that maps thread ids to the list of the
// currently typing users ids"
const threadIds = Object.keys(state);
// Walk through all threads in order to retrieve a list of all
// user ids
const userIds = threadIds.reduce(
(res, threadId) => res.concat(state[threadId].map(getUserId)),
[]
);
MercuryThreads.get().getMultiThreadMeta(threadIds, threads => {
ShortProfiles.getMulti(userIds, users => {
// Now that we"ve retrieved all the information we need
// about the threads and the users, we send it to the
// Chrome application to process and display it to the user.
window.postMessage({
type: "update",
threads,
users,
state,
}, "*");
});
});
}
}
);
这篇文章我最想让大家明白的是 hook 一个有着良好结构的现代 Web 应用是多么容易的事情。JavaScript 模块组合,React 和 Flux 使我们能够将我们的逻辑代码注入到 UI 和应用的数据流中去。
你可以在 Github 上找到 完整源代码.
这是全部!希望你阅读愉快。如果有任何问题,你可以在 twitter 上联系我 @Morhaus。
英文原文:http://kirszenberg.com/facebook-sixth-sense
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。