Skip to content

Commit

Permalink
push project
Browse files Browse the repository at this point in the history
  • Loading branch information
caixiangqi committed Mar 8, 2018
0 parents commit d0dc8b4
Show file tree
Hide file tree
Showing 14 changed files with 3,729 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.DS_Store
node_modules/
122 changes: 122 additions & 0 deletions README.md
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规范")
![notification](https://raw.githubusercontent.com/SangKa/PWA-Book-CN/master/assets/figure6.4.png "notification")

> -> 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
```
65 changes: 65 additions & 0 deletions app.js
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
90 changes: 90 additions & 0 deletions bin/www
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);
}
9 changes: 9 additions & 0 deletions db/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const config = {
host: '***',
user: '***',
password: '***',
database: '***',
port: ***
}

module.exports = config
55 changes: 55 additions & 0 deletions db/index.js
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
}
Loading

0 comments on commit d0dc8b4

Please sign in to comment.