commit 9420feb4c9d4ac02b0f5a94d9611281117b0ca3c Author: 编码猿 Date: Fri Sep 27 01:09:14 2024 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b7339a3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# Windows +[Dd]esktop.ini +Thumbs.db +$RECYCLE.BIN/ + +NodeServer/node_modules/ + +# macOS +.DS_Store +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.idea + +# Node.js +node_modules/ diff --git a/.pydio b/.pydio new file mode 100644 index 0000000..9c94843 --- /dev/null +++ b/.pydio @@ -0,0 +1 @@ +54e8a78c-451c-4ffb-bdb1-0c8b8ba5eb75 \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/NodeServer/.pydio b/NodeServer/.pydio new file mode 100644 index 0000000..f0c247c --- /dev/null +++ b/NodeServer/.pydio @@ -0,0 +1 @@ +51645263-0a19-469f-811e-4b9ab5070e69 \ No newline at end of file diff --git a/NodeServer/BroadcastChannel/.pydio b/NodeServer/BroadcastChannel/.pydio new file mode 100644 index 0000000..9eb59ae --- /dev/null +++ b/NodeServer/BroadcastChannel/.pydio @@ -0,0 +1 @@ +adc12d86-cd98-4497-aa04-dd7b13c37945 \ No newline at end of file diff --git a/NodeServer/BroadcastChannel/index.js b/NodeServer/BroadcastChannel/index.js new file mode 100644 index 0000000..839d1d9 --- /dev/null +++ b/NodeServer/BroadcastChannel/index.js @@ -0,0 +1,7 @@ +const { BroadcastChannel } = require('broadcast-channel'); + +module.exports = { + SubscribeExpired: new BroadcastChannel('SubscribeExpired'), + SweepStatus: new BroadcastChannel('SweepStatus'), + AuthorizedWeb: new BroadcastChannel('AuthorizedWeb') +}; \ No newline at end of file diff --git a/NodeServer/app.js b/NodeServer/app.js new file mode 100644 index 0000000..420fb15 --- /dev/null +++ b/NodeServer/app.js @@ -0,0 +1,33 @@ +var createError = require('http-errors'); +var express = require('express'); +var path = require('path'); +var cookieParser = require('cookie-parser'); +var logger = require('morgan'); + +var indexRouter = require('./routes/index'); + +var app = express(); + +app.use(logger('dev')); +app.use(express.json()); +app.use(express.urlencoded({ extended: false })); +app.use(cookieParser()); + +app.use('/', indexRouter); + +// catch 404 and forward to error handler +app.use(function(req, res, next) { + next(createError(404)); +}); + +// error handler +app.use(function(err, req, res, next) { + // set locals, only providing error in development + res.locals.message = err.message; + res.locals.error = req.app.get('env') === 'development' ? err : {}; + + // render the error page + res.status(err.status || 500); +}); + +module.exports = app; diff --git a/NodeServer/bin/.pydio b/NodeServer/bin/.pydio new file mode 100644 index 0000000..4ce7dc5 --- /dev/null +++ b/NodeServer/bin/.pydio @@ -0,0 +1 @@ +b6ceb8d6-c456-497e-91f8-db953297899f \ No newline at end of file diff --git a/NodeServer/bin/www b/NodeServer/bin/www new file mode 100755 index 0000000..26a4f49 --- /dev/null +++ b/NodeServer/bin/www @@ -0,0 +1,87 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var app = require('../app'); +var debug = require('debug')(`nodeserver: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); + +/** + * 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(); + console.log(`App Is Run: http://127.0.0.1:${addr.port}`) +} diff --git a/NodeServer/mysql/.pydio b/NodeServer/mysql/.pydio new file mode 100644 index 0000000..33a6f43 --- /dev/null +++ b/NodeServer/mysql/.pydio @@ -0,0 +1 @@ +254af4fe-8f9b-4edd-b6cf-893f0cc8185a \ No newline at end of file diff --git a/NodeServer/mysql/index.js b/NodeServer/mysql/index.js new file mode 100644 index 0000000..38b4238 --- /dev/null +++ b/NodeServer/mysql/index.js @@ -0,0 +1,21 @@ +const mysql = require("yn-mysql-utils"); + +class MSql { + + constructor() { + this.db = new mysql({ + host : '127.0.0.1', + user : 'root', + password : 'lb714500', + database : 'ScanCodeLogin', + connectionLimit : 10 + }); + } + + async query (params) { + return await this.db.Query({ sql: params.sql, par: params.data }) + } + +} + +module.exports = new MSql(); \ No newline at end of file diff --git a/NodeServer/package.json b/NodeServer/package.json new file mode 100644 index 0000000..f52d17c --- /dev/null +++ b/NodeServer/package.json @@ -0,0 +1,22 @@ +{ + "name": "nodeserver", + "version": "0.0.0", + "private": true, + "scripts": { + "dev": "node-dev ./bin/www", + "socket": "node-dev ./routes/socket.js" + }, + "dependencies": { + "broadcast-channel": "^3.1.0", + "cookie-parser": "~1.4.4", + "debug": "~2.6.9", + "express": "~4.16.1", + "http-errors": "~1.6.3", + "morgan": "~1.9.1", + "qr-image": "^3.2.0", + "redis": "^3.0.2", + "socket.io": "^2.3.0", + "string-random": "^0.1.3", + "yn-mysql-utils": "^1.0.4" + } +} diff --git a/NodeServer/redis/.pydio b/NodeServer/redis/.pydio new file mode 100644 index 0000000..4d29b5e --- /dev/null +++ b/NodeServer/redis/.pydio @@ -0,0 +1 @@ +6fc9f101-e55b-4932-8cfc-af46363fd9da \ No newline at end of file diff --git a/NodeServer/redis/index.js b/NodeServer/redis/index.js new file mode 100644 index 0000000..7b35d39 --- /dev/null +++ b/NodeServer/redis/index.js @@ -0,0 +1,45 @@ +const redis = require("redis"); +const BroadcastChannel = require("../BroadcastChannel"); + +class Redis { + constructor() { + this.client = redis.createClient({ + host:'127.0.0.1', + port:'6379', + ttl: 5*60*1000 + }); + + this.client.on("error", function(error) { + console.error(error); + }); + + this.client.send_command('config', ['set', 'notify-keyspace-events', 'Ex'], this.SubscribeExpired); + } + + async get(key) { + return new Promise((resolve)=>{ + this.client.get(key, (err,res) => resolve(res)); + }) + } + + set(key, value="") { + this.client.set(key, value); + // 设置key的过期时间60秒(其实也就是二维码的有效时间) + this.client.expire(key, 60) + } + + SubscribeExpired(e, r) { + let sub = redis.createClient(6379); + const expired_subKey = '__keyevent@0__:expired'; + sub.subscribe(expired_subKey, function () { + sub.on('message', function (chan, msg) { + console.log(`二维码:${msg} 已超时过期... `); + // 二维码失效,通过 BroadcastChannel 通知 routes/socket.js + BroadcastChannel.SubscribeExpired.postMessage(msg); + }); + }) + } +} + +module.exports = new Redis(); + diff --git a/NodeServer/routes/.pydio b/NodeServer/routes/.pydio new file mode 100644 index 0000000..2b24f7e --- /dev/null +++ b/NodeServer/routes/.pydio @@ -0,0 +1 @@ +3fa0f904-8283-42e5-bf5b-a1b1c84cadc7 \ No newline at end of file diff --git a/NodeServer/routes/index.js b/NodeServer/routes/index.js new file mode 100644 index 0000000..92ce2b7 --- /dev/null +++ b/NodeServer/routes/index.js @@ -0,0 +1,129 @@ +const express = require('express'); +const router = express.Router(); +const sql = require("../mysql"); +const redis = require("../redis"); +const BroadcastChannel = require("../BroadcastChannel"); + +/** + * 用户已扫码但是未确认,这个接口只是为了通知web端,用户当前的进展状态 + */ +router.get('/SweepStatus', async (req, res, next) => { + let uuid = req.query.uuid; + BroadcastChannel.SweepStatus.postMessage(uuid); + res.json({ status: 200, msg: '发送成功', data: null }) +}); + +/** + * 移动端App确认登录按钮点击时候的ajax处理 + */ +router.get('/login', async (req, res, next) => { + let uuid = req.query.uuid; // uuid + let uid = req.query.uid; // 用户id + + let RedisUid = await redis.get(uuid); + if (RedisUid == null || RedisUid == undefined) { + res.json({ + status: 404, + msg: '二维码失效或不存在', + data: null + }); + } else { + if (RedisUid.length !== 0) { + res.json({ + status: 404, + msg: '已登录,请勿重复扫码', + data: null + }); + } else { + // 查询用户信息 + let user = (await sql.query({ sql: 'select * from user where id = ?', data: [ uid ] }))[0]; + // 保存用户数据到对应key的redis中 + redis.set(uuid, JSON.stringify(user)); + res.json({ + status: 200, + msg: '扫码成功', + data: null + }); + } + } + +}); + +/** + * TODO + * 被移动端App轮询的接口,主要让移动端知道,我移动端现在已登录的用户所对 + * 应的web端是否在请求让我授权登录,如果在web端在请求登录,那么移动端自 + * 动进入授权页面让用户授权,授权后结束轮询,否则一直轮询 + * PS: uniapp 无法连接到 Node.js 的 socket.io,所以只能用很low而且 + * 很消耗移动端资源的轮询了。等解决这个问题自然用socket重写这里实现 + */ +router.get('/InquiryWebIsLogining', async (req, res, next) => { + let uid = req.query.uid; // 用户id + // console.log("uid: ", uid); + let UserId = await redis.get(uid); + // 没有查询到redis的 uid 数据的话 + if (UserId == null || UserId == undefined) { + res.json({ + status: 404, + msg: '没有对应的web端请求登录', + data: null + }); + } else { + // UserId 有数值,但是为空,表示web请求登录,但移动端未授权 + if (UserId.length == 0) { + res.json({ + status: 200, + msg: 'web端正在请求授权', + data: null + }); + // UserId 有数值,但是不为空,表示web请求登录,移动端已授权,并且redis未超时 + } else { + res.json({ + status: 500, + msg: '授权成功,请勿重复授权', + data: null + }) + } + } + +}); + +/** + * 授权web端登录的接口,主要被移动端App调用拿到移动端给的用户id, + * 然后从数据库获取用户的详细数据,通过广播把用户数据通知给web端 + */ +router.get('/AuthorizedWeb', async (req, res, next) => { + let uid = req.query.uid; // 用户id + let UserId = await redis.get(uid); + + if (UserId == null || UserId == undefined) { + res.json({ + status: 404, + msg: '没有对应的web端请求登录', + data: null + }); + } else { + // UserId 有数值,但是为空 + if (UserId.length == 0) { + // 查询用户信息 + let user = (await sql.query({ sql: 'select * from user where id = ?', data: [ uid ] }))[0]; + // 保存用户数据到对应key的redis中 + redis.set(uid, JSON.stringify(user)); + BroadcastChannel.AuthorizedWeb.postMessage(user); + res.json({ + status: 200, + msg: '成功授权', + data: null + }); + + // UserId 有数值,但是不为空,表示web请求登录,移动端已授权,并且redis未超时 + } else { + res.json({ + status: 404, + msg: '授权成功,请勿重复授权', + data: null + }) + } + } +}); +module.exports = router; diff --git a/NodeServer/routes/socket.js b/NodeServer/routes/socket.js new file mode 100644 index 0000000..1d77fab --- /dev/null +++ b/NodeServer/routes/socket.js @@ -0,0 +1,68 @@ +const app = require('express')(); +const http = require('http').createServer(app); +const io = require('socket.io')(http); +const utils = require('../utils'); +const redis = require('../redis'); +const BroadcastChannel = require("../BroadcastChannel"); + +io.on('connection', (socket) => { + + // Web端获取二维码和uuid + socket.on('getQRcode', () => { + let uuid = utils.randomNumber(); // 生成uuid + let QRcode = utils.getQRcode(uuid); // 根据uuid生成二维码的base64 + redis.set(uuid, ''); // 保存uuid 到redis中,但是用户数据留空,由移动端上传 + socket.emit('sendQRcode', { uuid: uuid, QRcode: QRcode }); // 向web端发送uuid和二维码base64数据 + }); + + /** + * 检查移动端用户是否扫码二维码登录,如果扫码登录了 + * 那么二维码显示登录成功样式,并显示登录用户信息 + */ + socket.on('checkScanCode', async key => { + console.log(`Web端(Vue)在询问用户是否扫码:${key}...`); + + let userInfo = await redis.get(key); + // 如果key对应的value为空,那么用户未扫码 + // 反之 用户扫码并确定登录 + userInfo == null || userInfo.length == 0 ? socket.emit("waitScanCode") : socket.emit("SuccessScanCode", userInfo); + + }); + + /** + * 场景:用户已经移动端App扫码登录后,本地保存有用户的id + * 功能:模仿微信web端发送请求,然后移动端授权web端的本次登录 + * web端请求移动端授权登录 + */ + socket.on('WebRequestLogin', async id => { + console.log("web端请求登录用户的id: ", id); + let WebId = await redis.get(id); + + // 如果redis中没有数值,表示web端还未开始请求移动端授权登录,那么保存id为redis key + // 如果redis中有数值,表示web端用户已经在请求移动端授权登录,那么不重复保存id,并且提示web端 + if (WebId == null) { + redis.set(id, ''); // 使用web端的用户id作为redis的key存储起来,value对应的数值由移动端填充 + } else { + console.log("不等于null") + socket.emit("DoNotRepeatRequest", '已发送登录请求,请在移动端授权本次登录') + } + }); + + // 接受redis key超时的广播通知,也就是用户长时间未扫二维码主要在redis/index.js中进行订阅key失效的事件 + // 然后广播通知被这里代码onmessage到,然后 socket 通知 Vue 前端 + BroadcastChannel.SubscribeExpired.onmessage = event => socket.emit("ScanCodeOvertime"); + + // 用户已扫码,但是未确认 + BroadcastChannel.SweepStatus.onmessage = event => socket.emit("ScanCodeing", event.data); + + // 移动端成功授权web端 + BroadcastChannel.AuthorizedWeb.onmessage = function(event) { + console.log("event: ", event); + socket.emit("AuthorizedWebSuccess", event); + }; + + socket.on('disconnect', () => console.log('web端用户掉线...')); + +}); + +http.listen(9080, () => console.log('socket listening on *: 9080')); \ No newline at end of file diff --git a/NodeServer/utils/.pydio b/NodeServer/utils/.pydio new file mode 100644 index 0000000..0e19deb --- /dev/null +++ b/NodeServer/utils/.pydio @@ -0,0 +1 @@ +c5a72c45-7bb0-4bec-8631-53fb9b81e78f \ No newline at end of file diff --git a/NodeServer/utils/index.js b/NodeServer/utils/index.js new file mode 100644 index 0000000..ee6eb3b --- /dev/null +++ b/NodeServer/utils/index.js @@ -0,0 +1,17 @@ +var random = require('string-random'); +var qr = require('qr-image'); + +class Utils { + + // 产随机字符串 + randomNumber() { + return 'login/' + random(16, { numbers: true }) + } + + // 根据 randomNumber 的随机随机字符串生成base64二维码 + getQRcode(str) { + return `data:image/png;base64,${qr.imageSync(str,{ type:'png' }).toString("base64")}` + } +} + +module.exports = new Utils(); \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ad99d91 --- /dev/null +++ b/README.md @@ -0,0 +1,116 @@ +## 1:仿微信,QQ,微博扫码登录 + +一直都感觉扫码登录很神奇啊!!然后谷歌一搜思路一看,发现还真是挺简单的,所以技痒自己写个玩玩,代码里面的实现方式和思路可以借鉴。不要问为什么不用`PHP`,`Java`,我不会告诉你`Node.js`写起来更简单更方便,至于用什么后端语言实现这个不是关键,看你个人爱好。 + +**哦,对了。这里实现的是扫码登录的一整套代码,看懂了拿过去改改封装一下就可以在项目中使用了。这其中包含服务端(NodeServer),App端(UniAppMobile),Web端(VueQRCode),可以直接部署跑起来哦。 +不过你要先准备好以下环境并且掌握不少的前后端技术点才能愉快玩耍哦:** + +- Nodejs +- Mysql +- Redis +- Uni-app +- Socket +- Express +- Vue.js + +来自大佬 [尤雨溪](https://baike.baidu.com/item/%E5%B0%A4%E9%9B%A8%E6%BA%AA/2281470?fr=aladdin) 的鼓励: + +![haha](./Resources/haha.jpeg) + +无图无真相,下面请接招: + +**效果图:** + +1.1 Web端未扫码: +![web-start](./Resources/web-start.png) + +1.2 App端已扫码,但未在App端确认登录: +![web-start](./Resources/web-ing.png) + +1.3 Web端二维码失效: +![web-start](./Resources/web-end.png) + +1.4 Web端扫码成功: +![web-start](./Resources/web-success.png) + +1.5 App端首页 +![web-start](./Resources/app-index.png) + +1.6 App端扫码页面 +![app-scan](./Resources/app-scan.jpg) + +1.7 App端确认登录 +![web-start](./Resources/app-login.png) + + +## 2:实现的功能 + +额,类似你用过的`QQ`,`微信`,`微博`扫码登录的那些功能,简单说下: + +- [x] web端从服务器获取二维码和uuid +- [x] web端处理二维码失效,重复扫码等 +- [x] redis控制key的超时时间,前端二维码超时样式控制 +- [x] App端扫码成功后,web端的样式控制和用户信息显示 +- [x] App端首页扫码后进入login页面确认登录 +- [x] 二维码失效后,可以点击重新获取 +- [x] App端首页扫码后通知 --> 服务器--> web端 展示最新扫码进度 +- [x] 已通过扫码登录后,可以使用【移动端授权登录】来唤醒App对web端登录进行授权 + +## 3 实现思路 + +![img](./Resources/silu.jpg) + +## 4:项目结构 + +下面先看下项目的基本结构,后面再说实现思路。 + +### 4.1 NodeServer - 服务端 + +主要用`Node.js`实现后端,通过`socket.io`+`redis`实现`web`端的二维码生成,`uuid`等,同时还实现了`android`端的扫码登录的接口等。具体看下面目录结构 + +```text +├── BroadcastChannel # 广播频道,主要为了让在socket.io中能及时知道 redis 订阅key的失效事件 +│   └── index.js +├── app.js # express 框架的启动入口文件 +├── bin # express 框架的启动脚本文件 +│   └── www +├── mysql # mysql的封装,主要使用了我自己开发的npm插件"yn-mysql-utils" +│   └── index.js +├── node_modules # 依赖包,没什么好说的 +├── package.json # 项目依赖配置文件,没什么好说的 +├── redis # redis 的简单封装 +│   └── index.js +├── routes # 路由接口 +│   ├── index.js # 这里面实现的接口主要给APP端使用 +│   └── socket.js # 实现socket.io,主要给Vue的web端使用 +└── utils # 工具类的封装 + ├── index.js +``` +### 4.2 UniAppMobile - Android App端 + +使用`uni-app`开发的`android`端,主要为了实现扫码然后把`App`上已登录的用户信息GET传到`redis`中。没什么,很简单。主要逻辑在: + +- pages/index/index.vue +- pages/login/login.vue + +如果`uni-app`不会,请自行看官方文档学习: + +> [uni-app](https://uniapp.dcloud.io/) + +### 4.3 VueQRCode - Vue web端 + +主要使用`Vue`写的`Web`,代码里面注释很多,看看就明白了。主要的代码逻辑都在这个`web`端中。 + +## 5 关于我 + +一个热爱编程喜欢折腾的人,一个曾经的后端但如今迷失在前端技术中的人,一个喜欢各种技术的人,更多开源项目请看: + +- [HtmlToPug](https://gitee.com/bmycode/HtmlToPug) + +- [Whtml](https://www.npmjs.com/package/whtml) + +- [Vue3Template](https://github.com/helpcode/Vue3Template) + +- [vue3decorators](https://www.npmjs.com/package/vue3decorators) + +- [vue-cli-plugin-autorouter](https://www.npmjs.com/package/vue-cli-plugin-autorouter) \ No newline at end of file diff --git a/Resources/.pydio b/Resources/.pydio new file mode 100644 index 0000000..32bcc3a --- /dev/null +++ b/Resources/.pydio @@ -0,0 +1 @@ +70d76944-3cda-416c-9483-a49d892a61ae \ No newline at end of file diff --git a/Resources/app-index.png b/Resources/app-index.png new file mode 100644 index 0000000..2628c86 Binary files /dev/null and b/Resources/app-index.png differ diff --git a/Resources/app-login.png b/Resources/app-login.png new file mode 100644 index 0000000..b963685 Binary files /dev/null and b/Resources/app-login.png differ diff --git a/Resources/app-scan.jpg b/Resources/app-scan.jpg new file mode 100644 index 0000000..8061541 Binary files /dev/null and b/Resources/app-scan.jpg differ diff --git a/Resources/haha.jpeg b/Resources/haha.jpeg new file mode 100644 index 0000000..ba99e84 Binary files /dev/null and b/Resources/haha.jpeg differ diff --git a/Resources/silu.jpg b/Resources/silu.jpg new file mode 100644 index 0000000..e91cb7c Binary files /dev/null and b/Resources/silu.jpg differ diff --git a/Resources/web-end.png b/Resources/web-end.png new file mode 100644 index 0000000..c8edc96 Binary files /dev/null and b/Resources/web-end.png differ diff --git a/Resources/web-ing.png b/Resources/web-ing.png new file mode 100644 index 0000000..f94b77d Binary files /dev/null and b/Resources/web-ing.png differ diff --git a/Resources/web-start.png b/Resources/web-start.png new file mode 100644 index 0000000..a9dcb06 Binary files /dev/null and b/Resources/web-start.png differ diff --git a/Resources/web-success.png b/Resources/web-success.png new file mode 100644 index 0000000..fa2de54 Binary files /dev/null and b/Resources/web-success.png differ diff --git a/UniAppMobile/.pydio b/UniAppMobile/.pydio new file mode 100644 index 0000000..1f721f6 --- /dev/null +++ b/UniAppMobile/.pydio @@ -0,0 +1 @@ +a8d049cb-d9e2-4fd3-9416-1241476da7ec \ No newline at end of file diff --git a/UniAppMobile/App.vue b/UniAppMobile/App.vue new file mode 100644 index 0000000..bbfa09a --- /dev/null +++ b/UniAppMobile/App.vue @@ -0,0 +1,19 @@ + + + diff --git a/UniAppMobile/config/.pydio b/UniAppMobile/config/.pydio new file mode 100644 index 0000000..9f149b7 --- /dev/null +++ b/UniAppMobile/config/.pydio @@ -0,0 +1 @@ +acfc842c-161f-4645-9466-9c72847944a6 \ No newline at end of file diff --git a/UniAppMobile/config/index.js b/UniAppMobile/config/index.js new file mode 100644 index 0000000..d23f976 --- /dev/null +++ b/UniAppMobile/config/index.js @@ -0,0 +1,3 @@ +export default { + baseUrl: 'http://192.168.0.133:3000' +} \ No newline at end of file diff --git a/UniAppMobile/main.js b/UniAppMobile/main.js new file mode 100644 index 0000000..8d98985 --- /dev/null +++ b/UniAppMobile/main.js @@ -0,0 +1,13 @@ +import Vue from 'vue' +import App from './App' +import config from './config/index.js' + +Vue.config.productionTip = false + +App.mpType = 'app' +Vue.prototype.$config = config + +const app = new Vue({ + ...App +}) +app.$mount() diff --git a/UniAppMobile/manifest.json b/UniAppMobile/manifest.json new file mode 100644 index 0000000..0254885 --- /dev/null +++ b/UniAppMobile/manifest.json @@ -0,0 +1,75 @@ +{ + "name" : "rr", + "appid" : "__UNI__D277C34", + "description" : "", + "versionName" : "1.0.0", + "versionCode" : "100", + "transformPx" : false, + /* 5+App特有相关 */ + "app-plus" : { + "usingComponents" : true, + "nvueCompiler" : "uni-app", + "compilerVersion" : 3, + "splashscreen" : { + "alwaysShowBeforeRender" : true, + "waiting" : true, + "autoclose" : true, + "delay" : 0 + }, + /* 模块配置 */ + "modules" : {}, + /* 应用发布信息 */ + "distribute" : { + /* android打包配置 */ + "android" : { + "permissions" : [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + /* ios打包配置 */ + "ios" : {}, + /* SDK配置 */ + "sdkConfigs" : {} + } + }, + /* 快应用特有相关 */ + "quickapp" : {}, + /* 小程序特有相关 */ + "mp-weixin" : { + "appid" : "", + "setting" : { + "urlCheck" : false + }, + "usingComponents" : true + }, + "mp-alipay" : { + "usingComponents" : true + }, + "mp-baidu" : { + "usingComponents" : true + }, + "mp-toutiao" : { + "usingComponents" : true + } +} diff --git a/UniAppMobile/package-lock.json b/UniAppMobile/package-lock.json new file mode 100644 index 0000000..1013a88 --- /dev/null +++ b/UniAppMobile/package-lock.json @@ -0,0 +1,294 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "after": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" + }, + "arraybuffer.slice": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", + "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" + }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" + }, + "base64-arraybuffer": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" + }, + "better-assert": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "requires": { + "callsite": "1.0.0" + } + }, + "blob": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", + "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==" + }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" + }, + "component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "component-inherit": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "engine.io-client": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.4.0.tgz", + "integrity": "sha512-a4J5QO2k99CM2a0b12IznnyQndoEvtA4UAldhGzKqnHf42I3Qs2W5SPnDvatZRcMaNZs4IevVicBPayxYt6FwA==", + "requires": { + "component-emitter": "1.2.1", + "component-inherit": "0.0.3", + "debug": "~4.1.0", + "engine.io-parser": "~2.2.0", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "ws": "~6.1.0", + "xmlhttprequest-ssl": "~1.5.4", + "yeast": "0.1.2" + } + }, + "engine.io-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.0.tgz", + "integrity": "sha512-6I3qD9iUxotsC5HEMuuGsKA0cXerGz+4uGcXQEkfBidgKf0amsjrrtwcbwK/nzpZBxclXlV7gGl9dgWvu4LF6w==", + "requires": { + "after": "0.8.2", + "arraybuffer.slice": "~0.0.7", + "base64-arraybuffer": "0.1.5", + "blob": "0.0.5", + "has-binary2": "~1.0.2" + } + }, + "has-binary2": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", + "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "requires": { + "isarray": "2.0.1" + } + }, + "has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" + }, + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "object-component": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=" + }, + "parseqs": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseuri": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "requires": { + "better-assert": "~1.0.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "socket.io-client": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.3.0.tgz", + "integrity": "sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==", + "requires": { + "backo2": "1.0.2", + "base64-arraybuffer": "0.1.5", + "component-bind": "1.0.0", + "component-emitter": "1.2.1", + "debug": "~4.1.0", + "engine.io-client": "~3.4.0", + "has-binary2": "~1.0.2", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "object-component": "0.0.3", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "socket.io-parser": "~3.3.0", + "to-array": "0.1.4" + } + }, + "socket.io-parser": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.0.tgz", + "integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==", + "requires": { + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "isarray": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "to-array": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" + }, + "ultron": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" + }, + "weapp.engine.io-client": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/weapp.engine.io-client/-/weapp.engine.io-client-3.2.3.tgz", + "integrity": "sha512-PDqfZG4o4m7uvDAEUg7DLV0OZrj2mWZglq5W3I7K7tFFSfyvhrFa2yVRQIE6HfIZgq+Z1YhAO1wLg1mKT3JH9g==", + "requires": { + "component-emitter": "1.2.1", + "component-inherit": "0.0.3", + "debug": "~3.1.0", + "engine.io-parser": "~2.1.1", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "ws": "~3.3.1", + "xmlhttprequest-ssl": "~1.5.4", + "yeast": "0.1.2" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "engine.io-parser": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz", + "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==", + "requires": { + "after": "0.8.2", + "arraybuffer.slice": "~0.0.7", + "base64-arraybuffer": "0.1.5", + "blob": "0.0.5", + "has-binary2": "~1.0.2" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } + } + } + }, + "weapp.socket.io": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/weapp.socket.io/-/weapp.socket.io-2.0.1.tgz", + "integrity": "sha512-NzWCOa3zzMo70RuV61Mm4ic68r7jV4xLplx9cnTVJ/K9ecMjuA4ANgWRlB3U1ww6kyOyYnbDC2o2umpJFM3thQ==", + "requires": { + "socket.io-client": "^2.1.1", + "weapp.engine.io-client": "^3.2.3" + } + }, + "ws": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", + "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", + "requires": { + "async-limiter": "~1.0.0" + } + }, + "xmlhttprequest-ssl": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", + "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=" + }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" + } + } +} diff --git a/UniAppMobile/package.json b/UniAppMobile/package.json new file mode 100644 index 0000000..ba971ef --- /dev/null +++ b/UniAppMobile/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + + } +} diff --git a/UniAppMobile/pages.json b/UniAppMobile/pages.json new file mode 100644 index 0000000..2072743 --- /dev/null +++ b/UniAppMobile/pages.json @@ -0,0 +1,28 @@ +{ + "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages + { + "path": "pages/index/index", + "style": { + "navigationBarTitleText": "移动端扫码登录" + } + }, + { + "path" : "pages/login/login", + "style" : { + "navigationBarTitleText": "确认登录" + } + } + ,{ + "path" : "pages/authorization/authorization", + "style" : { + "navigationBarTitleText": "移动端授权登录" + } + } + ], + "globalStyle": { + "navigationBarTextStyle": "black", + "navigationBarTitleText": "uni-app", + "navigationBarBackgroundColor": "#F8F8F8", + "backgroundColor": "#F8F8F8" + } +} diff --git a/UniAppMobile/pages/.pydio b/UniAppMobile/pages/.pydio new file mode 100644 index 0000000..d6b6bde --- /dev/null +++ b/UniAppMobile/pages/.pydio @@ -0,0 +1 @@ +3e252970-c241-4b5f-a964-2b49c8802b47 \ No newline at end of file diff --git a/UniAppMobile/pages/authorization/.pydio b/UniAppMobile/pages/authorization/.pydio new file mode 100644 index 0000000..37b75b0 --- /dev/null +++ b/UniAppMobile/pages/authorization/.pydio @@ -0,0 +1 @@ +c9b89ada-5572-47b3-9ed2-1ae864de57d5 \ No newline at end of file diff --git a/UniAppMobile/pages/authorization/authorization.vue b/UniAppMobile/pages/authorization/authorization.vue new file mode 100644 index 0000000..c23c140 --- /dev/null +++ b/UniAppMobile/pages/authorization/authorization.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/UniAppMobile/pages/index/.pydio b/UniAppMobile/pages/index/.pydio new file mode 100644 index 0000000..68cf4a1 --- /dev/null +++ b/UniAppMobile/pages/index/.pydio @@ -0,0 +1 @@ +f3d71557-f4a9-4a7c-b077-e36d352dbb4e \ No newline at end of file diff --git a/UniAppMobile/pages/index/index.vue b/UniAppMobile/pages/index/index.vue new file mode 100644 index 0000000..973b499 --- /dev/null +++ b/UniAppMobile/pages/index/index.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/UniAppMobile/pages/login/.pydio b/UniAppMobile/pages/login/.pydio new file mode 100644 index 0000000..7eb54d9 --- /dev/null +++ b/UniAppMobile/pages/login/.pydio @@ -0,0 +1 @@ +6e7a3a19-b3de-4452-9d21-1dee74cb05a3 \ No newline at end of file diff --git a/UniAppMobile/pages/login/login.vue b/UniAppMobile/pages/login/login.vue new file mode 100644 index 0000000..c84cf48 --- /dev/null +++ b/UniAppMobile/pages/login/login.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/UniAppMobile/static/.pydio b/UniAppMobile/static/.pydio new file mode 100644 index 0000000..12f0cd9 --- /dev/null +++ b/UniAppMobile/static/.pydio @@ -0,0 +1 @@ +a7ed3376-db93-4230-8c23-a6b1d3dc880e \ No newline at end of file diff --git a/UniAppMobile/static/logo.png b/UniAppMobile/static/logo.png new file mode 100644 index 0000000..b5771e2 Binary files /dev/null and b/UniAppMobile/static/logo.png differ diff --git a/UniAppMobile/uni.scss b/UniAppMobile/uni.scss new file mode 100644 index 0000000..f0f426c --- /dev/null +++ b/UniAppMobile/uni.scss @@ -0,0 +1,76 @@ +/** + * 这里是uni-app内置的常用样式变量 + * + * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量 + * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App + * + */ + +/** + * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 + * + * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件 + */ + +/* 颜色变量 */ + +/* 行为相关颜色 */ +$uni-color-primary: #007aff; +$uni-color-success: #4cd964; +$uni-color-warning: #f0ad4e; +$uni-color-error: #dd524d; + +/* 文字基本颜色 */ +$uni-text-color:#333;//基本色 +$uni-text-color-inverse:#fff;//反色 +$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息 +$uni-text-color-placeholder: #808080; +$uni-text-color-disable:#c0c0c0; + +/* 背景颜色 */ +$uni-bg-color:#ffffff; +$uni-bg-color-grey:#f8f8f8; +$uni-bg-color-hover:#f1f1f1;//点击状态颜色 +$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色 + +/* 边框颜色 */ +$uni-border-color:#c8c7cc; + +/* 尺寸变量 */ + +/* 文字尺寸 */ +$uni-font-size-sm:24upx; +$uni-font-size-base:28upx; +$uni-font-size-lg:32upx; + +/* 图片尺寸 */ +$uni-img-size-sm:40upx; +$uni-img-size-base:52upx; +$uni-img-size-lg:80upx; + +/* Border Radius */ +$uni-border-radius-sm: 4upx; +$uni-border-radius-base: 6upx; +$uni-border-radius-lg: 12upx; +$uni-border-radius-circle: 50%; + +/* 水平间距 */ +$uni-spacing-row-sm: 10px; +$uni-spacing-row-base: 20upx; +$uni-spacing-row-lg: 30upx; + +/* 垂直间距 */ +$uni-spacing-col-sm: 8upx; +$uni-spacing-col-base: 16upx; +$uni-spacing-col-lg: 24upx; + +/* 透明度 */ +$uni-opacity-disabled: 0.3; // 组件禁用态的透明度 + +/* 文章场景相关 */ +$uni-color-title: #2C405A; // 文章标题颜色 +$uni-font-size-title:40upx; +$uni-color-subtitle: #555555; // 二级标题颜色 +$uni-font-size-subtitle:36upx; +$uni-color-paragraph: #3F536E; // 文章段落颜色 +$uni-font-size-paragraph:30upx; \ No newline at end of file diff --git a/VueQRCode/.pydio b/VueQRCode/.pydio new file mode 100644 index 0000000..78a5cb2 --- /dev/null +++ b/VueQRCode/.pydio @@ -0,0 +1 @@ +5871485d-34e9-410d-b06c-e5b04078fa6d \ No newline at end of file diff --git a/VueQRCode/img/.pydio b/VueQRCode/img/.pydio new file mode 100644 index 0000000..0fb1dea --- /dev/null +++ b/VueQRCode/img/.pydio @@ -0,0 +1 @@ +16eac697-009f-45a6-b5de-63e2dddc5f59 \ No newline at end of file diff --git a/VueQRCode/img/reload.png b/VueQRCode/img/reload.png new file mode 100644 index 0000000..7b086bc Binary files /dev/null and b/VueQRCode/img/reload.png differ diff --git a/VueQRCode/img/success.png b/VueQRCode/img/success.png new file mode 100644 index 0000000..89cde8d Binary files /dev/null and b/VueQRCode/img/success.png differ diff --git a/VueQRCode/index.html b/VueQRCode/index.html new file mode 100644 index 0000000..98bf258 --- /dev/null +++ b/VueQRCode/index.html @@ -0,0 +1,278 @@ + + + + + Title + + + + + + + + +
+ + + Fork me on Gitee + + +

扫码登录 案例

+

Nodejs + Redis + Socket + Mysql + Vue + Uni-app

+
+ + +
+ +
+ success +

扫码成功

+
+ +
+

二维码失效,点击重新获取

+ reload.png +
+ + + +
+

{{ tipsText }}

+
+ +
+

请点击【登录】按钮

+

在移动端弹窗的页面中授权本次登录

+

{{WapSuccess}}

+ 登 录 +
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/VueQRCode/js/.pydio b/VueQRCode/js/.pydio new file mode 100644 index 0000000..844302a --- /dev/null +++ b/VueQRCode/js/.pydio @@ -0,0 +1 @@ +5603e8bb-6ec9-4f4b-89f5-b99e50f0be03 \ No newline at end of file diff --git a/VueQRCode/js/socket.io.js b/VueQRCode/js/socket.io.js new file mode 100644 index 0000000..270777b --- /dev/null +++ b/VueQRCode/js/socket.io.js @@ -0,0 +1,9 @@ +/*! + * Socket.IO v2.3.0 + * (c) 2014-2019 Guillermo Rauch + * Released under the MIT License. + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.io=e():t.io=e()}(this,function(){return function(t){function e(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return t[r].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var n={};return e.m=t,e.c=n,e.p="",e(0)}([function(t,e,n){function r(t,e){"object"==typeof t&&(e=t,t=void 0),e=e||{};var n,r=o(t),i=r.source,u=r.id,p=r.path,h=c[u]&&p in c[u].nsps,f=e.forceNew||e["force new connection"]||!1===e.multiplex||h;return f?(a("ignoring socket cache for %s",i),n=s(i,e)):(c[u]||(a("new io instance for %s",i),c[u]=s(i,e)),n=c[u]),r.query&&!e.query&&(e.query=r.query),n.socket(r.path,e)}var o=n(1),i=n(7),s=n(15),a=n(3)("socket.io-client");t.exports=e=r;var c=e.managers={};e.protocol=i.protocol,e.connect=r,e.Manager=n(15),e.Socket=n(39)},function(t,e,n){function r(t,e){var n=t;e=e||"undefined"!=typeof location&&location,null==t&&(t=e.protocol+"//"+e.host),"string"==typeof t&&("/"===t.charAt(0)&&(t="/"===t.charAt(1)?e.protocol+t:e.host+t),/^(https?|wss?):\/\//.test(t)||(i("protocol-less url %s",t),t="undefined"!=typeof e?e.protocol+"//"+t:"https://"+t),i("parse %s",t),n=o(t)),n.port||(/^(http|ws)$/.test(n.protocol)?n.port="80":/^(http|ws)s$/.test(n.protocol)&&(n.port="443")),n.path=n.path||"/";var r=n.host.indexOf(":")!==-1,s=r?"["+n.host+"]":n.host;return n.id=n.protocol+"://"+s+":"+n.port,n.href=n.protocol+"://"+s+(e&&e.port===n.port?"":":"+n.port),n}var o=n(2),i=n(3)("socket.io-client:url");t.exports=r},function(t,e){var n=/^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,r=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"];t.exports=function(t){var e=t,o=t.indexOf("["),i=t.indexOf("]");o!=-1&&i!=-1&&(t=t.substring(0,o)+t.substring(o,i).replace(/:/g,";")+t.substring(i,t.length));for(var s=n.exec(t||""),a={},c=14;c--;)a[r[c]]=s[c]||"";return o!=-1&&i!=-1&&(a.source=e,a.host=a.host.substring(1,a.host.length-1).replace(/;/g,":"),a.authority=a.authority.replace("[","").replace("]","").replace(/;/g,":"),a.ipv6uri=!0),a}},function(t,e,n){(function(r){"use strict";function o(){return!("undefined"==typeof window||!window.process||"renderer"!==window.process.type&&!window.process.__nwjs)||("undefined"==typeof navigator||!navigator.userAgent||!navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/))&&("undefined"!=typeof document&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||"undefined"!=typeof window&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/))}function i(e){if(e[0]=(this.useColors?"%c":"")+this.namespace+(this.useColors?" %c":" ")+e[0]+(this.useColors?"%c ":" ")+"+"+t.exports.humanize(this.diff),this.useColors){var n="color: "+this.color;e.splice(1,0,n,"color: inherit");var r=0,o=0;e[0].replace(/%[a-zA-Z%]/g,function(t){"%%"!==t&&(r++,"%c"===t&&(o=r))}),e.splice(o,0,n)}}function s(){var t;return"object"===("undefined"==typeof console?"undefined":p(console))&&console.log&&(t=console).log.apply(t,arguments)}function a(t){try{t?e.storage.setItem("debug",t):e.storage.removeItem("debug")}catch(n){}}function c(){var t=void 0;try{t=e.storage.getItem("debug")}catch(n){}return!t&&"undefined"!=typeof r&&"env"in r&&(t=r.env.DEBUG),t}function u(){try{return localStorage}catch(t){}}var p="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t};e.log=s,e.formatArgs=i,e.save=a,e.load=c,e.useColors=o,e.storage=u(),e.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"],t.exports=n(5)(e);var h=t.exports.formatters;h.j=function(t){try{return JSON.stringify(t)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}}}).call(e,n(4))},function(t,e){function n(){throw new Error("setTimeout has not been defined")}function r(){throw new Error("clearTimeout has not been defined")}function o(t){if(p===setTimeout)return setTimeout(t,0);if((p===n||!p)&&setTimeout)return p=setTimeout,setTimeout(t,0);try{return p(t,0)}catch(e){try{return p.call(null,t,0)}catch(e){return p.call(this,t,0)}}}function i(t){if(h===clearTimeout)return clearTimeout(t);if((h===r||!h)&&clearTimeout)return h=clearTimeout,clearTimeout(t);try{return h(t)}catch(e){try{return h.call(null,t)}catch(e){return h.call(this,t)}}}function s(){y&&l&&(y=!1,l.length?d=l.concat(d):m=-1,d.length&&a())}function a(){if(!y){var t=o(s);y=!0;for(var e=d.length;e;){for(l=d,d=[];++m1)for(var n=1;n100)){var e=/^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(t);if(e){var n=parseFloat(e[1]),r=(e[2]||"ms").toLowerCase();switch(r){case"years":case"year":case"yrs":case"yr":case"y":return n*h;case"weeks":case"week":case"w":return n*p;case"days":case"day":case"d":return n*u;case"hours":case"hour":case"hrs":case"hr":case"h":return n*c;case"minutes":case"minute":case"mins":case"min":case"m":return n*a;case"seconds":case"second":case"secs":case"sec":case"s":return n*s;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return n;default:return}}}}function r(t){var e=Math.abs(t);return e>=u?Math.round(t/u)+"d":e>=c?Math.round(t/c)+"h":e>=a?Math.round(t/a)+"m":e>=s?Math.round(t/s)+"s":t+"ms"}function o(t){var e=Math.abs(t);return e>=u?i(t,e,u,"day"):e>=c?i(t,e,c,"hour"):e>=a?i(t,e,a,"minute"):e>=s?i(t,e,s,"second"):t+" ms"}function i(t,e,n,r){var o=e>=1.5*n;return Math.round(t/n)+" "+r+(o?"s":"")}var s=1e3,a=60*s,c=60*a,u=24*c,p=7*u,h=365.25*u;t.exports=function(t,e){e=e||{};var i=typeof t;if("string"===i&&t.length>0)return n(t);if("number"===i&&isFinite(t))return e["long"]?o(t):r(t);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(t))}},function(t,e,n){function r(){}function o(t){var n=""+t.type;if(e.BINARY_EVENT!==t.type&&e.BINARY_ACK!==t.type||(n+=t.attachments+"-"),t.nsp&&"/"!==t.nsp&&(n+=t.nsp+","),null!=t.id&&(n+=t.id),null!=t.data){var r=i(t.data);if(r===!1)return g;n+=r}return f("encoded %j as %s",t,n),n}function i(t){try{return JSON.stringify(t)}catch(e){return!1}}function s(t,e){function n(t){var n=d.deconstructPacket(t),r=o(n.packet),i=n.buffers;i.unshift(r),e(i)}d.removeBlobs(t,n)}function a(){this.reconstructor=null}function c(t){var n=0,r={type:Number(t.charAt(0))};if(null==e.types[r.type])return h("unknown packet type "+r.type);if(e.BINARY_EVENT===r.type||e.BINARY_ACK===r.type){for(var o="";"-"!==t.charAt(++n)&&(o+=t.charAt(n),n!=t.length););if(o!=Number(o)||"-"!==t.charAt(n))throw new Error("Illegal attachments");r.attachments=Number(o)}if("/"===t.charAt(n+1))for(r.nsp="";++n;){var i=t.charAt(n);if(","===i)break;if(r.nsp+=i,n===t.length)break}else r.nsp="/";var s=t.charAt(n+1);if(""!==s&&Number(s)==s){for(r.id="";++n;){var i=t.charAt(n);if(null==i||Number(i)!=i){--n;break}if(r.id+=t.charAt(n),n===t.length)break}r.id=Number(r.id)}if(t.charAt(++n)){var a=u(t.substr(n)),c=a!==!1&&(r.type===e.ERROR||y(a));if(!c)return h("invalid payload");r.data=a}return f("decoded %s as %j",t,r),r}function u(t){try{return JSON.parse(t)}catch(e){return!1}}function p(t){this.reconPack=t,this.buffers=[]}function h(t){return{type:e.ERROR,data:"parser error: "+t}}var f=n(8)("socket.io-parser"),l=n(11),d=n(12),y=n(13),m=n(14);e.protocol=4,e.types=["CONNECT","DISCONNECT","EVENT","ACK","ERROR","BINARY_EVENT","BINARY_ACK"],e.CONNECT=0,e.DISCONNECT=1,e.EVENT=2,e.ACK=3,e.ERROR=4,e.BINARY_EVENT=5,e.BINARY_ACK=6,e.Encoder=r,e.Decoder=a;var g=e.ERROR+'"encode error"';r.prototype.encode=function(t,n){if(f("encoding packet %j",t),e.BINARY_EVENT===t.type||e.BINARY_ACK===t.type)s(t,n);else{var r=o(t);n([r])}},l(a.prototype),a.prototype.add=function(t){var n;if("string"==typeof t)n=c(t),e.BINARY_EVENT===n.type||e.BINARY_ACK===n.type?(this.reconstructor=new p(n),0===this.reconstructor.reconPack.attachments&&this.emit("decoded",n)):this.emit("decoded",n);else{if(!m(t)&&!t.base64)throw new Error("Unknown type: "+t);if(!this.reconstructor)throw new Error("got binary data when not reconstructing a packet");n=this.reconstructor.takeBinaryData(t),n&&(this.reconstructor=null,this.emit("decoded",n))}},a.prototype.destroy=function(){this.reconstructor&&this.reconstructor.finishedReconstruction()},p.prototype.takeBinaryData=function(t){if(this.buffers.push(t),this.buffers.length===this.reconPack.attachments){var e=d.reconstructPacket(this.reconPack,this.buffers);return this.finishedReconstruction(),e}return null},p.prototype.finishedReconstruction=function(){this.reconPack=null,this.buffers=[]}},function(t,e,n){(function(r){"use strict";function o(){return!("undefined"==typeof window||!window.process||"renderer"!==window.process.type)||("undefined"==typeof navigator||!navigator.userAgent||!navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/))&&("undefined"!=typeof document&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||"undefined"!=typeof window&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/))}function i(t){var n=this.useColors;if(t[0]=(n?"%c":"")+this.namespace+(n?" %c":" ")+t[0]+(n?"%c ":" ")+"+"+e.humanize(this.diff),n){var r="color: "+this.color;t.splice(1,0,r,"color: inherit");var o=0,i=0;t[0].replace(/%[a-zA-Z%]/g,function(t){"%%"!==t&&(o++,"%c"===t&&(i=o))}),t.splice(i,0,r)}}function s(){return"object"===("undefined"==typeof console?"undefined":p(console))&&console.log&&Function.prototype.apply.call(console.log,console,arguments)}function a(t){try{null==t?e.storage.removeItem("debug"):e.storage.debug=t}catch(n){}}function c(){var t;try{t=e.storage.debug}catch(n){}return!t&&"undefined"!=typeof r&&"env"in r&&(t=r.env.DEBUG),t}function u(){try{return window.localStorage}catch(t){}}var p="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t};e=t.exports=n(9),e.log=s,e.formatArgs=i,e.save=a,e.load=c,e.useColors=o,e.storage="undefined"!=typeof chrome&&"undefined"!=typeof chrome.storage?chrome.storage.local:u(),e.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"],e.formatters.j=function(t){try{return JSON.stringify(t)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}},e.enable(c())}).call(e,n(4))},function(t,e,n){"use strict";function r(t){var n,r=0;for(n in t)r=(r<<5)-r+t.charCodeAt(n),r|=0;return e.colors[Math.abs(r)%e.colors.length]}function o(t){function n(){if(n.enabled){var t=n,r=+new Date,i=r-(o||r);t.diff=i,t.prev=o,t.curr=r,o=r;for(var s=new Array(arguments.length),a=0;a100)){var e=/^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(t);if(e){var n=parseFloat(e[1]),r=(e[2]||"ms").toLowerCase();switch(r){case"years":case"year":case"yrs":case"yr":case"y":return n*p;case"days":case"day":case"d":return n*u;case"hours":case"hour":case"hrs":case"hr":case"h":return n*c;case"minutes":case"minute":case"mins":case"min":case"m":return n*a;case"seconds":case"second":case"secs":case"sec":case"s":return n*s;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return n;default:return}}}}function r(t){return t>=u?Math.round(t/u)+"d":t>=c?Math.round(t/c)+"h":t>=a?Math.round(t/a)+"m":t>=s?Math.round(t/s)+"s":t+"ms"}function o(t){return i(t,u,"day")||i(t,c,"hour")||i(t,a,"minute")||i(t,s,"second")||t+" ms"}function i(t,e,n){if(!(t0)return n(t);if("number"===i&&isNaN(t)===!1)return e["long"]?o(t):r(t);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(t))}},function(t,e,n){function r(t){if(t)return o(t)}function o(t){for(var e in r.prototype)t[e]=r.prototype[e];return t}t.exports=r,r.prototype.on=r.prototype.addEventListener=function(t,e){return this._callbacks=this._callbacks||{},(this._callbacks["$"+t]=this._callbacks["$"+t]||[]).push(e),this},r.prototype.once=function(t,e){function n(){this.off(t,n),e.apply(this,arguments)}return n.fn=e,this.on(t,n),this},r.prototype.off=r.prototype.removeListener=r.prototype.removeAllListeners=r.prototype.removeEventListener=function(t,e){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var n=this._callbacks["$"+t];if(!n)return this;if(1==arguments.length)return delete this._callbacks["$"+t],this;for(var r,o=0;o0&&!this.encoding){var t=this.packetBuffer.shift();this.packet(t)}},r.prototype.cleanup=function(){p("cleanup");for(var t=this.subs.length,e=0;e=this._reconnectionAttempts)p("reconnect failed"),this.backoff.reset(),this.emitAll("reconnect_failed"),this.reconnecting=!1;else{var e=this.backoff.duration();p("will wait %dms before reconnect attempt",e),this.reconnecting=!0;var n=setTimeout(function(){t.skipReconnect||(p("attempting reconnect"),t.emitAll("reconnect_attempt",t.backoff.attempts),t.emitAll("reconnecting",t.backoff.attempts),t.skipReconnect||t.open(function(e){e?(p("reconnect attempt error"),t.reconnecting=!1,t.reconnect(),t.emitAll("reconnect_error",e.data)):(p("reconnect success"),t.onreconnect())}))},e);this.subs.push({destroy:function(){clearTimeout(n)}})}},r.prototype.onreconnect=function(){var t=this.backoff.attempts;this.reconnecting=!1,this.backoff.reset(),this.updateSocketIds(),this.emitAll("reconnect",t)}},function(t,e,n){t.exports=n(17),t.exports.parser=n(24)},function(t,e,n){function r(t,e){return this instanceof r?(e=e||{},t&&"object"==typeof t&&(e=t,t=null),t?(t=p(t),e.hostname=t.host,e.secure="https"===t.protocol||"wss"===t.protocol,e.port=t.port,t.query&&(e.query=t.query)):e.host&&(e.hostname=p(e.host).host),this.secure=null!=e.secure?e.secure:"undefined"!=typeof location&&"https:"===location.protocol,e.hostname&&!e.port&&(e.port=this.secure?"443":"80"),this.agent=e.agent||!1,this.hostname=e.hostname||("undefined"!=typeof location?location.hostname:"localhost"),this.port=e.port||("undefined"!=typeof location&&location.port?location.port:this.secure?443:80),this.query=e.query||{},"string"==typeof this.query&&(this.query=h.decode(this.query)),this.upgrade=!1!==e.upgrade,this.path=(e.path||"/engine.io").replace(/\/$/,"")+"/",this.forceJSONP=!!e.forceJSONP,this.jsonp=!1!==e.jsonp,this.forceBase64=!!e.forceBase64,this.enablesXDR=!!e.enablesXDR,this.withCredentials=!1!==e.withCredentials,this.timestampParam=e.timestampParam||"t",this.timestampRequests=e.timestampRequests,this.transports=e.transports||["polling","websocket"],this.transportOptions=e.transportOptions||{},this.readyState="",this.writeBuffer=[],this.prevBufferLen=0,this.policyPort=e.policyPort||843,this.rememberUpgrade=e.rememberUpgrade||!1,this.binaryType=null,this.onlyBinaryUpgrades=e.onlyBinaryUpgrades,this.perMessageDeflate=!1!==e.perMessageDeflate&&(e.perMessageDeflate||{}),!0===this.perMessageDeflate&&(this.perMessageDeflate={}),this.perMessageDeflate&&null==this.perMessageDeflate.threshold&&(this.perMessageDeflate.threshold=1024),this.pfx=e.pfx||null,this.key=e.key||null,this.passphrase=e.passphrase||null,this.cert=e.cert||null,this.ca=e.ca||null,this.ciphers=e.ciphers||null,this.rejectUnauthorized=void 0===e.rejectUnauthorized||e.rejectUnauthorized,this.forceNode=!!e.forceNode,this.isReactNative="undefined"!=typeof navigator&&"string"==typeof navigator.product&&"reactnative"===navigator.product.toLowerCase(),("undefined"==typeof self||this.isReactNative)&&(e.extraHeaders&&Object.keys(e.extraHeaders).length>0&&(this.extraHeaders=e.extraHeaders),e.localAddress&&(this.localAddress=e.localAddress)),this.id=null,this.upgrades=null,this.pingInterval=null,this.pingTimeout=null,this.pingIntervalTimer=null,this.pingTimeoutTimer=null,void this.open()):new r(t,e)}function o(t){var e={};for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e}var i=n(18),s=n(11),a=n(3)("engine.io-client:socket"),c=n(38),u=n(24),p=n(2),h=n(32);t.exports=r,r.priorWebsocketSuccess=!1,s(r.prototype),r.protocol=u.protocol,r.Socket=r,r.Transport=n(23),r.transports=n(18),r.parser=n(24),r.prototype.createTransport=function(t){a('creating transport "%s"',t);var e=o(this.query);e.EIO=u.protocol,e.transport=t;var n=this.transportOptions[t]||{};this.id&&(e.sid=this.id);var r=new i[t]({query:e,socket:this,agent:n.agent||this.agent,hostname:n.hostname||this.hostname,port:n.port||this.port,secure:n.secure||this.secure,path:n.path||this.path,forceJSONP:n.forceJSONP||this.forceJSONP,jsonp:n.jsonp||this.jsonp,forceBase64:n.forceBase64||this.forceBase64,enablesXDR:n.enablesXDR||this.enablesXDR,withCredentials:n.withCredentials||this.withCredentials,timestampRequests:n.timestampRequests||this.timestampRequests,timestampParam:n.timestampParam||this.timestampParam,policyPort:n.policyPort||this.policyPort,pfx:n.pfx||this.pfx,key:n.key||this.key,passphrase:n.passphrase||this.passphrase,cert:n.cert||this.cert,ca:n.ca||this.ca,ciphers:n.ciphers||this.ciphers,rejectUnauthorized:n.rejectUnauthorized||this.rejectUnauthorized,perMessageDeflate:n.perMessageDeflate||this.perMessageDeflate,extraHeaders:n.extraHeaders||this.extraHeaders,forceNode:n.forceNode||this.forceNode,localAddress:n.localAddress||this.localAddress,requestTimeout:n.requestTimeout||this.requestTimeout,protocols:n.protocols||void 0,isReactNative:this.isReactNative});return r},r.prototype.open=function(){var t;if(this.rememberUpgrade&&r.priorWebsocketSuccess&&this.transports.indexOf("websocket")!==-1)t="websocket";else{ +if(0===this.transports.length){var e=this;return void setTimeout(function(){e.emit("error","No transports available")},0)}t=this.transports[0]}this.readyState="opening";try{t=this.createTransport(t)}catch(n){return this.transports.shift(),void this.open()}t.open(),this.setTransport(t)},r.prototype.setTransport=function(t){a("setting transport %s",t.name);var e=this;this.transport&&(a("clearing existing transport %s",this.transport.name),this.transport.removeAllListeners()),this.transport=t,t.on("drain",function(){e.onDrain()}).on("packet",function(t){e.onPacket(t)}).on("error",function(t){e.onError(t)}).on("close",function(){e.onClose("transport close")})},r.prototype.probe=function(t){function e(){if(f.onlyBinaryUpgrades){var e=!this.supportsBinary&&f.transport.supportsBinary;h=h||e}h||(a('probe transport "%s" opened',t),p.send([{type:"ping",data:"probe"}]),p.once("packet",function(e){if(!h)if("pong"===e.type&&"probe"===e.data){if(a('probe transport "%s" pong',t),f.upgrading=!0,f.emit("upgrading",p),!p)return;r.priorWebsocketSuccess="websocket"===p.name,a('pausing current transport "%s"',f.transport.name),f.transport.pause(function(){h||"closed"!==f.readyState&&(a("changing transport and sending upgrade packet"),u(),f.setTransport(p),p.send([{type:"upgrade"}]),f.emit("upgrade",p),p=null,f.upgrading=!1,f.flush())})}else{a('probe transport "%s" failed',t);var n=new Error("probe error");n.transport=p.name,f.emit("upgradeError",n)}}))}function n(){h||(h=!0,u(),p.close(),p=null)}function o(e){var r=new Error("probe error: "+e);r.transport=p.name,n(),a('probe transport "%s" failed because of error: %s',t,e),f.emit("upgradeError",r)}function i(){o("transport closed")}function s(){o("socket closed")}function c(t){p&&t.name!==p.name&&(a('"%s" works - aborting "%s"',t.name,p.name),n())}function u(){p.removeListener("open",e),p.removeListener("error",o),p.removeListener("close",i),f.removeListener("close",s),f.removeListener("upgrading",c)}a('probing transport "%s"',t);var p=this.createTransport(t,{probe:1}),h=!1,f=this;r.priorWebsocketSuccess=!1,p.once("open",e),p.once("error",o),p.once("close",i),this.once("close",s),this.once("upgrading",c),p.open()},r.prototype.onOpen=function(){if(a("socket open"),this.readyState="open",r.priorWebsocketSuccess="websocket"===this.transport.name,this.emit("open"),this.flush(),"open"===this.readyState&&this.upgrade&&this.transport.pause){a("starting upgrade probes");for(var t=0,e=this.upgrades.length;t1?{type:b[o],data:t.substring(1)}:{type:b[o]}:C}var i=new Uint8Array(t),o=i[0],s=f(t,1);return w&&"blob"===n&&(s=new w([s])),{type:b[o],data:s}},e.decodeBase64Packet=function(t,e){var n=b[t.charAt(0)];if(!u)return{type:n,data:{base64:!0,data:t.substr(1)}};var r=u.decode(t.substr(1));return"blob"===e&&w&&(r=new w([r])),{type:n,data:r}},e.encodePayload=function(t,n,r){function o(t){return t.length+":"+t}function i(t,r){e.encodePacket(t,!!s&&n,!1,function(t){r(null,o(t))})}"function"==typeof n&&(r=n,n=null);var s=h(t);return n&&s?w&&!g?e.encodePayloadAsBlob(t,r):e.encodePayloadAsArrayBuffer(t,r):t.length?void c(t,i,function(t,e){return r(e.join(""))}):r("0:")},e.decodePayload=function(t,n,r){if("string"!=typeof t)return e.decodePayloadAsBinary(t,n,r);"function"==typeof n&&(r=n,n=null);var o;if(""===t)return r(C,0,1);for(var i,s,a="",c=0,u=t.length;c0;){for(var s=new Uint8Array(o),a=0===s[0],c="",u=1;255!==s[u];u++){if(c.length>310)return r(C,0,1);c+=s[u]}o=f(o,2+c.length),c=parseInt(c);var p=f(o,0,c);if(a)try{p=String.fromCharCode.apply(null,new Uint8Array(p))}catch(h){var l=new Uint8Array(p);p="";for(var u=0;ur&&(n=r),e>=r||e>=n||0===r)return new ArrayBuffer(0);for(var o=new Uint8Array(t),i=new Uint8Array(n-e),s=e,a=0;s=55296&&e<=56319&&o65535&&(e-=65536,o+=d(e>>>10&1023|55296),e=56320|1023&e),o+=d(e);return o}function o(t,e){if(t>=55296&&t<=57343){if(e)throw Error("Lone surrogate U+"+t.toString(16).toUpperCase()+" is not a scalar value");return!1}return!0}function i(t,e){return d(t>>e&63|128)}function s(t,e){if(0==(4294967168&t))return d(t);var n="";return 0==(4294965248&t)?n=d(t>>6&31|192):0==(4294901760&t)?(o(t,e)||(t=65533),n=d(t>>12&15|224),n+=i(t,6)):0==(4292870144&t)&&(n=d(t>>18&7|240),n+=i(t,12),n+=i(t,6)),n+=d(63&t|128)}function a(t,e){e=e||{};for(var r,o=!1!==e.strict,i=n(t),a=i.length,c=-1,u="";++c=f)throw Error("Invalid byte index");var t=255&h[l];if(l++,128==(192&t))return 63&t;throw Error("Invalid continuation byte")}function u(t){var e,n,r,i,s;if(l>f)throw Error("Invalid byte index");if(l==f)return!1;if(e=255&h[l],l++,0==(128&e))return e;if(192==(224&e)){if(n=c(),s=(31&e)<<6|n,s>=128)return s;throw Error("Invalid continuation byte")}if(224==(240&e)){if(n=c(),r=c(),s=(15&e)<<12|n<<6|r,s>=2048)return o(s,t)?s:65533;throw Error("Invalid continuation byte")}if(240==(248&e)&&(n=c(),r=c(),i=c(),s=(7&e)<<18|n<<12|r<<6|i,s>=65536&&s<=1114111))return s;throw Error("Invalid UTF-8 detected")}function p(t,e){e=e||{};var o=!1!==e.strict;h=n(t),f=h.length,l=0;for(var i,s=[];(i=u(o))!==!1;)s.push(i);return r(s)}/*! https://mths.be/utf8js v2.1.2 by @mathias */ +var h,f,l,d=String.fromCharCode;t.exports={version:"2.1.2",encode:a,decode:p}},function(t,e){!function(){"use strict";for(var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",n=new Uint8Array(256),r=0;r>2],i+=t[(3&r[n])<<4|r[n+1]>>4],i+=t[(15&r[n+1])<<2|r[n+2]>>6],i+=t[63&r[n+2]];return o%3===2?i=i.substring(0,i.length-1)+"=":o%3===1&&(i=i.substring(0,i.length-2)+"=="),i},e.decode=function(t){var e,r,o,i,s,a=.75*t.length,c=t.length,u=0;"="===t[t.length-1]&&(a--,"="===t[t.length-2]&&a--);var p=new ArrayBuffer(a),h=new Uint8Array(p);for(e=0;e>4,h[u++]=(15&o)<<4|i>>2,h[u++]=(3&i)<<6|63&s;return p}}()},function(t,e){function n(t){return t.map(function(t){if(t.buffer instanceof ArrayBuffer){var e=t.buffer;if(t.byteLength!==e.byteLength){var n=new Uint8Array(t.byteLength);n.set(new Uint8Array(e,t.byteOffset,t.byteLength)),e=n.buffer}return e}return t})}function r(t,e){e=e||{};var r=new i;return n(t).forEach(function(t){r.append(t)}),e.type?r.getBlob(e.type):r.getBlob()}function o(t,e){return new Blob(n(t),e||{})}var i="undefined"!=typeof i?i:"undefined"!=typeof WebKitBlobBuilder?WebKitBlobBuilder:"undefined"!=typeof MSBlobBuilder?MSBlobBuilder:"undefined"!=typeof MozBlobBuilder&&MozBlobBuilder,s=function(){try{var t=new Blob(["hi"]);return 2===t.size}catch(e){return!1}}(),a=s&&function(){try{var t=new Blob([new Uint8Array([1,2])]);return 2===t.size}catch(e){return!1}}(),c=i&&i.prototype.append&&i.prototype.getBlob;"undefined"!=typeof Blob&&(r.prototype=Blob.prototype,o.prototype=Blob.prototype),t.exports=function(){return s?a?Blob:o:c?r:void 0}()},function(t,e){e.encode=function(t){var e="";for(var n in t)t.hasOwnProperty(n)&&(e.length&&(e+="&"),e+=encodeURIComponent(n)+"="+encodeURIComponent(t[n]));return e},e.decode=function(t){for(var e={},n=t.split("&"),r=0,o=n.length;r0);return e}function r(t){var e=0;for(p=0;p';i=document.createElement(e)}catch(t){i=document.createElement("iframe"),i.name=o.iframeId,i.src="javascript:0"}i.id=o.iframeId,o.form.appendChild(i),o.iframe=i}var o=this;if(!this.form){var i,s=document.createElement("form"),a=document.createElement("textarea"),c=this.iframeId="eio_iframe_"+this.index;s.className="socketio",s.style.position="absolute",s.style.top="-1000px",s.style.left="-1000px",s.target=c,s.method="POST",s.setAttribute("accept-charset","utf-8"),a.name="d",s.appendChild(a),document.body.appendChild(s),this.form=s,this.area=a}this.form.action=this.uri(),r(),t=t.replace(p,"\\\n"),this.area.value=t.replace(u,"\\n");try{this.form.submit()}catch(h){}this.iframe.attachEvent?this.iframe.onreadystatechange=function(){"complete"===o.iframe.readyState&&n()}:this.iframe.onload=n}}).call(e,function(){return this}())},function(t,e,n){function r(t){var e=t&&t.forceBase64;e&&(this.supportsBinary=!1),this.perMessageDeflate=t.perMessageDeflate,this.usingBrowserWebSocket=o&&!t.forceNode,this.protocols=t.protocols,this.usingBrowserWebSocket||(l=i),s.call(this,t)}var o,i,s=n(23),a=n(24),c=n(32),u=n(33),p=n(34),h=n(3)("engine.io-client:websocket");if("undefined"!=typeof WebSocket?o=WebSocket:"undefined"!=typeof self&&(o=self.WebSocket||self.MozWebSocket),"undefined"==typeof window)try{i=n(37)}catch(f){}var l=o||i;t.exports=r,u(r,s),r.prototype.name="websocket",r.prototype.supportsBinary=!0,r.prototype.doOpen=function(){if(this.check()){var t=this.uri(),e=this.protocols,n={agent:this.agent,perMessageDeflate:this.perMessageDeflate};n.pfx=this.pfx,n.key=this.key,n.passphrase=this.passphrase,n.cert=this.cert,n.ca=this.ca,n.ciphers=this.ciphers,n.rejectUnauthorized=this.rejectUnauthorized,this.extraHeaders&&(n.headers=this.extraHeaders),this.localAddress&&(n.localAddress=this.localAddress);try{this.ws=this.usingBrowserWebSocket&&!this.isReactNative?e?new l(t,e):new l(t):new l(t,e,n)}catch(r){return this.emit("error",r)}void 0===this.ws.binaryType&&(this.supportsBinary=!1),this.ws.supports&&this.ws.supports.binary?(this.supportsBinary=!0,this.ws.binaryType="nodebuffer"):this.ws.binaryType="arraybuffer",this.addEventListeners()}},r.prototype.addEventListeners=function(){var t=this;this.ws.onopen=function(){t.onOpen()},this.ws.onclose=function(){t.onClose()},this.ws.onmessage=function(e){t.onData(e.data)},this.ws.onerror=function(e){t.onError("websocket error",e)}},r.prototype.write=function(t){function e(){n.emit("flush"),setTimeout(function(){n.writable=!0,n.emit("drain")},0)}var n=this;this.writable=!1;for(var r=t.length,o=0,i=r;o0&&t.jitter<=1?t.jitter:0,this.attempts=0}t.exports=n,n.prototype.duration=function(){var t=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var e=Math.random(),n=Math.floor(e*this.jitter*t);t=0==(1&Math.floor(10*e))?t-n:t+n}return 0|Math.min(t,this.max)},n.prototype.reset=function(){this.attempts=0},n.prototype.setMin=function(t){this.ms=t},n.prototype.setMax=function(t){this.max=t},n.prototype.setJitter=function(t){this.jitter=t}}])}); +//# sourceMappingURL=socket.io.js.map \ No newline at end of file