基本认证机制

认证

HTTP的质询/响应认证框架
Web应用程序收到一个HTTP请求报文时,服务器没有按照请求执行动作,而是以一个”认证质询”进行
响应,要求用户提供一些保密信息来说明他是谁,从而对其进行质询。用户再次发送请求时,要附上
保密证书(用户名和密码)。如果证书不匹配,服务器可以再次质询客户端,或产生一条错误信息。
如果证书匹配,就可以完成正常请求了。
认证协议与首部
HTTP通过一组可定制的控制首部,为不同的认证协议提供了一个可扩展框架,首部格式和内容会随
认证协议的不同而发生变化。认证协议也是在HTTP认证首部中指定的。HTTP定义了两个官方的认证
协议:基本认证和摘要认证。
认证的4个步骤:
首部        描述        方法状态

  • 请求 第一条请求没有认证信息
  • 质询 WWW-Authenticate 服务器用401状态拒绝请求,说明需要用户提供用户名和密码,服务器
    可能分为不同的区域,每个区域都有自己的密码,所以服务器会在WWW-Authenticate首部对保护区域
    进行描述,同样,认证算法也是在WWW-Authenticate首部中指定的。
  • 授权 Authorization 客户端重新发出请求,但这一次会附加一个Authorization首部,用来
    说明认证算法,用户名,密码。
  • 成功 Authentication-Info 如果授权证书是正确的,服务器就将文档返回。有些授权算法会
    在可选的Authentication-Info首部返回一些与授权会话相关的附加信息。

基本认证的安全缺陷

  1. 基本认证会通过网络发送用户名和密码,这些用户名和密码以一种很容易解码的形式表示,不安全
  2. 即使密码是一更难解码的方式加密的,第三方用户仍然可以捕获修改过的用户名和密码,并将
    修改过的用户名和密码一次一次的重放给原始服务器。
  3. 基本认证没有提供任何针对代理和作为中间人的中间节点防护措施,他们没有修改认证首部,但
    却修改了报文的其余部分,这样就严重的改变了事务的本质。

支持的Chrom命令行开关

可以在你的项目的main脚本的ready事件前使用app.commondLine.appendSwitch

1
2
3
4
5
6
7
const {app} = require('electron');
app.commandLine.appendSwitch('remote-debugging-port', '8315');
app.commandLine.appendSwitch('host-rules', 'MAP * 127.0.0.1');

app.on('ready', () => {
// Your code here
});

API TODO

多入口配置

前提需要: 代码拆分

如果你需要对不同的HTML页面使用不同的打包文件可以使用多入口拆分点的特性。webpack会立即
创建多个打包文件,额外的块文件可以被这些打包文件共享。
每一个入口块文件都包含webpack执行时间,所以每个页面加载一个入口块文件(提示:使用CommonsChunkPlugin
可以将运行时的东西移到一个单独的块中来避免这个限制)

配置

可以在entry这个属性中使用对象来配置多入口,每个值作为一个入口块,并且对应的键代表了入口
块的名称。
当使用多入口配置的时候,必须重写默认的output.filename否则每个入口块都会被写在相同的
文件中,使用[name]来获得每个入口块的名字

1
2
3
4
5
6
7
8
9
10
11
{
entry: {
a: "./a",
b: "./b",
c: ["./c", "./d"]
},
output: {
path: path.join(__dirname, "dist"),
filename: "[name].entry.js"
}
}

如何写加载器

加载器是一个返回函数的node模块。
当加载器转换资源时,这个函数将被调用。
一个很简单的实例是,当只有一个加载器应用于原资源文件时,传递一个参数调用加载器:资源文件
内容是字符串.
加载器通过这个函数的this上下文访问loader API
同步加载器可以通过return仅返回一个值,在其他情况下,可以通过this.callback(err, values...)
返回任意数量的值,错误将传给回调函数,或者抛给一个同步加载器。
加载器被期望返回两个值,第一个值是返回JavaScript的字符串代码,第二个值是返回一个资源图的
JavaScript对象。
在复杂的情况下,当有多个加载器链式调用,只有最后一个加载器获得资源文件,仅第一个加载器会
输出两个值(JavaScript和SourceMap),其余的加载器传递值给前一个加载器。

Examples

1
2
3
4
// Identity loader
module.exports = function(source) {
return source;
};

1
2
3
4
// Identity loader with SourceMap support
module.exports = function(source, map) {
this.callback(null, source, map);
};

指导方针(参考)

优先级顺序,第一个应该给最高的优先级,加载器应该:
只做一个简单的任务
加载器可以链式化,为每一步创建加载器,替代一个加载器立刻做任何事。同样的意思就是,如果
没有必要的话不必转换为JavaScript.
例如:通过应用查询参数从模板文件渲染HTML。
写一个加载器从源文件里编译模板,执行然后返回一个组件,它是一个返回了包含HTML代码的字符串
这太糟糕了。
替代方案是,为每一个任务写一个加载器然后全部调用(管道的形式)。

  • jade-loader: 转换模板为一个模块输出一个函数
  • apply-loader: 拿到输出函数并且返回通过应用查询参数返回行结果
  • html-loader: 拿到HTML并输出一个字符串形式的模块
    生产标准的模块
    加载器生成的模块需要像标准的模块遵守同样的管道设计方式。
    例如:这是一个糟糕的设计方式(非标准,通用场景)
    1
    2
    3
    require("any-template-language-loader!./xyz.atl");

    var html = anyTemplateLanguage.render("xyz");

尽可能的标记自己为可缓存

大多数加载器可缓存,所以他们得标记自己为可缓存。
只需在加载器中调用cacheable

1
2
3
4
5
// Cacheable identity loader
module.exports = function(source) {
this.cacheable();
return source;
};

不要在模块和流之间保持状态
一个加载器需要在其它加载器编译时保持独立。
一个模块先前的编译结果,和当前加载器应该保持独立.
标记依赖
如果一个加载器使用了外部资源(从文件系统读),他们必须被告知,被用来使缓存加载器无效
并且在监听方法中重新编译

1
2
3
4
5
6
7
8
9
10
11
12
// Loader adding a header
var path = require("path");
module.exports = function(source) {
this.cacheable();
var callback = this.async();
var headerPath = path.resolve("header.js");
this.addDependency(headerPath);
fs.readFile(headerPath, "utf-8", function(err, header) {
if(err) return callback(err);
callback(null, header + "\n" + source);
});
};

解析依赖
在很多种语言中,有多重模式去指定依赖,比如在css中的@import和url(…),这些依赖关系需
要模块系统去解析。有两种方式去处理:

  • 转换他们为requires
  • 使用this.resolve函数去解析路径
    实例一 css-loader: css-loader在样式表中替换@importurl(...)为require的方式去编译
    依赖关系。
    实例二 less-loader: less-loader不能将@import转换为require因为所有的less文件都会
    被编译到一个临时变量中,因此,less-loader继承了less编译器的通过定义路径解析的逻辑,这个
    定义逻辑使用this.resolve通过模块系统的配置去解析文件(或者定义的模块目录等等)
    如果语言之允许相对路径(比如css中:url(file)意思就是./file),这儿用~的惯例去引用模块
    1
    2
    url(file) -> require("./file")
    url(~module) -> require("module")

提取公共代码
不要在加载器中去处理每个模块中相同的代码,不要生成这些公共代码,在加载器中创建一个运行时
文件,通过require来使用这些公共代码
不要嵌入绝对路劲
不要在模块代码中使用绝对路径,当项目被移动时会破坏hash,在loader-utils中有一个
stringifyRequest的方法将绝对路径转换为相对路径。

1
2
3
4
var loaderUtils = require("loader-utils");
return "var runtime = require(" +
loaderUtils.stringifyRequest(this, "!" + require.resolve("module/runtime")) +
");";

进程对象

progress(进程)

进程对象的扩展
progress对象扩展了以下的API

事件

‘loaded’事件
当Electron已经加载完它内部的初始化脚本并准备加载web页面或者main脚本时触发.
当node集成被关闭时,它可以用来预加载移除的node全局变量给全局作用域

1
2
3
4
5
6
7
// preload.js
const _setImmediate = setImmediate;
const _clearImmediate = clearImmediate;
process.once('loaded', () => {
global.setImmediate = _setImmediate;
global.clearImmediate = _clearImmediate;
});

属性

process.noAsar
设置为true可以使nodejs的asar内置文件无效。
process.type
当前的进程类型,可以是"browser"(主进程)或者"render"
process.versions.electron
Electron的字符串版本号
process.versions.chrome
Chrome的字符串版本号
process.resourcesPath
资源目录的路劲
process.mas
对于Mac App Store,这个属性是true,其余的构建是undefined
process.windowsStore
对于Windows的app这个属性是true,其他的则是undefined
process.defaultApp
应用启动时,这个值作为参数默认传入,在主进程中这个属性是true,其他方面是undefined

方法

进程有以下的方法:
process.crash()
销毁当前进程的主线程
process.hang()
挂起当前进程的主线程
process.setFdLimit(maxDescriptors) macOS Linux

  • maxDescriptors 整数
    设置文件描述符软限制于 maxDescriptors 或硬限制与os, 无论它是否低于当前进程.
    process.getProcessMemoryInfo()
    返回当前进程的内存使用总计计的一个对象,注意所有的统计以千字节报告:
  • workingSetSize- 被压入到真实RAM物理机的内存总计
  • peakWorkingSetSize- 被压入到真实RAM物理机的的最大内存
  • privateBytes- 不被其他进程共享的内存总计,比如JS堆,HTML内容
  • sharedBytes- 进程间共享的内存总计,通常情况下是Electron自己的代码消耗的内存

process.getSystemMemoryInfo()
返回系统内存使用总计的对象,注意所有的统计以千字节报告:

  • total- 系统可用物理内存的总计
  • free- 还有多少空闲内存可用

在Windows或Linux

  • swapTotal
  • swapFree

API概要

如何使用Nodejs和Electron的API

nodejs所有的内置模块在Electron都是可用的并且第三方模块也支持。
Electron也提供了一些额外的内置模块用来开发传统的桌面应用,一些模块只能在主进程中使用,
一些只能在渲染进程中使用,也有一些可以同时在主进程和渲染进程中使用。
基本规则就是: GUI或者系统底层的模块只能在主进程中使用,要使用这些模块,你应当很熟悉主进
程vs渲染进程脚本的概念。
主进程中的脚本就像普通的nodejs脚本:

1
2
3
4
5
6
7
8
const {app, BrowserWindow} = require('electron');

let win = null;

app.on('ready', () => {
win = new BrowserWindow({width: 800, height: 600});
win.loadURL('https://github.com');
});

渲染进程和web页面没有什么不同,除了它有使用node模块的能力:

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html>
<body>
<script>
const {app} = require('electron').remote;
console.log(app.getVersion());
</script>
</body>
</html>

如果想运行应用,阅读quik startrun your app

解构赋值

自0.37起,你可以使用destructuring assignment
更便捷的使用内置模块.

1
const {app, BrowserWindow} = require('electron');

如果你需要Electron全部的模块,你可以引用electron并且使用解构来访问个别的模块:

1
2
const electron = require('electron');
const {app, BrowserWindow} = electron;

这和下面的语法是一样的

1
2
3
const electron = require('electron');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;

在线离线事件探测

使用标准的HTML5API可以在渲染进程中实现在线和离线事件的探测,如下所示

main.js

1
2
3
4
5
6
7
8
9
10
const electron = require('electron');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;

let onlineStatusWindow;

app.on('ready', () => {
onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false });
onlineStatusWindow.loadURL(`file://${__dirname}/online-status.html`);
});

online-status.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html>
<body>
<script>
const alertOnlineStatus = () => {
window.alert(navigator.onLine ? 'online' : 'offline');
};

window.addEventListener('online', alertOnlineStatus);
window.addEventListener('offline', alertOnlineStatus);

alertOnlineStatus();
</script>
</body>
</html>

主进程是没有navigator这个对象的,所以不能直接的探测这些事件,使用Electron的进程间通信
的工具,这些事件将会被传递到主进程,并且根据需要去处理,下面的就是个例子:

main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const electron = require('electron');
const app = electron.app;
const ipcMain = electron.ipcMain;
const BrowserWindow = electron.BrowserWindow;

let onlineStatusWindow;

app.on('ready', () => {
onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false });
onlineStatusWindow.loadURL(`file://${__dirname}/online-status.html`);
});

ipcMain.on('online-status-changed', (event, status) => {
console.log(status);
});

online-status.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
<body>
<script>
const {ipcRenderer} = require('electron');
const updateOnlineStatus = () => {
ipcRenderer.send('online-status-changed', navigator.onLine ? 'online' : 'offline');
};

window.addEventListener('online', updateOnlineStatus);
window.addEventListener('offline', updateOnlineStatus);

updateOnlineStatus();
</script>
</body>
</html>