├── .editorconfig ├── .gitattributes ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── gulpfile.js ├── lib └── index.js ├── package.json └── test └── index.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - v5 4 | - v4 5 | - '0.12' 6 | - '0.10' 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 calidion (calidion.github.io) 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-weixin-jssdk [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][daviddm-image]][daviddm-url] [![Coverage percentage][coveralls-image]][coveralls-url] 2 | 3 | > JSSDK Functions for weixin 4 | 5 | 6 | ## Install 7 | 8 | ```sh 9 | $ npm install --save node-weixin-jssdk 10 | ``` 11 | 12 | 13 | ## Usage 14 | 15 | 0.2.3之前的版本使用请见[这里](https://github.com/node-weixin/node-weixin-jssdk/wiki/0.2.3%E5%8F%8A%E4%B9%8B%E5%89%8D%E7%9A%84%E7%89%88%E6%9C%AC%E7%9A%84%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95) 16 | 17 | jssdk已经更新,不再需要传入auth模板, 18 | 一般建议配合 [node-weixin-router](https://github.com/node-weixin/node-weixin-router)使用。 19 | 单独使用时可参考router里面的实现 20 | 代码示例如下: 21 | 22 | ```js 23 | var router = require('node-weixin-router'); 24 | var settings = require('node-weixin-settings'); 25 | 26 | module.exports = { 27 | config: function (req, res) { 28 | var url = null; 29 | var keys = ['body', 'query', 'params']; 30 | //1.获取传入的URL 31 | for (var i = 0; i < keys.length; i++) { 32 | var k = keys[i]; 33 | if (req[k] && req[k].url) { 34 | url = req[k].url; 35 | break; 36 | } 37 | } 38 | //2.获取ID 39 | var id = router.getId(req); 40 | //3.获取app,必须初始化时保存或者已经提前保存到settings里面 41 | var app = settings.get(id, 'app'); 42 | //4. 初始化jssdk匹配 43 | weixin.jssdk.prepare(app, url, function() { 44 | }); 45 | } 46 | }; 47 | ``` 48 | 49 | ## License 50 | 51 | Apache-2.0 © [calidion](calidion.github.io) 52 | 53 | 54 | [npm-image]: https://badge.fury.io/js/node-weixin-jssdk.svg 55 | [npm-url]: https://npmjs.org/package/node-weixin-jssdk 56 | [travis-image]: https://travis-ci.org/node-weixin/node-weixin-jssdk.svg?branch=master 57 | [travis-url]: https://travis-ci.org/node-weixin/node-weixin-jssdk 58 | [daviddm-image]: https://david-dm.org/node-weixin/node-weixin-jssdk.svg?theme=shields.io 59 | [daviddm-url]: https://david-dm.org/node-weixin/node-weixin-jssdk 60 | [coveralls-image]: https://coveralls.io/repos/node-weixin/node-weixin-jssdk/badge.svg 61 | [coveralls-url]: https://coveralls.io/r/node-weixin/node-weixin-jssdk 62 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | var gulp = require('gulp'); 4 | var eslint = require('gulp-eslint'); 5 | var excludeGitignore = require('gulp-exclude-gitignore'); 6 | var mocha = require('gulp-mocha'); 7 | var istanbul = require('gulp-istanbul'); 8 | var nsp = require('gulp-nsp'); 9 | var plumber = require('gulp-plumber'); 10 | var coveralls = require('gulp-coveralls'); 11 | 12 | gulp.task('static', function () { 13 | return gulp.src('**/*.js') 14 | .pipe(excludeGitignore()) 15 | .pipe(eslint()) 16 | .pipe(eslint.format()) 17 | .pipe(eslint.failAfterError()); 18 | }); 19 | 20 | gulp.task('nsp', function (cb) { 21 | nsp({package: path.resolve('package.json')}, cb); 22 | }); 23 | 24 | gulp.task('pre-test', function () { 25 | return gulp.src('lib/**/*.js') 26 | .pipe(excludeGitignore()) 27 | .pipe(istanbul({ 28 | includeUntested: true 29 | })) 30 | .pipe(istanbul.hookRequire()); 31 | }); 32 | 33 | gulp.task('test', ['pre-test'], function (cb) { 34 | var mochaErr; 35 | 36 | gulp.src('test/**/*.js') 37 | .pipe(plumber()) 38 | .pipe(mocha({reporter: 'spec', timeout: 10000})) 39 | .on('error', function (err) { 40 | mochaErr = err; 41 | throw err; 42 | }) 43 | .pipe(istanbul.writeReports()) 44 | .on('end', function () { 45 | cb(mochaErr); 46 | }); 47 | }); 48 | 49 | gulp.task('watch', function () { 50 | gulp.watch(['lib/**/*.js', 'test/**'], ['test']); 51 | }); 52 | 53 | gulp.task('coveralls', ['test'], function () { 54 | if (!process.env.CI) { 55 | return; 56 | } 57 | 58 | gulp.src(path.join(__dirname, 'coverage/lcov.info')) 59 | .pipe(coveralls()); 60 | }); 61 | 62 | gulp.task('prepublish', ['nsp']); 63 | gulp.task('default', ['static', 'test', 'coveralls']); 64 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var restful = require('node-weixin-request'); 3 | var util = require('node-weixin-util'); 4 | var auth = require('node-weixin-auth'); 5 | var crypto = require('crypto'); 6 | var baseUrl = 'https://api.weixin.qq.com/cgi-bin/ticket/'; 7 | 8 | module.exports = { 9 | // Last time got a token 10 | TICKET_EXP: 7200 * 1000, 11 | // for real use 12 | /** 13 | * Prepare a config for jssdk to be enabled in weixin browser 14 | * @param auth 15 | * @param app 16 | * @param url 17 | * @param cb 18 | */ 19 | prepare: function (settings, app, url, cb) { 20 | var self = this; 21 | auth.determine(settings, app, function () { 22 | self.signify(settings, app, url, function (error, json) { 23 | if (!error && !json.errcode) { 24 | cb(false, { 25 | appId: app.id, 26 | signature: json.signature, 27 | nonceStr: json.noncestr, 28 | timestamp: json.timestamp 29 | }); 30 | } else { 31 | cb(true, error); 32 | } 33 | }); 34 | }); 35 | }, 36 | /** 37 | * Get config 38 | * 39 | * @param auth 40 | * @param app 41 | * @param url 42 | * @param cb 43 | */ 44 | signify: function (settings, app, url, cb) { 45 | var self = this; 46 | this.getTicket(settings, app, function (error, ticket) { 47 | if (error) { 48 | cb(true); 49 | } else { 50 | var config = self.generate(ticket, url); 51 | var signature = self.sign(config); 52 | config.signature = signature; 53 | cb(false, config); 54 | } 55 | }); 56 | }, 57 | getTicket: function (settings, app, cb) { 58 | var self = this; 59 | 60 | settings.get(app.id, 'jssdk', function (jssdk) { 61 | if (!jssdk) { 62 | jssdk = {}; 63 | } 64 | jssdk.passed = false; 65 | var now = new Date().getTime(); 66 | if (jssdk.lastTime && (now - jssdk.lastTime < self.TICKET_EXP)) { 67 | jssdk.passed = true; 68 | cb(false, jssdk.ticket); 69 | return; 70 | } 71 | jssdk.lastTime = now; 72 | settings.get(app.id, 'auth', function (authData) { 73 | var params = { 74 | type: 'jsapi', 75 | /* eslint camelcase: [2, {properties: "never"}] */ 76 | access_token: authData.accessToken 77 | }; 78 | settings.set(app.id, 'jssdk', jssdk, function () { 79 | var url = baseUrl + 'getticket?' + util.toParam(params); 80 | var callback = function (error, json) { 81 | if (json.errcode === 0) { 82 | jssdk.ticket = json.ticket; 83 | settings.set(app.id, 'jssdk', jssdk, function () { 84 | cb(false, json.ticket); 85 | }); 86 | } else { 87 | console.error(json); 88 | cb(true); 89 | } 90 | }; 91 | restful.request(url, null, callback); 92 | }); 93 | }); 94 | }); 95 | }, 96 | sign: function (config, type) { 97 | var str = util.marshall(config); 98 | var sha1 = crypto.createHash(type || 'sha1'); 99 | sha1.update(str); 100 | return sha1.digest('hex'); 101 | }, 102 | generate: function (ticket, url) { 103 | var timestamp = String((new Date().getTime() / 1000).toFixed(0)); 104 | var sha1 = crypto.createHash('sha1'); 105 | sha1.update(timestamp); 106 | var noncestr = sha1.digest('hex'); 107 | return { 108 | jsapi_ticket: ticket, 109 | noncestr: noncestr, 110 | timestamp: timestamp, 111 | url: url 112 | }; 113 | } 114 | }; 115 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-weixin-jssdk", 3 | "version": "0.5.0", 4 | "description": "", 5 | "homepage": "", 6 | "author": { 7 | "name": "calidion", 8 | "email": "calidion@gmail.com", 9 | "url": "calidion.github.io" 10 | }, 11 | "files": [ 12 | "lib" 13 | ], 14 | "main": "lib/index.js", 15 | "keywords": [ 16 | "" 17 | ], 18 | "devDependencies": { 19 | "eslint-config-xo-space": "^0.7.0", 20 | "gulp": "^3.9.0", 21 | "gulp-coveralls": "^0.1.0", 22 | "gulp-eslint": "^1.0.0", 23 | "gulp-exclude-gitignore": "^1.0.0", 24 | "gulp-istanbul": "^0.10.3", 25 | "gulp-mocha": "^2.0.0", 26 | "gulp-nsp": "^2.1.0", 27 | "gulp-plumber": "^1.0.0", 28 | "node-weixin-config": "^0.3.0", 29 | "node-weixin-settings": "^0.2.0" 30 | }, 31 | "eslintConfig": { 32 | "extends": "xo-space", 33 | "env": { 34 | "mocha": true 35 | } 36 | }, 37 | "repository": "node-weixin/node-weixin-jssdk", 38 | "scripts": { 39 | "prepublish": "gulp prepublish", 40 | "test": "gulp" 41 | }, 42 | "license": "Apache-2.0", 43 | "dependencies": { 44 | "node-weixin-auth": "^0.6.3", 45 | "node-weixin-request": "^0.4.0", 46 | "node-weixin-util": "^0.3.1" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var assert = require('assert'); 3 | var nodeWeixinJssdk = require('../lib/index'); 4 | var config = require('node-weixin-config'); 5 | var settings = require('node-weixin-settings'); 6 | 7 | var app = { 8 | id: process.env.APP_ID, 9 | secret: process.env.APP_SECRET, 10 | token: process.env.APP_TOKEN 11 | }; 12 | 13 | config.app.init(app); 14 | 15 | var url = 'http://wx.t1bao.com/pay'; 16 | 17 | describe('node-weixin-jssdk node module', function () { 18 | it('should be able to get jsapi_ticket', function (done) { 19 | nodeWeixinJssdk.prepare(settings, app, url, function (error, data) { 20 | settings.get(app.id, 'jssdk', function (jssdk) { 21 | assert.equal(true, !error); 22 | assert.equal(true, jssdk.passed === false); 23 | assert.equal(true, data.appId === app.id); 24 | assert.equal(true, data.signature.length > 1); 25 | assert.equal(true, data.nonceStr.length > 1); 26 | assert.equal(true, data.timestamp.length > 1); 27 | done(); 28 | }); 29 | }); 30 | }); 31 | 32 | it('should be able to get jsapi_ticket', function (done) { 33 | nodeWeixinJssdk.prepare(settings, app, url, function (error, data) { 34 | settings.get(app.id, 'jssdk', function (jssdk) { 35 | assert.equal(true, !error); 36 | assert.equal(true, jssdk.passed === true); 37 | assert.equal(true, data.appId === app.id); 38 | assert.equal(true, data.signature.length > 1); 39 | assert.equal(true, data.nonceStr.length > 1); 40 | assert.equal(true, data.timestamp.length > 1); 41 | done(); 42 | }); 43 | }); 44 | }); 45 | 46 | it('should be fail to get jsapi_ticket', function (done) { 47 | nodeWeixinJssdk.prepare(settings, {}, url, function (error) { 48 | assert.equal(true, error); 49 | done(); 50 | }); 51 | }); 52 | }); 53 | --------------------------------------------------------------------------------