-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
caixiangqi
committed
Mar 8, 2018
0 parents
commit d0dc8b4
Showing
14 changed files
with
3,729 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.DS_Store | ||
node_modules/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
# KOA2-PWA | ||
本项目是pwa推送服务器的简单实现,使用 [koa2](https://github.com/koajs/koa "koa2 github") 开发,使用 [mysql](https://dev.mysql.com/doc/ "mysql") 数据库储存,使用 [pm2](http://pm2.keymetrics.io/ "pm2") 管理进程和日志。 | ||
|
||
## 概念 | ||
[Progressive Web Apps](https://developer.mozilla.org/zh-CN/Apps/Progressive "pwa") 是渐进式Web应用程序,运行在现代浏览器并展现出超级能力。支持可发现,可安装,离线运行,消息推送等APP特有的能力,本项目以最简单的方式封装了消息推送功能在nodejs端的实现。 | ||
|
||
## 开始 | ||
koa2需要nodejs 7.6以上版本。 | ||
``` bash | ||
# install pm2 | ||
npm install -g pm2 | ||
|
||
# install dependencies | ||
npm install | ||
|
||
# serve with hot reload at localhost:3000 | ||
npm start | ||
|
||
# run with pm2 | ||
npm run prd | ||
# or pm2 start bin/www | ||
``` | ||
|
||
## 项目结构 | ||
本项目使用koa-generator生成目录结构。 | ||
- /bin | ||
- www | ||
- /db | ||
- config.js | ||
- index.js | ||
- /routes | ||
- index.js | ||
- notification.js | ||
- /public | ||
- /views | ||
- app.js | ||
- package.json | ||
- package-lock.json | ||
- README.md | ||
|
||
## 运行过程 | ||
pwa 消息推送功能依赖于Service Worker(简称sw),使用 [VAPID 协议](https://tools.ietf.org/id/draft-ietf-webpush-vapid-03.html "web-push VAPID规范")。 | ||
 | ||
|
||
> -> server端使用 [web-push](https://github.com/web-push-libs/web-push "web-push github") 生成vapidKeys(publicKey,privateKey)<br /> | ||
-> server端保存publicKey,privateKey,前端保存publicKey<br /> | ||
-> 前端sw使用加密后的publicKey去订阅并获得订阅对象,然后保存到server端<br /> | ||
-> server端在需要推送时获取订阅对象即可推送 | ||
项目目录中,app.js是启动文件,/bin/www是启动文件的封装,一般使用www启动服务;<br /> | ||
/public是web静态文件,/views是网页模板;<br /> | ||
重点来看/db和/routes,显然/db是跟数据库相关,/routes是跟路由相关。 | ||
```js | ||
// /db/config.js 保存数据库登录信息 | ||
const config = { | ||
host: ***, | ||
user: ***, | ||
password: ***, | ||
database: '***', // 数据库名称,自定义 | ||
port: *** | ||
} | ||
module.exports = config | ||
|
||
// /db/index.js 数据库相关操作 | ||
const mysql = require('mysql') | ||
const config = require('./config') | ||
|
||
const pool = mysql.createPool(config) | ||
|
||
// some sql | ||
|
||
module.exports = { | ||
// some methods | ||
} | ||
``` | ||
**/routes/notification.js:**<br /> | ||
更多推送消息配置项 [Notification options](https://developer.mozilla.org/en-US/docs/Web/API/notification/Notification "配置项") | ||
```js | ||
const router = require('koa-router')() | ||
const webpush = require('web-push') | ||
const dbModel = require('../db/index') | ||
|
||
// VAPID keys should only be generated only once. | ||
// const vapidKeys = webpush.generateVAPIDKeys() | ||
const publicKey = '***' | ||
const privateKey = '***' | ||
const gcmapiKey = 'PWA_LITE_GCMAPIKey' // 自定义,保存在前端manifest.json | ||
const mailto = 'mailto:[email protected]' | ||
|
||
// send notification to client | ||
const sendNotification = (pushSubscription, body) => { | ||
return webpush.sendNotification(pushSubscription, JSON.stringify(body)) | ||
} | ||
|
||
webpush.setGCMAPIKey(gcmapiKey) | ||
webpush.setVapidDetails(mailto, publicKey, privateKey) | ||
|
||
// router prefix | ||
router.prefix('/api/notification') | ||
|
||
// user subscribe | ||
router.post('/subscribe', async (ctx, next) => { | ||
let body = ctx.request.body | ||
let user = [body.authSecret, body.key, body.endpoint] | ||
let pushSubscription = { | ||
endpoint: body.endpoint, | ||
keys: { | ||
auth: body.authSecret, | ||
p256dh: body.key | ||
} | ||
} | ||
let body = { | ||
title: 'Congratulations', | ||
message: `Hello,Thank you for subscribtion!`, | ||
data: {} | ||
} | ||
sendNotification(pushSubscription, body) | ||
// do something | ||
}) | ||
|
||
module.exports = router | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
const Koa = require('koa') | ||
const app = new Koa() | ||
const views = require('koa-views') | ||
const json = require('koa-json') | ||
const onerror = require('koa-onerror') | ||
const bodyparser = require('koa-bodyparser') | ||
const logger = require('koa-logger') | ||
var cors = require('koa2-cors') | ||
|
||
const index = require('./routes/index') | ||
const notification = require('./routes/notification') | ||
|
||
// error handler | ||
onerror(app) | ||
|
||
// middlewares | ||
app.use(bodyparser({ | ||
enableTypes:['json', 'form', 'text'] | ||
})) | ||
app.use(json()) | ||
app.use(logger()) | ||
app.use(require('koa-static')(__dirname + '/public')) | ||
|
||
// views | ||
app.use(views(__dirname + '/views', { | ||
extension: 'pug' | ||
})) | ||
|
||
// cors | ||
app.use(cors({ | ||
origin: '*', | ||
allowMethods: ['GET', 'PUT', 'POST', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'] | ||
})) | ||
|
||
// logger | ||
app.use(async (ctx, next) => { | ||
const start = new Date() | ||
await next() | ||
const ms = new Date() - start | ||
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`) | ||
}) | ||
|
||
// errors response | ||
app.use(async (ctx, next) => { | ||
try { | ||
await next() | ||
} catch (err) { | ||
ctx.status = err.statusCode || err.status || 500 | ||
ctx.response.body = { | ||
ERROR_CODE: 'ERROR', | ||
ERROR_INFO: err | ||
} | ||
} | ||
}) | ||
|
||
// routes | ||
app.use(index.routes(), index.allowedMethods()) | ||
app.use(notification.routes(), notification.allowedMethods()) | ||
|
||
// error-handling | ||
app.on('error', (err, ctx) => { | ||
console.error('server error', err, ctx) | ||
}); | ||
|
||
module.exports = app |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
#!/usr/bin/env node | ||
|
||
/** | ||
* Module dependencies. | ||
*/ | ||
|
||
var app = require('../app'); | ||
var debug = require('debug')('demo:server'); | ||
var http = require('http'); | ||
|
||
/** | ||
* Get port from environment and store in Express. | ||
*/ | ||
|
||
var port = normalizePort(process.env.PORT || '3000'); | ||
// app.set('port', port); | ||
|
||
/** | ||
* Create HTTP server. | ||
*/ | ||
|
||
var server = http.createServer(app.callback()); | ||
|
||
/** | ||
* Listen on provided port, on all network interfaces. | ||
*/ | ||
|
||
server.listen(port); | ||
server.on('error', onError); | ||
server.on('listening', onListening); | ||
|
||
/** | ||
* Normalize a port into a number, string, or false. | ||
*/ | ||
|
||
function normalizePort(val) { | ||
var port = parseInt(val, 10); | ||
|
||
if (isNaN(port)) { | ||
// named pipe | ||
return val; | ||
} | ||
|
||
if (port >= 0) { | ||
// port number | ||
return port; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* Event listener for HTTP server "error" event. | ||
*/ | ||
|
||
function onError(error) { | ||
if (error.syscall !== 'listen') { | ||
throw error; | ||
} | ||
|
||
var bind = typeof port === 'string' | ||
? 'Pipe ' + port | ||
: 'Port ' + port; | ||
|
||
// handle specific listen errors with friendly messages | ||
switch (error.code) { | ||
case 'EACCES': | ||
console.error(bind + ' requires elevated privileges'); | ||
process.exit(1); | ||
break; | ||
case 'EADDRINUSE': | ||
console.error(bind + ' is already in use'); | ||
process.exit(1); | ||
break; | ||
default: | ||
throw error; | ||
} | ||
} | ||
|
||
/** | ||
* Event listener for HTTP server "listening" event. | ||
*/ | ||
|
||
function onListening() { | ||
var addr = server.address(); | ||
var bind = typeof addr === 'string' | ||
? 'pipe ' + addr | ||
: 'port ' + addr.port; | ||
debug('Listening on ' + bind); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
const config = { | ||
host: '***', | ||
user: '***', | ||
password: '***', | ||
database: '***', | ||
port: *** | ||
} | ||
|
||
module.exports = config |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
const mysql = require('mysql') | ||
const config = require('./config') | ||
|
||
const pool = mysql.createPool(config) | ||
|
||
// user table sql | ||
const userSql = { | ||
create: | ||
`create table if not exists user( | ||
user_id int(11) not null auto_increment, | ||
auth_secret varchar(1024) not null, | ||
auth_key varchar(1024) not null, | ||
endpoint varchar(1024) not null, | ||
create_time timestamp not null default current_timestamp, | ||
PRIMARY KEY (user_id) | ||
)auto_increment=10000;`, | ||
insert: | ||
`insert into user set auth_secret=?,auth_key=?,endpoint=?;` | ||
} | ||
|
||
// db query | ||
function query(sql, value) { | ||
return new Promise((resolve, reject) => { | ||
pool.getConnection((err, connection) => { | ||
if (err) { | ||
reject(err) | ||
} else { | ||
connection.query(sql, value, (err, rows) => { | ||
if (err) { | ||
reject(err) | ||
} else { | ||
resolve(rows) | ||
} | ||
connection.release() | ||
}) | ||
} | ||
}) | ||
}) | ||
} | ||
|
||
// db create | ||
async function createTable(sql) { | ||
return query(sql, []) | ||
} | ||
|
||
// user subscribe | ||
async function subscribe(value) { | ||
return query(userSql.insert, value) | ||
} | ||
|
||
createTable(userSql.create) | ||
|
||
module.exports = { | ||
subscribe | ||
} |
Oops, something went wrong.