JoyQi - Javascript https://joyqi.com/category/develop/javascript zh-CN Fri, 07 Jul 2017 15:34:00 +0800 Fri, 07 Jul 2017 15:34:00 +0800 思路清奇:通过 JavaScript 获取移动设备的型号 https://joyqi.com/javascript/how-to-detect-mobile-devices-model-using-javascript.html https://joyqi.com/javascript/how-to-detect-mobile-devices-model-using-javascript.html Fri, 07 Jul 2017 15:34:00 +0800 JoyQi 我们一般在浏览器里识别用户的访问设备都是通过 User Agent 这个字段来获取的,但是通过它我们只能获取一个大概的信息,比如你用的是 Mac 还是 Windows,用的是 iPhone 还是 iPad。如果我想知道你用的是第几代 iPhone,这个方法就不行了,前段时间我正好有这个需求,识别移动客户端的具体型号(主要是 iOS 设备),于是思考了下这个问题的实现。

首先,我跟大家一样想到了 UA,不过事实证明这路走不通。就在我无聊一个一个摆弄浏览器的 API 时,突然一篇文章里的某段代码提醒了了我。这篇文章讲的是怎样通过 js 获取图形设备信息的,因为 HTML5 支持了 canvas,所以可以通过 API 获取图形设备的型号,比如显卡的型号。

(function () {
    var canvas = document.createElement('canvas'),
        gl = canvas.getContext('experimental-webgl'),
        debugInfo = gl.getExtension('WEBGL_debug_renderer_info');

    console.log(gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL));
})();

运行这段代码就可以获取显卡的型号了,如果你在iOS的设备里运行,会获取到诸如 Apple A9 GPU 之类的信息。而我们知道每一代 iOS 设备的 GPU 型号都是不同的,比如 iPhone 6 是 A8,而 iPhone 6s 就是 A9。看到这里,你应该大概知道我的思路了,就是通过识别 GPU 的型号来辨别设备的型号。

不过这还有个小瑕疵,有些设备是同一代,也就是 GPU 型号完全相同,比如 iPhone 6s, iPhone 6s Plus, iPhone SE。它们用的都是 Apple A9 GPU,怎么区分开它们呢?你会发现它们最大的不同不就是分辨率不同吗?而通过 JavaScript 我们又可以方便地获取屏幕分辨率,这样把两个手段综合应用一下就可以获取设备的准确型号了。

这里有个示例网址,大家可以用手机访问
https://joyqi.github.io/mobile-device-js/example.html

我的代码都放在了 GitHub 上
https://github.com/joyqi/mobile-device-js

这次思考给了我一些解决问题的启发,我们在思考解决方案的时候从侧面入手说不定会有新的发现。就比如我们的这个代码,目前还无法识别同一代的 iPad Air 和 iPad mini,因为它们的 GPU 和分辨率均相同,但是延续这个思路其实是有很多解决方案的,比如大家可以研究下这两个设备的话筒和喇叭个数,而这个数量也是可以通过 JS 获取的 :P

]]>
3 https://joyqi.com/javascript/how-to-detect-mobile-devices-model-using-javascript.html#comments https://joyqi.com/feed/category/develop/javascript/javascript/how-to-detect-mobile-devices-model-using-javascript.html
CoffeeScript 是一门让人上瘾的语言 https://joyqi.com/javascript/awesome-coffescript.html https://joyqi.com/javascript/awesome-coffescript.html Mon, 17 Apr 2017 02:06:00 +0800 JoyQi 屏幕快照 2017-04-17 上午2.48.37.png

昨天偶然去逛 CoffeeScript 官网发现,这门陪伴我时间最短,但是我最喜欢的语言已经发布了 2.0 版。虽然目前还在 Alpha 阶段,但也足以让我高兴。

新版本主要是对 ES6 的支持加强,比如 class 会被直接编译为 ES6 的 class,而不是 1.0 中用恶心的 prototype。类似的还有之前的剩余参数 (a, b, args...) 会被直接翻译为 function (a, b, ...args)

除了这些,还有对asyncawait的支持,这让我们在 CoffeeScript 上也可以使用这一最先进的生产力了,避免了回调地狱的产生,真是大好。不过需要注意的是,支持这些新特性需要你将 node.js 升级到至少 7.6+

我的标题里说了,CoffeeScript 是一门让人上瘾的语言。这在我身上已经得以验证,我认为优雅的程序员都要去学习它。就像它的名字一样,它可以让你干净利落地完成一件工作,就像喝着咖啡那样 decent。

这是我刚刚顺手写的一个脚本,因为我之前写程序习惯用四个空格缩进,直到今天我才注意到 CoffeeScript 官方推荐的是两空格。作为一个很懒的程序员,我当然不会一行一行地去改。因此我哼着歌写着代码,几分钟就完成了这飘着咖啡香味的代码。

fs = require 'fs'
path = require 'path'

dir = process.argv.pop()

listFiles = (dir) ->
  fs.readdirSync dir
    .forEach (file) ->
      file = path.join dir, file

      if (fs.statSync file).isDirectory()
        listFiles file
      else if /\.coffee$/.test file
        changeIndention file


changeIndention = (file) ->
  console.log file

  content = fs.readFileSync file, encoding: 'utf8'
    .split "\n"
    .map (line) ->
      line.replace /^(\s*)(.*)$/, (m, a, b) ->
        (a.substring 0, a.length / 2) + b
    .join "\n"

  fs.writeFileSync file, content, encoding: 'utf8'


listFiles dir

把它保存为文件 file.coffee,然后你只需要运行

coffee file.coffee {目标目录}

{目标目录} 替换为你的coffee文件存放的目录即可。此脚本会自动将此目录下所有的 .coffee 文件的缩进大小缩减为原来的一半。

我小小总结了下使用 CoffeeScript 的好处

  1. 干净,我对代码整洁干净有一些近乎偏执的追求
  2. 没有废话。相比之下 JavaScript 简直是一个废话连篇的语言,废话越多意味着越甘于平庸,有追求的人当有自己的语言。
  3. 保持敏锐。大家在写 JS, PHP, JAVA 这类比较大众化的语言时有没有这种感觉,写得越多越麻木,写到最后你自己都不知道在写什么玩意。这对程序员来说实际上是很要命的,写程序是需要想象力的,当你的想象力被冗长的语言消磨没了的时候,你的大脑就再也闪现不出那些美妙的创意了。而使用 CoffeeScript 时,你会不自觉地想要审视你的代码,让它直指需求所在,让它更贴近你想要的东西。

我使用 CoffeeScript 也已经有三年多的时间,目前我的绝大部分 JS 代码都由其完成。这个过程让我受益匪浅,甚至有段时间我都不愿去碰 PHP 这门宇宙第一大语言(手动滑稽。。。)。我建议大家可以去看看它的文档,这会让你以后的编码过程都心情愉悦的。

题外

四月一过半,可以不开空调在外浪的日子也已经屈指可数了。我赶紧抓住春天的尾巴,在周五把公司的小伙伴拉去完成承诺已久的爬山

IMG_2955.JPG

山顶的石板路,阳光很充足,四周没有遮挡,但是非常安静。人群过后,只听得见风声

IMG_2958.JPG

这是我爬完后,坐在路边的排水渠上等公交时照的。

IMG_2950.JPG

而当我终于爬上去的时候,脑子里回响的却是李宗盛的《山丘》。

]]>
9 https://joyqi.com/javascript/awesome-coffescript.html#comments https://joyqi.com/feed/category/develop/javascript/javascript/awesome-coffescript.html
纯手工自制的内网穿透瑞士军刀 Socket Pipe https://joyqi.com/javascript/socket-pipe.html https://joyqi.com/javascript/socket-pipe.html Thu, 29 Dec 2016 15:06:00 +0800 JoyQi 今年撸的内部工具比较多,其中比较有用的我认为Socket Pipe绝对算一个。如果你有如下需求

  1. 需要做端口转发(udp or tcp)又懒得去配置复杂的iptables规则甚至还要用到nginx这种牛刀
  2. 需要把内网的某个监听端口反向穿透复杂的路由影射到一个固定的外网端口上(我知道有ngork,但是使用上限制较多而且还要收费不是吗)
  3. 你的茫茫多内网http服务需要在外网也能访问

那么我开发的这款小工具就再适合你不过了,它究竟有什么功能呢?我们先从最简单的说起

对了,忘了说如何安装了,这款工具是基于node.js的,所以你要先有它的运行环境,然后直接用npm安装即可

npm install -g socket-pipe

端口转发

这个理解起来很简单,就是把源地址的某个端口映射到当前运行 socket-pipe 这台机器的指定端口上,相当于做了一个透明的网络代理。比如我要把 192.168.1.10080 端口映射到本地的 80 端口,那么直接运行

socket-pipe -l 127.0.0.1@80 -r 192.168.1.100@80 -t tcp

它同时支持 tcp 和 udp 协议,只要把 -t 参数改成 udp 即可

tcp 反向代理

这个在我们平时的开发需求中也很大,比如我们在内网已经开发好了一套网络服务,现在要放到公网上调试,以前你可能还要在公网上搭一套环境,然后运行程序,调试出错起来特别麻烦。而使用 socket-pipe 可以让公网的访问流量直接到达你的本机,并且不需要在路由器上做任何设置

首先你需要一个拥有公网ip的服务器(在这里比如 ip 地址是 123.123.123.123),安装好 socket-pipe 以后,假如我们要求外网访问它的 8888 端口那么

socket-pipe -l 123.123.123.123@18888 -r 123.123.123.123@8888 -t tserver

注意这里的 -r 参数指定了能被任意访问的监听地址,而 -l 参数则指定了一个接受局域网里的服务器反向代理网络请求的地址,后面我们会用到这个地址

然后我们在本地局域网的服务器上同样运行 socket-pipe,假如本地(192.168.1.100)要调试的这个网络服务端口是 7777 那么

socket-pipe -l 192.168.1.100@7777 -r 123.123.123.123@18888 -t tclient

这样我们就把本地 192.168.1.1007777 端口,完全透明地映射到远程 123.123.123.1238888 端口上了。

http 反向代理

可能你也注意到了,上面的 tcp 反向代理也可以用来代理 http 协议,因为其本身就是基于 tcp 的。确实是可以的,但是你如果只有一台服务器需要代理还好说,如果你有非常多的 http 服务需要暴露在公网上,你可能还要在公网服务器的 socket-pipe 前端加一台 nginx 之类的,把它的每个域名分配给每个连接上来的端口,然后还要启动茫茫多的 socket-pipe 进程来代理每台内网服务器,并且每次新增一台还要手动去改 nginx 配置。

这显然成本太高,但 ngork 不就是解决这个问题的么。确实可以解决,但首先 ngork 网络流量不可控,涉及到一些敏感信息的会有泄密的可能,其次它速度比较慢,我们自己有更快的服务器为什么不能用自己的,最后,它的一些高级服务或者更多的数量需要收费。

所以 socket-pipe 的最后这一个功能可以彻底干掉 ngork 了,它可以把无限多的内网 http 反向映射到外网去

同样我们先在外网服务器上运行,既然是代理 http 服务,那我们就直接监听 80 端口好了

socket-pipe -l 123.123.123.123@10080 -r 123.123.123.123@80 -t hserver

同样我们指定了一个 10080 端口来接受反向代理请求。然后在局域网的服务器上

socket-pipe -l 192.168.1.100@80 -r 123.123.123.123@10080 -t hclient -x git.dev.com -s git

在这里多了两个可选参数

  1. -x 这个参数可以将外网请求的 Host 头转化为指定的地址,比如你在外网访问的域名是 www.example.com,到本地http服务器上的时候就被 socket-pipe 转换为了 git.dev.com。如果你不填这个参数,那么就不会转换任何地址
  2. -s 指定了一个外网访问的二级域名。因为我们上面说了,有一堆 http 都被映射到了同一台外网服务器上,那么如何区分并单独访问他们呢,就是二级域名。比如你把 *.example.com 的泛域名全部解析到了 123.123.123.123 上,那么通过 -s git 参数,你访问 git.example.com 这个地址,流量就会被转发到当前指定的地址上。如果这个参数不填,socket-pipe 会自动创建一个随机字符串作为二级域名,并且会输出告诉你。

写在最后

目前这个工具已经在我们内部使用了一段时间,使用起来还是非常舒爽的,还有几个小问题需要解决

  • udp的反向代理,虽然我没有这个需求,但我了解到有人需要这个
  • 连接的稳定性,目前我们有心跳和自动重连机制,但是在某些情况下会失效,我正在处理这个问题

欢迎大家给我提bug和需求
https://github.com/joyqi/socket-pipe

]]>
17 https://joyqi.com/javascript/socket-pipe.html#comments https://joyqi.com/feed/category/develop/javascript/javascript/socket-pipe.html