关于Node.js的依赖管理

关于Nodejs的依赖管理

npm和yarn之类的工具能够自动化引入和构建依赖,但是最近发现一个比较坑的地方。

在npm上发布的package一般来说会锁定其所依赖的包的版本

我在使用request-promise的时候,发现request-promise已经依赖了tough-cookie,于是在把几个代码文件迁移到另一个项目的时候没有重新添加tough-cookie的依赖,导致中间解析cookie时出现了很多莫名其妙的错误,遂重新添加依赖后才成功。

也就是说,当一个项目越做越大以后,里面的包的依赖关系将会变得更加复杂,一个包所依赖的其他包的可能包含了某个package的多个版本。而随着package的增多,这种臃肿的包将会越来越多。

不禁想反思一下前端这个步子是不是迈得太大了

浅述使用 Javascript 事件劫持干扰 Vue v-model的一些思路

浅述使用 Javascript 事件劫持干扰 Vue v-model的一些思路

关于近日在开发一个UserScript的时候一些做法和思考

相关:

前言

本文主要探讨一下Javascript解决Vue的v-model绑定机制的问题。案例实现代码如上。

主要实现方法是事件捕获和事件生成。

准备

首先我们需要准备一个Vue页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>测试页面</title>
    <script src="//cdn.bootcss.com/vue/2.5.13/vue.js" ></script>
    <script src="//cdn.bootcss.com/jquery/3.3.1/jquery.js" ></script>
</head>
<body>
<div id="app1">
    <div id="in">
        <textarea name="input" id="myinput" cols="60" rows="5" v-model="text" @input.prevent="text=text.substr(0,10)"></textarea>
        <p>
            <span>输入:</span>{{text}}
        </p>
        <p>
            <button @click="sended=text,text=''">点击发送</button> {{sended}}
        </p>
    </div>
</div>
</body>
<script>
    new Vue({
        el:"#app1",
        data:{
            text:"",
            sended:"",
        },
    });
</script>
</html>

该页面实现了将输入的内容回显,在点击发送的时候将内容显示到发送位置,并且限制文本框最大输入字符个数为10,接下来将使用用户脚本,来干扰 Vue 的 v-model。

用户脚本

用户脚本是指由用户指定的运行在页面之上的 Javascript 脚本,用于为网站提供新功能或者对页面进行修改使其更易用,主要依赖 油猴、Tampermonkey 等浏览器插件,现在也有很多浏览器自带用户脚本的功能,在专门的网站上可以搜索到其他人上传的用户脚本,比如 GreasyFork

Vue 中的 v-model 是通过监听事件来实现监听页面元素更改的

用户脚本相当于是在页面中直接插入由你编写的一个 js 文件,并由浏览器扩展负责在每次展现指定页面的时候都自动引入这个文件。

为了方便调试,我们直接使用Chrome控制台来进行代码调试

调试

  • 首先,使用jq修改输入框
    运行$("#myinput").val("1234567890"),发现textarea元素改变,但是没有在页面中看到输入的回显,点击发送时也不能获取该值,说明 v-model 中的值没有得到更新。
  • 第二步,伪造一个input事件
    使用 $("#myinput")[0].dispatchEvent(new Event("input")) 后,发现能够正确回显,说明 v-model 的值得到了更新
  • 第三步,确认 v-model 的更新位置
    由于事件存在捕获阶段和冒泡阶段,我们在input上使用了stop修饰符阻止了事件冒泡,所以只需要添加一些监听器即可。

使用以下代码部署监听器:

let m = $("#myinput")[0];
m.addEventListener("input",(e)=>{console.log("textarea :在冒泡阶段发现该事件!")});
m.addEventListener("input",(e)=>{console.log("textarea :在捕获阶段发现该事件!")},true);
let d = $("#in")[0];
d.addEventListener("input",(e)=>{console.log("div :在冒泡阶段发现该事件!")});
d.addEventListener("input",(e)=>{console.log("div :在捕获阶段发现该事件!")},true);

然后使用 $("#myinput")[0].dispatchEvent(new Event("input")),触发监听器,结果为:

div :在捕获阶段发现该事件!
textarea :在冒泡阶段发现该事件!
textarea :在捕获阶段发现该事件!

textarea的冒泡事件触发在捕获事件之前,测试了一下时间,大概确实是在目标阶段,会先触发元素的冒泡时间,再触发捕获事件。(也许冒泡事件的利用率高,所以为了提高性能故意提前?坐等指教)。

可以看到在整个事件流中,v-model 的监听是在目标阶段,然后就因stop修饰符阻止了事件继续冒泡。所以如果我们要对 input 事件进行劫持,可以依靠父元素的事件监听,在捕获阶段将事件劫持。

尝试

刷新页面清除之前的监听器,然后使用以下代码,尝试在捕获阶段对事件进行劫持:

$("#in")[0].addEventListener("input",(e)=>{
    e.stopPropagation(); //阻止事件传播
    console.log("Hijacked A Event");
    return false;
},true);

此时,直接在输入框中进行输入不会回显,可以看到控制台打印出 Hijacked A Event,说明劫持事件已经完成。

完成事件的劫持,则我们最终对 v-model 的欺骗完成了一半,接下来要使用自定义事件来使 v-model 的值可以被js 修改。

首先,刷新页面清除之前的监听器,然后对之前的生成事件的代码进行修改。

function FakedValue(){
     $("#myinput").val("Faked");//修改value    
  
    let e = new Event("input");
    e.myself = true;
      $("#myinput")[0].dispatchEvent(e)//伪造事件,触发V-model更新
}

为事件设置自定义属性,即可在捕获的时候进行区分,监听器的代码只需修改成:

$("#in")[0].addEventListener("input",(e)=>{
    if(e.myself) return true; //判断是否是伪造的事件
    e.stopPropagation(); //阻止事件传播
    console.log("Hijacked A Event");
    return false;
},true);

在页面上运行上述代码,然后就可以发现,自己在输入框的输入会被劫持,而执行 FakedValue() 才可以触发v-model的更新。

总结

以上就是使用 Javascript 通过对事件的劫持来对v-model进行控制的一些思路,对按钮的click事件劫持也可以使用同样的操作。

该文仅供同样开发 UserScript 的同学们参考,正常的网页开发用不到这些内容。详细案例代码参考在文章开头。

tampermonkey的一个坑

关于Tampermonkey脚本开发时遇到的一个坑

  • GM_XmlhttpRequest
    这个GM函数在wiki上是如此解释的:

Make an xmlHttpRequest.
Property of details:
method one of GET, HEAD, POST
url the destination URL
headers ie. user-agent, referer, ... (some special headers are not supported by Safari and Android browsers)
data some string to send via a POST request
binary send the data string in binary mode
timeout a timeout in ms
context a property which will be added to the response object
responseType one of arraybuffer, blob, json
overrideMimeType a MIME type for the request
anonymous don't send cookies with the requests (please see the fetch notes)
fetch (beta) use a fetch instead of a xhr request
(at Chrome this causes xhr.abort, details.timeout and xhr.onprogress to not work and makes >xhr.onreadystatechange receive only readyState 4 events)
username a username for authentication
password a password
<events> onload, onerror, onreadystatechange, onprogress, onloadstart, ontimeout

在这个地方,约定了Data参数,与ajax的不同,是使用string字符串的形式,详细表述在网上也有一些,但是问题不在这里
比较坑的是,使用post方法传送数据时,一直出现400错误,经过反复排查,才发现headers首部设置不能随便添加值,headers的字段会对请求发生干扰,由于chrome环境没法捕获数据包,因此不清楚到底是哪里发生了错误。

解决这个错误之后,又发现,不设置headers字段时,服务器上会接收不到参数
必须设置headers参数为

headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }

至此,一个完整的post请求才算正常

API停止

Typecho竟然有0day,已更新迁移到新版本,API ALL down,转至其他地方

直播间自动领便当的升级

记一次脚本升级

2017年10月下旬,伟大的二次元同性交友网站更新了他们的直播页面,在前几天反复收到脚本更新的反馈请求,于是抽了两天时间仔细研究了一下 BiliBili 的新直播间页面。

  • api.live.bilibili.com和live.bilibili.com混用

在新的页面上,所有的数据接口全部转向了api.live.bilibili.com,使用jsonp的方法跨域请求,尤其是宝箱验证码的部分,也是这样一个跨域的图片,由于跨子域的图片会污染canvas导致无法识别,所以这否决了在当前页面中进行验证码识别的方案,只能另外寻找识别方法。

基于iframe的跨子域通信的方法,可以将验证码识别单独放在一个iframe中,脚本另外绑定该网址,在该网址中进行验证码识别,在需要识别验证码的时候,主页面打开iframe,脚本完成识别后立即回调主页面的验证函数,即可做到处理图片跨域的问题。

这里也很想吐槽 标准 ,明明iframe都可以跨子域相互通信,那么禁止图片跨子域的意义不大啊……如果可以使用脚本的话,不过这对网站方来说还是有一定意义的。

  • vue.js使用的双向绑定技术无法识别JS操作value的结果

在vue.js的双绑中,是没法识别到JS对input元素的操作的,因此在验证码识别完成后,提交之时还是会显示验证码为空,原因在于v-model中的值没有能够正确改变。

网上对与该问题只找到一个处理办法,使用.focus函数先使input取得焦点,实际测试的时候还是发现并不能做到100%解决这个问题,其他方案大多数是建议操作input时同样适用vue.js内部的方法。

该问题困扰了我很长时间,最后依然无法解决,只能另外写一套计时程序,仿照其API与后台进行通讯,也就是目前的做法。于是又不得不写了一套消息提示函数和ajax函数。最后使用iframe进行回调。

整个处理的流程就是在第一次载入以后读取目前的宝箱状态,然后将原有的宝箱隐藏,用自己的一套计时函数替代原有的函数,最后按照流程嵌套,即可完成最终的识别和验证过程

有一个比较坑的就是在验证完成之后,需要请求另一个API来刷新服务器的宝箱状态,这个我觉得是多余的,但是不得不加上去。否则服务器不会为下一个宝箱计时。

最后将脚本整合在同一个脚本中,完成一些扫尾工作,就算是完成这一次升级了。