提交 89745c0e 编写于 作者: DCloud_JSON's avatar DCloud_JSON

init

上级
/.hbuilderx/
/node_modules/
**/node_modules/**
/unpackage/
/vue.config.js
.DS_Store
/uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center/
<script lang="uts">
let firstBackTime = 0
import uniIdPageInit from '@/uni_modules/uni-id-pages-x/init.uts';
export default {
onLaunch: function(){
console.log('App Launch')
uniIdPageInit()
},
onShow: function () {
console.log('App Show')
},
onHide: function () {
console.log('App Hide')
},
onLastPageBackPress: function () {
console.log('App LastPageBackPress')
if (firstBackTime == 0) {
uni.showToast({
title: '再按一次退出应用',
position: 'bottom',
})
firstBackTime = Date.now()
setTimeout(() => {
firstBackTime = 0
}, 2000)
} else if (Date.now() - firstBackTime < 2000) {
firstBackTime = Date.now()
uni.exit()
}
},
onExit: function () {
console.log('App Exit')
},
}
</script>
<style>
/*每个页面公共css */
.uni-row {
flex-direction: row;
}
.uni-column {
flex-direction: column;
}
</style>
\ No newline at end of file
@font-face {
font-family: "UniIconsLight";
src: url('./uniicons.ttf') format('truetype');
}
.uni-icons {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.uniui-back:before {
content: "\e600";
}
<template>
<view class="uni-navbar">
<view class="uni-navbar-inner" :style="navbarStyle">
<view class="left-content" @click="back">
<text class="uni-icons">{{unicode}}</text>
</view>
<view class="uni-navbar-content" :class="{'is-left':isLeft}">
<slot name="title">{{title}}</slot>
</view>
<view class="right-content">
<slot name="right"></slot>
</view>
</view>
</view>
</template>
<script>
import iconpath from './uniicons.ttf'
export default {
name: "uni-navbar",
props: {
title: {
type: String,
default: ''
},
isLeft: {
type: Boolean,
default: false
}
},
data() {
return {
statusBarHeight: 0
};
},
computed: {
navbarStyle() : string {
return `margin-top:${this.statusBarHeight}px`
},
unicode() : string {
return '\ue600'
}
},
created() {
uni.loadFontFace({
global: false,
family: 'UniIconsFontFamily',
source: iconpath,
success() { },
fail(err) {
console.log(err);
},
})
const sys = uni.getSystemInfoSync()
const statusBarHeight = sys.statusBarHeight
this.statusBarHeight = statusBarHeight
},
mounted() {
// TODO 暂时加定时器,否则不生效
setTimeout(() => {
uni.setNavigationBarColor({
frontColor: '#000000',
backgroundColor: '#ffffff'
})
}, 100)
},
methods: {
back() {
uni.navigateBack({})
}
},
}
</script>
<style>
.uni-icons {
font-family: "UniIconsFontFamily" !important;
font-size: 22px;
font-style: normal;
color: #333;
}
.uni-navbar {
border: 1px #eee solid;
background-color: #fff;
}
.uni-navbar-inner {
position: relative;
display: flex;
flex-direction: row;
justify-content: space-between;
height: 45px;
}
.left-content,
.right-content {
display: flex;
justify-content: center;
align-items: center;
width: 45px;
height: 100%;
}
.uni-navbar-content {
position: absolute;
height: 100%;
top: 0;
bottom: 0;
left: 45px;
right: 45px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.is-left {
justify-content: flex-start;
}
</style>
\ No newline at end of file
<template>
<view class="uni-navbar">
<view class="uni-navbar-inner" :style="navbarStyle">
<view class="left-content" @click="back">
<text class="uni-icons uniui-back"></text>
</view>
<view class="uni-navbar-content">
<slot>{{title}}</slot>
</view>
<view class="right-content">
<slot name="right"></slot>
</view>
</view>
</view>
</template>
<script>
export default {
name: "uni-navbar",
props: {
title: {
type: String,
default: ''
}
},
data() {
return {
statusBarHeight: 0
};
},
computed: {
navbarStyle() {
return `margin-top:${this.statusBarHeight}px`
},
},
created() {
const sys = uni.getSystemInfoSync()
const statusBarHeight = sys.statusBarHeight
this.statusBarHeight = statusBarHeight
},
methods: {
back() {
uni.navigateBack({})
}
},
}
</script>
<style>
@import './uni-icons.css';
.uni-icons {
font-family: UniIconsLight;
text-decoration: none;
text-align: center;
font-size: 22px;
font-style: normal;
color: #333;
}
.uni-navbar {
border: 1px #eee solid;
background-color: #fff;
}
.uni-navbar-inner {
position: relative;
display: flex;
flex-direction: row;
justify-content: space-between;
height: 45px;
}
.left-content,
.right-content {
display: flex;
justify-content: center;
align-items: center;
width: 45px;
height: 100%;
}
.uni-navbar-content {
position: absolute;
height: 100%;
top: 0;
bottom: 0;
left: 45px;
right: 45px;
display: flex;
justify-content: center;
align-items: center;
}
</style>
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
</body>
</html>
import App from './App'
import { createSSRApp } from 'vue'
export function createApp() {
const app = createSSRApp(App)
return {
app
}
}
\ No newline at end of file
{
"name" : "hello uni-id-pages",
"appid" : "__UNI__17D54C2",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
"uni-app-x" : {},
/* 快应用特有相关 */
"quickapp" : {},
/* 小程序特有相关 */
"mp-weixin" : {
"appid" : "",
"setting" : {
"urlCheck" : false
},
"usingComponents" : true
},
"mp-alipay" : {
"usingComponents" : true
},
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "3"
}
{
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
// {
// "path" : "pages/test/test",
// "style" :
// {
// "navigationBarTitleText" : "",
// "enablePullDownRefresh" : false
// }
// },
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "uni-id-pages-x"
}
}, {
"path": "uni_modules/uni-id-pages-x/pages/login/login",
"style": {
"navigationBarTitleText": "登录账号",
"enablePullDownRefresh": false
}
}, {
"path": "uni_modules/uni-id-pages-x/pages/userinfo/userinfo",
"style": {
"navigationBarTitleText": "我的资料",
"enablePullDownRefresh": false
}
},
{
"path": "uni_modules/uni-id-pages-x/pages/register/register",
"style": {
"navigationBarTitleText": "注册",
"enablePullDownRefresh": false
}
},
{
"path": "uni_modules/uni-id-pages-x/pages/retrieve/retrieve",
"style": {
"navigationBarTitleText": "重置密码",
"enablePullDownRefresh": false
}
},
{
"path": "uni_modules/uni-id-pages-x/pages/common/webview/webview",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
},
{
"path": "uni_modules/uni-id-pages-x/pages/userinfo/deactivate/deactivate",
"style": {
"navigationBarTitleText": "注销账号",
"enablePullDownRefresh": false
}
},
{
"path": "uni_modules/uni-id-pages-x/pages/userinfo/bindMobile/bindMobile",
"style": {
"navigationBarTitleText": "绑定手机号码",
"enablePullDownRefresh": false
}
},
{
"path": "uni_modules/uni-id-pages-x/pages/userinfo/setNickname/setNickname",
"style": {
"navigationBarTitleText": "设置昵称",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
// "app-plus": {
// "titleNView": {
// "buttons": [
// {
// "color": "#fff",
// "text": "完成",
// "float": "right",
// "fontWeight": "normal"
// }
// ]
// }
// }
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
"uniIdRouter": {}
}
\ No newline at end of file
<template>
<view class="content">
<view class="userinfo" @click="toUserInfoPage">
<uni-id-pages-x-avatar class="avatar" ref="avatar" width="45px" height="45px" :readOnly="true" :border="false"></uni-id-pages-x-avatar>
<view class="openName">
<text class="openName-text">{{openName}}</text>
</view>
<uni-id-pages-x-icons type="right-arrow" color="#999" />
</view>
<view class="list">
<view class="list-item" hover-class="list-active" @click="toLogin('smsCode')">
<text class="list-item-text">手机验证码登录</text>
<uni-id-pages-x-icons type="right-arrow" color="#999" />
</view>
<view class="list-item" @click="toLogin('username')">
<text class="list-item-text">账号密码登录</text>
<uni-id-pages-x-icons type="right-arrow" color="#999" />
</view>
</view>
</view>
</template>
<script>
import { state } from '@/uni_modules/uni-id-pages-x/store.uts';
export default {
computed: {
userInfo() : UTSJSONObject {
return state.userInfo
},
isLogin() : boolean {
return state.isLogin
},
openName():string{
if(this.isLogin){
const nickname = userInfo['nickname'] as string;
if(nickname.length != 0){
return nickname
}else{
return userInfo['username'] as string
}
}else{
return '未登录'
}
}
},
data() {
return {}
},
onLoad() {},
methods: {
toLogin(loginType:string) {
uni.navigateTo({
url: "/uni_modules/uni-id-pages-x/pages/login/login?type=" + loginType
})
},
toUserInfoPage() {
uni.navigateTo({
url: "/uni_modules/uni-id-pages-x/pages/userinfo/userinfo?showLoginManage=true"
})
},
}
}
</script>
<style lang="scss">
.content {
height: 100%;
background-color: #f5f5f5;
}
.userinfo {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 15px;
// padding-bottom: 30px;
background-color: #fff;
.openName {
flex: 1;
padding: 0 10px;
.openName-text {
font-size: 14px;
color: #333;
}
}
}
.list {
background-color: #fff;
margin-top: 15px;
.list-item {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 0 15px;
height: 50px;
border-bottom: 1px #f5f5f5 solid;
.list-item-text {
font-size: 14px;
}
}
}
.arrow {
}
</style>
\ No newline at end of file
<template>
<view>
<view v-if="inputIsShow">
<template v-for="i in 2" :key="i">
<input type="number" ref="input" class="input" placeholder="input" />
</template>
<button type="primary" @click="doFocus">doFocus</button>
</view>
<button @click="inputIsShow = !inputIsShow">{{inputIsShow?'隐藏':'显示'}}</button>
</view>
</template>
<script>
export default {
data() {
return {
inputIsShow: true
}
},
methods: {
doFocus() {
console.log('doFocus');
(this.$refs["input"] as Element[])[0].focus();
}
}
}
</script>
<style>
.input {
border: 1px solid #aaa;
margin: 15px;
padding: 5;
}
</style>
\ No newline at end of file
<template>
<view>
<input class="input" placeholder="请输入" v-model="text" @input="onInput" />
</view>
</template>
<script>
export default {
data() {
return {
text: ""
}
},
methods: {
onInput(e : InputEvent) {
e.stopPropagation()
e.preventDefault()
}
}
}
</script>
<style>
.input {
border: 1px solid #aaa;
margin: 15px;
padding: 5;
}
</style>
\ No newline at end of file
文件已添加
/**
* 这里是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:12px;
$uni-font-size-base:14px;
$uni-font-size-lg:16;
/* 图片尺寸 */
$uni-img-size-sm:20px;
$uni-img-size-base:26px;
$uni-img-size-lg:40px;
/* Border Radius */
$uni-border-radius-sm: 2px;
$uni-border-radius-base: 3px;
$uni-border-radius-lg: 6px;
$uni-border-radius-circle: 50%;
/* 水平间距 */
$uni-spacing-row-sm: 5px;
$uni-spacing-row-base: 10px;
$uni-spacing-row-lg: 15px;
/* 垂直间距 */
$uni-spacing-col-sm: 4px;
$uni-spacing-col-base: 8px;
$uni-spacing-col-lg: 12px;
/* 透明度 */
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
/* 文章场景相关 */
$uni-color-title: #2C405A; // 文章标题颜色
$uni-font-size-title:20px;
$uni-color-subtitle: #555555; // 二级标题颜色
$uni-font-size-subtitle:26px;
$uni-color-paragraph: #3F536E; // 文章段落颜色
$uni-font-size-paragraph:15px;
## 0.7.2(2023-11-07)
- 新增 前端组件:uni-captcha.uvue、uni-popup-captcha
## 0.7.1(2023-11-07)
- 新增 前端组件:uni-captcha.uvue、uni-popup-captcha
## 0.7.0(2023-10-10)
- 新增 支持在`uni-config-center`中配置mode,可选值为svg和bmp,配置成bmp后可以在uniappx的uvue页面正常显示验证码(uvue不支持显示svg验证码)
## 0.6.4(2023-01-16)
- 修复 部分情况下APP端无法获取验证码的问题
## 0.6.3(2023-01-11)
- 修复 抖音小程序无法显示的Bug
- 修复 刷新时兼容 device_uuid
## 0.6.1(2022-06-23)
- 修复:部分返回值,不符合响应体规范的问题
## 0.6.0(2022-05-27)
- 新增:支持在`uni-config-center`中根据场景值配置
- 修复:弹窗式验证码,输入内容后点击取消,重新打开验证码的值仍然存在的问题
## 0.5.2(2022-05-19)
- 修复在Vue3的兼容问题
## 0.5.1(2022-05-18)
- 修复在某些情况下微信小程序端验证码显示错误的问题
## 0.5.0(2022-05-17)
- 新增支持在`uni-captcha-co`->`config`配置验证码
## 0.4.1(2022-05-16)
- 新增示例项目
## 0.4.0(2022-05-16)
- 集成创建、刷新、显示验证码的云端一体验证码组件
- 云对象`uni-captcha-co`集成获取验证码的api,`getImageCaptcha`
## 0.3.1(2022-05-13)
- 新增 返回值符合响应体规范
## 0.3.0(2022-05-13)
- 新增 支持 uni-config-center 配置
## 0.2.2(2022-04-25)
- 修复 0.2.1 版本引起的使用 image 组件验证码不显示的Bug
## 0.2.1(2022-04-18)
- 更新 优化字体
## 0.2.0(2022-04-14)
- 新增 使用 svg 表现形式更好
- 新增 使用字体,可以任意替换默认字体
- 新增 支持设置字体大小
- 新增 支持忽略某些字符
- 注意 更新之后请重新上传公共模块
## 0.1.0(2021-03-01)
- 调整为uni_modules目录规范
<template>
<view class="captcha-box">
<view class="captcha-img-box">
<image class="loding" src="/uni_modules/uni-captcha/static/run.gif" v-if="loging" mode="widthFix" />
<image class="captcha-img" :class="{opacity:loging}" @click="getImageCaptcha(true)" :src="captchaBase64" mode="widthFix" />
</view>
<input @blur="focusCaptchaInput = false" @focus="focusCaptchaInput = true" :focus="focusCaptchaInput" type="digit" class="captcha" :inputBorder="false"
maxlength="4" v-model="val" placeholder="请输入验证码" :cursor-spacing="cursorSpacing" />
</view>
</template>
<script>
export default {
emits: ["modelValue"],
props: {
cursorSpacing: {
type: Number,
default: 100
},
modelValue: {
type: String,
default: ""
},
value: {
type: String,
default: ""
},
scene: {
type: String,
default: ""
},
focus: {
type: Boolean,
default: false
}
},
data() {
return {
focusCaptchaInput: false,
captchaBase64: "" as string,
loging: false,
val: ""
};
},
watch: {
value: {
handler(value : string) {
// console.log('setvue', value);
this.val = value
},
immediate: true
},
modelValue: {
handler(modelValue : string) {
// console.log('setvue', modelValue);
this.val = modelValue
},
immediate: true
},
scene: {
handler(scene : string) {
if (scene.length != 0) {
this.getImageCaptcha(this.focus)
} else {
uni.showToast({
title: 'scene不能为空',
icon: 'none'
});
}
},
immediate: true
},
val(value : string) {
// console.log('setvue', value);
// TODO 兼容 vue2
// #ifdef VUE2
this.$emit('input', value);
// #endif
// TODO 兼容 vue3
// #ifdef VUE3
this.$emit('update:modelValue', value)
// #endif
}
},
methods: {
setFocus(state:boolean){
this.focusCaptchaInput = state
},
getImageCaptcha(focus : boolean) {
this.loging = true
if (focus) {
this.val = ''
this.focusCaptchaInput = true
}
const uniIdCo = uniCloud.importObject("uni-captcha-co", {
customUI: true
})
uniIdCo.getImageCaptcha({
scene: this.scene,
isUniAppX:true
}).then((result : UTSJSONObject) => {
this.captchaBase64 = (result.getString('captchaBase64') as string)
})
.catch<void>((err : any | null) : void => {
const error = err as UniCloudError
console.error(error)
console.error(error.code)
uni.showToast({
title: error.message,
icon: 'none'
});
})
.finally(()=> {
this.loging = false
})
}
}
}
</script>
<style lang="scss" scoped>
.captcha-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
justify-content: flex-end;
flex: 1;
}
.captcha-img-box,
.captcha {
height: 44px;
}
.captcha {
flex: 1;
}
.captcha-img-box {
position: relative;
background-color: #FEFAE7;
}
.captcha {
background-color: #F8F8F8;
font-size: 14px;
flex: 1;
padding: 0 20rpx;
margin-left: 20rpx;
/* #ifndef APP-NVUE */
box-sizing: border-box;
/* #endif */
}
.captcha-img-box,
.captcha-img,
.loding {
width: 100px;
}
.captcha-img {
/* #ifdef WEB */
cursor: pointer;
/* #endif */
height: 44px;
}
.loding {
z-index: 9;
position: absolute;
width: 30px;
margin:7px 35px;
}
.opacity {
opacity: 0.5;
}
</style>
\ No newline at end of file
<template>
<view class="captcha-box">
<view class="captcha-img-box">
<uni-icons class="loding" size="20px" color="#BBB" v-if="loging" type="spinner-cycle"></uni-icons>
<image class="captcha-img" :class="{opacity:loging}" @click="getImageCaptcha" :src="captchaBase64"
mode="widthFix"></image>
</view>
<input @blur="focusCaptchaInput = false" :focus="focusCaptchaInput" type="text" class="captcha"
:inputBorder="false" maxlength="4" v-model="val" placeholder="请输入验证码">
</view>
</template>
<script>
export default {
props: {
modelValue:String,
value:String,
scene: {
type: String,
default () {
return ""
}
},
focus: {
type: Boolean,
default () {
return false
}
}
},
computed:{
val:{
get(){
return this.value||this.modelValue
},
set(value){
// console.log(value);
// TODO 兼容 vue2
// #ifdef VUE2
this.$emit('input', value);
// #endif
// TODO 兼容 vue3
// #ifdef VUE3
this.$emit('update:modelValue', value)
// #endif
}
}
},
data() {
return {
focusCaptchaInput: false,
captchaBase64: "",
loging: false
};
},
watch: {
scene: {
handler(scene) {
if (scene) {
this.getImageCaptcha(this.focus)
} else {
uni.showToast({
title: 'scene不能为空',
icon: 'none'
});
}
},
immediate:true
}
},
methods: {
getImageCaptcha(focus = true) {
this.loging = true
if (focus) {
this.val = ''
this.focusCaptchaInput = true
}
const uniIdCo = uniCloud.importObject("uni-captcha-co", {
customUI: true
})
uniIdCo.getImageCaptcha({
scene: this.scene
}).then(result => {
// console.log(result);
this.captchaBase64 = result.captchaBase64
})
.catch(e => {
uni.showToast({
title: e.message,
icon: 'none'
});
}).finally(e => {
this.loging = false
})
}
}
}
</script>
<style lang="scss" scoped>
.captcha-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
justify-content: flex-end;
flex: 1;
}
.captcha-img-box,
.captcha {
height: 44px;
line-height: 44px;
}
.captcha-img-box {
position: relative;
background-color: #FEFAE7;
}
.captcha {
background-color: #F8F8F8;
font-size: 14px;
flex: 1;
padding: 0 20rpx;
margin-left: 20rpx;
/* #ifndef APP-NVUE */
box-sizing: border-box;
/* #endif */
}
.captcha-img-box,
.captcha-img,
.loding {
height: 44px !important;
width: 100px;
}
.captcha-img{
cursor: pointer;
}
.loding {
z-index: 9;
color: #bbb;
position: absolute;
text-align: center;
line-height: 45px;
animation: rotate 1s linear infinite;
}
.opacity {
opacity: 0.5;
}
@keyframes rotate {
from {
transform: rotate(0deg)
}
to {
transform: rotate(360deg)
}
}
</style>
\ No newline at end of file
<template>
<uni-popup ref="popup" @clickMask="cancel">
<view class="popup-captcha">
<view class="content">
<text class="title">{{title}}</text>
<uni-captcha ref="captcha" :focus="focus" :scene="scene" v-model="val" :cursorSpacing="150"></uni-captcha>
</view>
<view class="button-box">
<text @click="cancel" class="btn cancel">取消</text>
<text @click="confirm" class="btn confirm">确认</text>
</view>
</view>
</uni-popup>
</template>
<script>
import uniPopup from './uni-popup/uni-popup.uvue';
let confirmCallBack = ():void=>console.log('未传入回调函数')
export default {
components: {
uniPopup
},
emits:["modelValue","confirm","cancel"],
data() {
return {
focus: false,
val:""
}
},
props: {
modelValue: {
type: String,
default: ""
},
value: {
type: String,
default: ""
},
scene: {
type: String,
default: ""
},
title: {
type: String,
default: "默认标题"
}
},
watch: {
val(val:string) {
// console.log(val);
// TODO 兼容 vue2
// #ifdef VUE2
this.$emit('input', val);
// #endif
// TODO 兼容 vue3
// #ifdef VUE3
this.$emit('update:modelValue', val)
// #endif
if(val.length == 4){
this.confirm()
}
}
},
mounted() {},
methods: {
open(callback: () => void) {
// console.log('callback',callback);
confirmCallBack = callback;
this.focus = true
this.val = "";
(this.$refs['popup'] as ComponentPublicInstance).$callMethod("open");
this.$nextTick(()=>{
(this.$refs['captcha'] as ComponentPublicInstance).$callMethod("getImageCaptcha",true);
})
},
close() {
this.focus = false;
(this.$refs['popup'] as ComponentPublicInstance).$callMethod("close");
},
cancel(){
this.close()
this.$emit("cancel")
},
confirm() {
if (this.val.length != 4) {
return uni.showToast({
title: '请填写验证码',
icon: 'none'
});
}
this.close()
this.$emit('confirm')
confirmCallBack()
}
}
}
</script>
<style lang="scss" scoped>
.popup-captcha {
background-color: #fff;
flex-direction: column;
width: 600rpx;
padding:10px 15px;
border-radius: 10px;
}
.popup-captcha .title {
text-align: center;
font-weight: 700;
margin: 5px 0;
}
.popup-captcha .button-box {
flex-direction: row;
justify-content: space-around;
margin-top: 5px;
}
.popup-captcha .button-box .btn {
flex: 1;
height: 35px;
line-height: 35px;
text-align: center;
}
.popup-captcha .button-box .cancel {
border: 1px solid #eee;
color: #666;
}
.confirm {
background-color: #0070ff;
color: #fff;
margin-left: 5px;
}
</style>
\ No newline at end of file
<template>
<uni-popup ref="popup" type="center">
<view class="popup-captcha">
<view class="content">
<text class="title">{{title}}</text>
<uni-captcha :focus="focus" :scene="scene" v-model="val"></uni-captcha>
</view>
<view class="button-box">
<view @click="close" class="btn">取消</view>
<view @click="confirm" class="btn confirm">确认</view>
</view>
</view>
</uni-popup>
</template>
<script>
export default {
data() {
return {
focus: false
}
},
props: {
modelValue:String,
value:String,
scene: {
type: String,
default () {
return ""
}
},
title: {
type: String,
default () {
return ""
}
},
},
computed:{
val:{
get(){
return this.value||this.modelValue
},
set(value){
// console.log(value);
// TODO 兼容 vue2
// #ifdef VUE2
this.$emit('input', value);
// #endif
// TODO 兼容 vue3
// #ifdef VUE3
this.$emit('update:modelValue', value)
// #endif
}
}
},
methods: {
open() {
this.focus = true
this.val = ""
this.$refs.popup.open()
},
close() {
this.focus = false
this.$refs.popup.close()
},
confirm() {
if(!this.val){
return uni.showToast({
title: '请填写验证码',
icon: 'none'
});
}
this.close()
this.$emit('confirm')
}
}
}
</script>
<style lang="scss" scoped>
/* #ifndef APP-NVUE */
view {
display: flex;
flex-direction: column;
}
/* #endif */
.popup-captcha {
/* #ifndef APP-NVUE */
display: flex;
max-width: 600px;
/* #endif */
width: 600rpx;
padding-bottom: 0;
background-color: #FFF;
border-radius: 10px;
flex-direction: column;
position: relative;
}
.popup-captcha .content {
padding: 1.3em 0.8em;
}
.popup-captcha .title {
text-align: center;
word-wrap: break-word;
word-break: break-all;
white-space: pre-wrap;
font-weight: 400;
font-size: 18px;
overflow: hidden;
text-overflow: ellipsis;
color: #111;
margin-bottom: 15px;
}
.button-box {
height: 44px;
border-top: solid 1px #eee;
flex-direction: row;
align-items: center;
justify-content: space-around;
}
.button-box ,.btn{
height: 44px;
line-height: 44px;
}
.button-box .btn{
flex: 1;
margin: 1px;
text-align: center;
}
.button-box .confirm{
color: #007aff;
border-left: solid 1px #eee;
}
</style>
<template>
<view class="popup-root" v-if="isOpen" v-show="isShow" @click="clickMask">
<view @click.stop>
<slot></slot>
</view>
</view>
</template>
<script>
type CloseCallBack = ()=> void;
let closeCallBack:CloseCallBack = () :void => {};
export default {
emits:["close","clickMask"],
data() {
return {
isShow:false,
isOpen:false
}
},
watch: {
// 设置show = true 时,如果没有 open 需要设置为 open
isShow:{
handler(isShow) {
// console.log("isShow",isShow)
if(isShow && this.isOpen == false){
this.isOpen = true
}
},
immediate:true
},
// 设置isOpen = true 时,如果没有 isShow 需要设置为 isShow
isOpen:{
handler(isOpen) {
// console.log("isOpen",isOpen)
if(isOpen && this.isShow == false){
this.isShow = true
}
},
immediate:true
}
},
methods:{
open(){
// ...funs : CloseCallBack[]
// if(funs.length > 0){
// closeCallBack = funs[0]
// }
this.isOpen = true;
},
clickMask(){
this.$emit('clickMask')
this.close()
},
close(): void{
this.isOpen = false;
this.$emit('close')
closeCallBack()
},
hiden(){
this.isShow = false
},
show(){
this.isShow = true
}
}
}
</script>
<style>
.popup-root {
position: fixed;
top: 0;
left: 0;
width: 750rpx;
height: 100%;
flex: 1;
background-color: rgba(0, 0, 0, 0.3);
justify-content: center;
align-items: center;
z-index: 99;
}
</style>
\ No newline at end of file
{
"id": "uni-captcha",
"displayName": "uni-captcha",
"version": "0.7.2",
"description": "云端一体图形验证码组件",
"keywords": [
"captcha",
"图形验证码",
"人机验证",
"防刷",
"防脚本"
],
"repository": "https://gitee.com/dcloud/uni-captcha",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "",
"type": "unicloud-template-function"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "u",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "u",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "u"
}
}
}
}
}
\ No newline at end of file
<h2>
文档已移至 <a href="https://uniapp.dcloud.io/uniCloud/uni-captcha.html" target="_blank">uni-captcha文档</a>
</h2>
\ No newline at end of file
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.
"use strict";var e=require("assert"),t=require("path");function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var o=n(e),r=n(t);const s={10001:"uni-captcha-create-fail",10002:"uni-captcha-verify-fail",10003:"uni-captcha-refresh-fail",10101:"uni-captcha-deviceId-required",10102:"uni-captcha-text-required",10103:"uni-captcha-verify-overdue",10104:"uni-captcha-verify-fail",50403:"uni-captcha-interior-fail"};function a(e){const t=.2*Math.random()-.1;switch(e.type){case"M":case"L":e.x+=t,e.y+=t;break;case"Q":case"C":e.x+=t,e.y+=t,e.x1+=t,e.y1+=t}return e}function i(e,t,n,o,r,s,a){let i,l,c,u,p,h;if(e<=0||e>=1)throw RangeError("spliteCurveAt requires position > 0 && position < 1");return u=[],p=0,i={},l={},c={},i.x=t,i.y=n,l.x=o,l.y=r,c.x=s,c.y=a,h=e,u[p++]=i.x,u[p++]=i.y,u[p++]=i.x+=(l.x-i.x)*h,u[p++]=i.y+=(l.y-i.y)*h,l.x+=(c.x-l.x)*h,l.y+=(c.y-l.y)*h,u[p++]=i.x+(l.x-i.x)*h,u[p++]=i.y+(l.y-i.y)*h,u[p++]=l.x,u[p++]=l.y,u[p++]=c.x,u[p++]=c.y,u}function l(e,t){return Math.random()*(t-e)+e}var c=function(e,t){const n=e[0];o.default(n,"expect a string");const r=t.fontSize,s=r/t.font.unitsPerEm,c=t.font.charToGlyph(n),u=c.advanceWidth?c.advanceWidth*s:0,p=t.x-u/2,h=(t.ascender+t.descender)*s,f=t.y+h/2,d=c.getPath(p,f,r);d.commands.forEach(a),d.commands=function(e,t){const n=[];for(let o=0;o<e.length-1;o++){const r=e[o];if("L"===r.type){const s=e[o+1];if("L"===s.type&&Math.random()>t.truncateLineProbability){const e=l(-.1,.1);n.push(r),n.push({type:"L",x:(r.x+s.x)/2+e,y:(r.y+s.y)/2+e})}else n.push(r)}else if("Q"===r.type&&o>=1){const s=e[o-1];if(("L"===s.type||"M"===s.type)&&Math.random()>t.truncateCurveProbability){const e=s.x,o=s.y,a=l(-.1,.1),c=r.x1+a,u=r.y1+a,p=r.x+a,h=r.y+a,f=i(l(t.truncateCurvePositionMin,t.truncateCurvePositionMax),e,o,c,u,p,h),d={type:"Q",x1:f[2],y1:f[3],x:f[4],y:f[5]},g={type:"L",x:f[4],y:f[5]},m={type:"Q",x1:f[6],y1:f[7],x:f[8],y:f[9]},y={type:"L",x:f[8],y:f[9]};n.push(d),n.push(g),n.push(m),n.push(y)}}else n.push(r)}return n}(d.commands,t);return d.toPathData()};function u(){this.table=new Uint16Array(16),this.trans=new Uint16Array(288)}function p(e,t){this.source=e,this.sourceIndex=0,this.tag=0,this.bitcount=0,this.dest=t,this.destLen=0,this.ltree=new u,this.dtree=new u}var h=new u,f=new u,d=new Uint8Array(30),g=new Uint16Array(30),m=new Uint8Array(30),y=new Uint16Array(30),v=new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),b=new u,x=new Uint8Array(320);function S(e,t,n,o){var r,s;for(r=0;r<n;++r)e[r]=0;for(r=0;r<30-n;++r)e[r+n]=r/n|0;for(s=o,r=0;r<30;++r)t[r]=s,s+=1<<e[r]}var U=new Uint16Array(16);function T(e,t,n,o){var r,s;for(r=0;r<16;++r)e.table[r]=0;for(r=0;r<o;++r)e.table[t[n+r]]++;for(e.table[0]=0,s=0,r=0;r<16;++r)U[r]=s,s+=e.table[r];for(r=0;r<o;++r)t[n+r]&&(e.trans[U[t[n+r]]++]=r)}function w(e){e.bitcount--||(e.tag=e.source[e.sourceIndex++],e.bitcount=7);var t=1&e.tag;return e.tag>>>=1,t}function E(e,t,n){if(!t)return n;for(;e.bitcount<24;)e.tag|=e.source[e.sourceIndex++]<<e.bitcount,e.bitcount+=8;var o=e.tag&65535>>>16-t;return e.tag>>>=t,e.bitcount-=t,o+n}function O(e,t){for(;e.bitcount<24;)e.tag|=e.source[e.sourceIndex++]<<e.bitcount,e.bitcount+=8;var n=0,o=0,r=0,s=e.tag;do{o=2*o+(1&s),s>>>=1,++r,n+=t.table[r],o-=t.table[r]}while(o>=0);return e.tag=s,e.bitcount-=r,t.trans[n+o]}function L(e,t,n){var o,r,s,a,i,l;for(o=E(e,5,257),r=E(e,5,1),s=E(e,4,4),a=0;a<19;++a)x[a]=0;for(a=0;a<s;++a){var c=E(e,3,0);x[v[a]]=c}for(T(b,x,0,19),i=0;i<o+r;){var u=O(e,b);switch(u){case 16:var p=x[i-1];for(l=E(e,2,3);l;--l)x[i++]=p;break;case 17:for(l=E(e,3,3);l;--l)x[i++]=0;break;case 18:for(l=E(e,7,11);l;--l)x[i++]=0;break;default:x[i++]=u}}T(t,x,0,o),T(n,x,o,r)}function k(e,t,n){for(;;){var o,r,s,a,i=O(e,t);if(256===i)return 0;if(i<256)e.dest[e.destLen++]=i;else for(o=E(e,d[i-=257],g[i]),r=O(e,n),a=s=e.destLen-E(e,m[r],y[r]);a<s+o;++a)e.dest[e.destLen++]=e.dest[a]}}function R(e){for(var t,n;e.bitcount>8;)e.sourceIndex--,e.bitcount-=8;if((t=256*(t=e.source[e.sourceIndex+1])+e.source[e.sourceIndex])!==(65535&~(256*e.source[e.sourceIndex+3]+e.source[e.sourceIndex+2])))return-3;for(e.sourceIndex+=4,n=t;n;--n)e.dest[e.destLen++]=e.source[e.sourceIndex++];return e.bitcount=0,0}!function(e,t){var n;for(n=0;n<7;++n)e.table[n]=0;for(e.table[7]=24,e.table[8]=152,e.table[9]=112,n=0;n<24;++n)e.trans[n]=256+n;for(n=0;n<144;++n)e.trans[24+n]=n;for(n=0;n<8;++n)e.trans[168+n]=280+n;for(n=0;n<112;++n)e.trans[176+n]=144+n;for(n=0;n<5;++n)t.table[n]=0;for(t.table[5]=32,n=0;n<32;++n)t.trans[n]=n}(h,f),S(d,g,4,3),S(m,y,2,1),d[28]=0,g[28]=258;var D=function(e,t){var n,o,r=new p(e,t);do{switch(n=w(r),E(r,2,0)){case 0:o=R(r);break;case 1:o=k(r,h,f);break;case 2:L(r,r.ltree,r.dtree),o=k(r,r.ltree,r.dtree);break;default:o=-3}if(0!==o)throw new Error("Data error")}while(!n);return r.destLen<r.dest.length?"function"==typeof r.dest.slice?r.dest.slice(0,r.destLen):r.dest.subarray(0,r.destLen):r.dest};function C(e,t,n,o,r){return Math.pow(1-r,3)*e+3*Math.pow(1-r,2)*r*t+3*(1-r)*Math.pow(r,2)*n+Math.pow(r,3)*o}function M(){this.x1=Number.NaN,this.y1=Number.NaN,this.x2=Number.NaN,this.y2=Number.NaN}function I(){this.commands=[],this.fill="black",this.stroke=null,this.strokeWidth=1}function B(e){throw new Error(e)}function N(e,t){e||B(t)}M.prototype.isEmpty=function(){return isNaN(this.x1)||isNaN(this.y1)||isNaN(this.x2)||isNaN(this.y2)},M.prototype.addPoint=function(e,t){"number"==typeof e&&((isNaN(this.x1)||isNaN(this.x2))&&(this.x1=e,this.x2=e),e<this.x1&&(this.x1=e),e>this.x2&&(this.x2=e)),"number"==typeof t&&((isNaN(this.y1)||isNaN(this.y2))&&(this.y1=t,this.y2=t),t<this.y1&&(this.y1=t),t>this.y2&&(this.y2=t))},M.prototype.addX=function(e){this.addPoint(e,null)},M.prototype.addY=function(e){this.addPoint(null,e)},M.prototype.addBezier=function(e,t,n,o,r,s,a,i){const l=[e,t],c=[n,o],u=[r,s],p=[a,i];this.addPoint(e,t),this.addPoint(a,i);for(let e=0;e<=1;e++){const t=6*l[e]-12*c[e]+6*u[e],n=-3*l[e]+9*c[e]-9*u[e]+3*p[e],o=3*c[e]-3*l[e];if(0===n){if(0===t)continue;const n=-o/t;0<n&&n<1&&(0===e&&this.addX(C(l[e],c[e],u[e],p[e],n)),1===e&&this.addY(C(l[e],c[e],u[e],p[e],n)));continue}const r=Math.pow(t,2)-4*o*n;if(r<0)continue;const s=(-t+Math.sqrt(r))/(2*n);0<s&&s<1&&(0===e&&this.addX(C(l[e],c[e],u[e],p[e],s)),1===e&&this.addY(C(l[e],c[e],u[e],p[e],s)));const a=(-t-Math.sqrt(r))/(2*n);0<a&&a<1&&(0===e&&this.addX(C(l[e],c[e],u[e],p[e],a)),1===e&&this.addY(C(l[e],c[e],u[e],p[e],a)))}},M.prototype.addQuad=function(e,t,n,o,r,s){const a=e+2/3*(n-e),i=t+2/3*(o-t),l=a+1/3*(r-e),c=i+1/3*(s-t);this.addBezier(e,t,a,i,l,c,r,s)},I.prototype.moveTo=function(e,t){this.commands.push({type:"M",x:e,y:t})},I.prototype.lineTo=function(e,t){this.commands.push({type:"L",x:e,y:t})},I.prototype.curveTo=I.prototype.bezierCurveTo=function(e,t,n,o,r,s){this.commands.push({type:"C",x1:e,y1:t,x2:n,y2:o,x:r,y:s})},I.prototype.quadTo=I.prototype.quadraticCurveTo=function(e,t,n,o){this.commands.push({type:"Q",x1:e,y1:t,x:n,y:o})},I.prototype.close=I.prototype.closePath=function(){this.commands.push({type:"Z"})},I.prototype.extend=function(e){if(e.commands)e=e.commands;else if(e instanceof M){const t=e;return this.moveTo(t.x1,t.y1),this.lineTo(t.x2,t.y1),this.lineTo(t.x2,t.y2),this.lineTo(t.x1,t.y2),void this.close()}Array.prototype.push.apply(this.commands,e)},I.prototype.getBoundingBox=function(){const e=new M;let t=0,n=0,o=0,r=0;for(let s=0;s<this.commands.length;s++){const a=this.commands[s];switch(a.type){case"M":e.addPoint(a.x,a.y),t=o=a.x,n=r=a.y;break;case"L":e.addPoint(a.x,a.y),o=a.x,r=a.y;break;case"Q":e.addQuad(o,r,a.x1,a.y1,a.x,a.y),o=a.x,r=a.y;break;case"C":e.addBezier(o,r,a.x1,a.y1,a.x2,a.y2,a.x,a.y),o=a.x,r=a.y;break;case"Z":o=t,r=n;break;default:throw new Error("Unexpected path command "+a.type)}}return e.isEmpty()&&e.addPoint(0,0),e},I.prototype.draw=function(e){e.beginPath();for(let t=0;t<this.commands.length;t+=1){const n=this.commands[t];"M"===n.type?e.moveTo(n.x,n.y):"L"===n.type?e.lineTo(n.x,n.y):"C"===n.type?e.bezierCurveTo(n.x1,n.y1,n.x2,n.y2,n.x,n.y):"Q"===n.type?e.quadraticCurveTo(n.x1,n.y1,n.x,n.y):"Z"===n.type&&e.closePath()}this.fill&&(e.fillStyle=this.fill,e.fill()),this.stroke&&(e.strokeStyle=this.stroke,e.lineWidth=this.strokeWidth,e.stroke())},I.prototype.toPathData=function(e){function t(t){return Math.round(t)===t?""+Math.round(t):t.toFixed(e)}function n(){let e="";for(let n=0;n<arguments.length;n+=1){const o=arguments[n];o>=0&&n>0&&(e+=" "),e+=t(o)}return e}e=void 0!==e?e:2;let o="";for(let e=0;e<this.commands.length;e+=1){const t=this.commands[e];"M"===t.type?o+="M"+n(t.x,t.y):"L"===t.type?o+="L"+n(t.x,t.y):"C"===t.type?o+="C"+n(t.x1,t.y1,t.x2,t.y2,t.x,t.y):"Q"===t.type?o+="Q"+n(t.x1,t.y1,t.x,t.y):"Z"===t.type&&(o+="Z")}return o},I.prototype.toSVG=function(e){let t='<path d="';return t+=this.toPathData(e),t+='"',this.fill&&"black"!==this.fill&&(null===this.fill?t+=' fill="none"':t+=' fill="'+this.fill+'"'),this.stroke&&(t+=' stroke="'+this.stroke+'" stroke-width="'+this.strokeWidth+'"'),t+="/>",t},I.prototype.toDOMElement=function(e){const t=this.toPathData(e),n=document.createElementNS("http://www.w3.org/2000/svg","path");return n.setAttribute("d",t),n};var G={fail:B,argument:N,assert:N};const P={},A={},F={};function H(e){return function(){return e}}A.BYTE=function(e){return G.argument(e>=0&&e<=255,"Byte value should be between 0 and 255."),[e]},F.BYTE=H(1),A.CHAR=function(e){return[e.charCodeAt(0)]},F.CHAR=H(1),A.CHARARRAY=function(e){const t=[];for(let n=0;n<e.length;n+=1)t[n]=e.charCodeAt(n);return t},F.CHARARRAY=function(e){return e.length},A.USHORT=function(e){return[e>>8&255,255&e]},F.USHORT=H(2),A.SHORT=function(e){return e>=32768&&(e=-(65536-e)),[e>>8&255,255&e]},F.SHORT=H(2),A.UINT24=function(e){return[e>>16&255,e>>8&255,255&e]},F.UINT24=H(3),A.ULONG=function(e){return[e>>24&255,e>>16&255,e>>8&255,255&e]},F.ULONG=H(4),A.LONG=function(e){return e>=2147483648&&(e=-(4294967296-e)),[e>>24&255,e>>16&255,e>>8&255,255&e]},F.LONG=H(4),A.FIXED=A.ULONG,F.FIXED=F.ULONG,A.FWORD=A.SHORT,F.FWORD=F.SHORT,A.UFWORD=A.USHORT,F.UFWORD=F.USHORT,A.LONGDATETIME=function(e){return[0,0,0,0,e>>24&255,e>>16&255,e>>8&255,255&e]},F.LONGDATETIME=H(8),A.TAG=function(e){return G.argument(4===e.length,"Tag should be exactly 4 ASCII characters."),[e.charCodeAt(0),e.charCodeAt(1),e.charCodeAt(2),e.charCodeAt(3)]},F.TAG=H(4),A.Card8=A.BYTE,F.Card8=F.BYTE,A.Card16=A.USHORT,F.Card16=F.USHORT,A.OffSize=A.BYTE,F.OffSize=F.BYTE,A.SID=A.USHORT,F.SID=F.USHORT,A.NUMBER=function(e){return e>=-107&&e<=107?[e+139]:e>=108&&e<=1131?[247+((e-=108)>>8),255&e]:e>=-1131&&e<=-108?[251+((e=-e-108)>>8),255&e]:e>=-32768&&e<=32767?A.NUMBER16(e):A.NUMBER32(e)},F.NUMBER=function(e){return A.NUMBER(e).length},A.NUMBER16=function(e){return[28,e>>8&255,255&e]},F.NUMBER16=H(3),A.NUMBER32=function(e){return[29,e>>24&255,e>>16&255,e>>8&255,255&e]},F.NUMBER32=H(5),A.REAL=function(e){let t=e.toString();const n=/\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/.exec(t);if(n){const o=parseFloat("1e"+((n[2]?+n[2]:0)+n[1].length));t=(Math.round(e*o)/o).toString()}let o="";for(let e=0,n=t.length;e<n;e+=1){const n=t[e];o+="e"===n?"-"===t[++e]?"c":"b":"."===n?"a":"-"===n?"e":n}o+=1&o.length?"f":"ff";const r=[30];for(let e=0,t=o.length;e<t;e+=2)r.push(parseInt(o.substr(e,2),16));return r},F.REAL=function(e){return A.REAL(e).length},A.NAME=A.CHARARRAY,F.NAME=F.CHARARRAY,A.STRING=A.CHARARRAY,F.STRING=F.CHARARRAY,P.UTF8=function(e,t,n){const o=[],r=n;for(let n=0;n<r;n++,t+=1)o[n]=e.getUint8(t);return String.fromCharCode.apply(null,o)},P.UTF16=function(e,t,n){const o=[],r=n/2;for(let n=0;n<r;n++,t+=2)o[n]=e.getUint16(t);return String.fromCharCode.apply(null,o)},A.UTF16=function(e){const t=[];for(let n=0;n<e.length;n+=1){const o=e.charCodeAt(n);t[t.length]=o>>8&255,t[t.length]=255&o}return t},F.UTF16=function(e){return 2*e.length};const z={"x-mac-croatian":"ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®Š™´¨≠ŽØ∞±≤≥∆µ∂∑∏š∫ªºΩžø¿¡¬√ƒ≈ƫȅ ÀÃÕŒœĐ—“”‘’÷◊©⁄€‹›Æ»–·‚„‰ÂćÁčÈÍÎÏÌÓÔđÒÚÛÙıˆ˜¯πË˚¸Êæˇ","x-mac-cyrillic":"АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ†°Ґ£§•¶І®©™Ђђ≠Ѓѓ∞±≤≥іµґЈЄєЇїЉљЊњјЅ¬√ƒ≈∆«»… ЋћЌќѕ–—“”‘’÷„ЎўЏџ№Ёёяабвгдежзийклмнопрстуфхцчшщъыьэю","x-mac-gaelic":"ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØḂ±≤≥ḃĊċḊḋḞḟĠġṀæøṁṖṗɼƒſṠ«»… ÀÃÕŒœ–—“”‘’ṡẛÿŸṪ€‹›Ŷŷṫ·Ỳỳ⁊ÂÊÁËÈÍÎÏÌÓÔ♣ÒÚÛÙıÝýŴŵẄẅẀẁẂẃ","x-mac-greek":"Ĺ²É³ÖÜ΅àâä΄¨çéèê룙î‰ôö¦€ùûü†ΓΔΘΛΞΠß®©ΣΪ§≠°·Α±≤≥¥ΒΕΖΗΙΚΜΦΫΨΩάΝ¬ΟΡ≈Τ«»… ΥΧΆΈœ–―“”‘’÷ΉΊΌΎέήίόΏύαβψδεφγηιξκλμνοπώρστθωςχυζϊϋΐΰ­","x-mac-icelandic":"ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûüݰ¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€ÐðÞþý·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ","x-mac-inuit":"ᐃᐄᐅᐆᐊᐋᐱᐲᐳᐴᐸᐹᑉᑎᑏᑐᑑᑕᑖᑦᑭᑮᑯᑰᑲᑳᒃᒋᒌᒍᒎᒐᒑ°ᒡᒥᒦ•¶ᒧ®©™ᒨᒪᒫᒻᓂᓃᓄᓅᓇᓈᓐᓯᓰᓱᓲᓴᓵᔅᓕᓖᓗᓘᓚᓛᓪᔨᔩᔪᔫᔭ… ᔮᔾᕕᕖᕗ–—“”‘’ᕘᕙᕚᕝᕆᕇᕈᕉᕋᕌᕐᕿᖀᖁᖂᖃᖄᖅᖏᖐᖑᖒᖓᖔᖕᙱᙲᙳᙴᙵᙶᖖᖠᖡᖢᖣᖤᖥᖦᕼŁł","x-mac-ce":"ÄĀāÉĄÖÜáąČäčĆć鏟ĎíďĒēĖóėôöõúĚěü†°Ę£§•¶ß®©™ę¨≠ģĮįĪ≤≥īĶ∂∑łĻļĽľĹĺŅņѬ√ńŇ∆«»… ňŐÕőŌ–—“”‘’÷◊ōŔŕŘ‹›řŖŗŠ‚„šŚśÁŤťÍŽžŪÓÔūŮÚůŰűŲųÝýķŻŁżĢˇ",macintosh:"ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›fifl‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ","x-mac-romanian":"ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ĂȘ∞±≤≥¥µ∂∑∏π∫ªºΩăș¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›Țț‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ","x-mac-turkish":"ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸĞğİıŞş‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙˆ˜¯˘˙˚¸˝˛ˇ"};P.MACSTRING=function(e,t,n,o){const r=z[o];if(void 0===r)return;let s="";for(let o=0;o<n;o++){const n=e.getUint8(t+o);s+=n<=127?String.fromCharCode(n):r[127&n]}return s};const W="function"==typeof WeakMap&&new WeakMap;let _;function q(e){return e>=-128&&e<=127}function X(e,t,n){let o=0;const r=e.length;for(;t<r&&o<64&&0===e[t];)++t,++o;return n.push(128|o-1),t}function V(e,t,n){let o=0;const r=e.length;let s=t;for(;s<r&&o<64;){const t=e[s];if(!q(t))break;if(0===t&&s+1<r&&0===e[s+1])break;++s,++o}n.push(o-1);for(let o=t;o<s;++o)n.push(e[o]+256&255);return s}function j(e,t,n){let o=0;const r=e.length;let s=t;for(;s<r&&o<64;){const t=e[s];if(0===t)break;if(q(t)&&s+1<r&&q(e[s+1]))break;++s,++o}n.push(64|o-1);for(let o=t;o<s;++o){const t=e[o];n.push(t+65536>>8&255,t+256&255)}return s}A.MACSTRING=function(e,t){const n=function(e){if(!_){_={};for(let e in z)_[e]=new String(e)}const t=_[e];if(void 0===t)return;if(W){const e=W.get(t);if(void 0!==e)return e}const n=z[e];if(void 0===n)return;const o={};for(let e=0;e<n.length;e++)o[n.charCodeAt(e)]=e+128;return W&&W.set(t,o),o}(t);if(void 0===n)return;const o=[];for(let t=0;t<e.length;t++){let r=e.charCodeAt(t);if(r>=128&&(r=n[r],void 0===r))return;o[t]=r}return o},F.MACSTRING=function(e,t){const n=A.MACSTRING(e,t);return void 0!==n?n.length:0},A.VARDELTAS=function(e){let t=0;const n=[];for(;t<e.length;){const o=e[t];t=0===o?X(e,t,n):o>=-128&&o<=127?V(e,t,n):j(e,t,n)}return n},A.INDEX=function(e){let t=1;const n=[t],o=[];for(let r=0;r<e.length;r+=1){const s=A.OBJECT(e[r]);Array.prototype.push.apply(o,s),t+=s.length,n.push(t)}if(0===o.length)return[0,0];const r=[],s=1+Math.floor(Math.log(t)/Math.log(2))/8|0,a=[void 0,A.BYTE,A.USHORT,A.UINT24,A.ULONG][s];for(let e=0;e<n.length;e+=1){const t=a(n[e]);Array.prototype.push.apply(r,t)}return Array.prototype.concat(A.Card16(e.length),A.OffSize(s),r,o)},F.INDEX=function(e){return A.INDEX(e).length},A.DICT=function(e){let t=[];const n=Object.keys(e),o=n.length;for(let r=0;r<o;r+=1){const o=parseInt(n[r],0),s=e[o];t=t.concat(A.OPERAND(s.value,s.type)),t=t.concat(A.OPERATOR(o))}return t},F.DICT=function(e){return A.DICT(e).length},A.OPERATOR=function(e){return e<1200?[e]:[12,e-1200]},A.OPERAND=function(e,t){let n=[];if(Array.isArray(t))for(let o=0;o<t.length;o+=1)G.argument(e.length===t.length,"Not enough arguments given for type"+t),n=n.concat(A.OPERAND(e[o],t[o]));else if("SID"===t)n=n.concat(A.NUMBER(e));else if("offset"===t)n=n.concat(A.NUMBER32(e));else if("number"===t)n=n.concat(A.NUMBER(e));else{if("real"!==t)throw new Error("Unknown operand type "+t);n=n.concat(A.REAL(e))}return n},A.OP=A.BYTE,F.OP=F.BYTE;const Y="function"==typeof WeakMap&&new WeakMap;function Z(e,t,n){for(let e=0;e<t.length;e+=1){const n=t[e];this[n.name]=n.value}if(this.tableName=e,this.fields=t,n){const e=Object.keys(n);for(let t=0;t<e.length;t+=1){const o=e[t],r=n[o];void 0!==this[o]&&(this[o]=r)}}}function $(e,t,n){void 0===n&&(n=t.length);const o=new Array(t.length+1);o[0]={name:e+"Count",type:"USHORT",value:n};for(let n=0;n<t.length;n++)o[n+1]={name:e+n,type:"USHORT",value:t[n]};return o}function Q(e,t,n){const o=t.length,r=new Array(o+1);r[0]={name:e+"Count",type:"USHORT",value:o};for(let s=0;s<o;s++)r[s+1]={name:e+s,type:"TABLE",value:n(t[s],s)};return r}function K(e,t,n){const o=t.length;let r=[];r[0]={name:e+"Count",type:"USHORT",value:o};for(let e=0;e<o;e++)r=r.concat(n(t[e],e));return r}function J(e){1===e.format?Z.call(this,"coverageTable",[{name:"coverageFormat",type:"USHORT",value:1}].concat($("glyph",e.glyphs))):G.assert(!1,"Can't create coverage table format 2 yet.")}function ee(e){Z.call(this,"scriptListTable",K("scriptRecord",e,(function(e,t){const n=e.script;let o=n.defaultLangSys;return G.assert(!!o,"Unable to write GSUB: script "+e.tag+" has no default language system."),[{name:"scriptTag"+t,type:"TAG",value:e.tag},{name:"script"+t,type:"TABLE",value:new Z("scriptTable",[{name:"defaultLangSys",type:"TABLE",value:new Z("defaultLangSys",[{name:"lookupOrder",type:"USHORT",value:0},{name:"reqFeatureIndex",type:"USHORT",value:o.reqFeatureIndex}].concat($("featureIndex",o.featureIndexes)))}].concat(K("langSys",n.langSysRecords,(function(e,t){const n=e.langSys;return[{name:"langSysTag"+t,type:"TAG",value:e.tag},{name:"langSys"+t,type:"TABLE",value:new Z("langSys",[{name:"lookupOrder",type:"USHORT",value:0},{name:"reqFeatureIndex",type:"USHORT",value:n.reqFeatureIndex}].concat($("featureIndex",n.featureIndexes)))}]}))))}]})))}function te(e){Z.call(this,"featureListTable",K("featureRecord",e,(function(e,t){const n=e.feature;return[{name:"featureTag"+t,type:"TAG",value:e.tag},{name:"feature"+t,type:"TABLE",value:new Z("featureTable",[{name:"featureParams",type:"USHORT",value:n.featureParams}].concat($("lookupListIndex",n.lookupListIndexes)))}]})))}function ne(e,t){Z.call(this,"lookupListTable",Q("lookup",e,(function(e){let n=t[e.lookupType];return G.assert(!!n,"Unable to write GSUB lookup type "+e.lookupType+" tables."),new Z("lookupTable",[{name:"lookupType",type:"USHORT",value:e.lookupType},{name:"lookupFlag",type:"USHORT",value:e.lookupFlag}].concat(Q("subtable",e.subtables,n)))})))}A.CHARSTRING=function(e){if(Y){const t=Y.get(e);if(void 0!==t)return t}let t=[];const n=e.length;for(let o=0;o<n;o+=1){const n=e[o];t=t.concat(A[n.type](n.value))}return Y&&Y.set(e,t),t},F.CHARSTRING=function(e){return A.CHARSTRING(e).length},A.OBJECT=function(e){const t=A[e.type];return G.argument(void 0!==t,"No encoding function for type "+e.type),t(e.value)},F.OBJECT=function(e){const t=F[e.type];return G.argument(void 0!==t,"No sizeOf function for type "+e.type),t(e.value)},A.TABLE=function(e){let t=[];const n=e.fields.length,o=[],r=[];for(let s=0;s<n;s+=1){const n=e.fields[s],a=A[n.type];G.argument(void 0!==a,"No encoding function for field type "+n.type+" ("+n.name+")");let i=e[n.name];void 0===i&&(i=n.value);const l=a(i);"TABLE"===n.type?(r.push(t.length),t=t.concat([0,0]),o.push(l)):t=t.concat(l)}for(let n=0;n<o.length;n+=1){const s=r[n],a=t.length;G.argument(a<65536,"Table "+e.tableName+" too big."),t[s]=a>>8,t[s+1]=255&a,t=t.concat(o[n])}return t},F.TABLE=function(e){let t=0;const n=e.fields.length;for(let o=0;o<n;o+=1){const n=e.fields[o],r=F[n.type];G.argument(void 0!==r,"No sizeOf function for field type "+n.type+" ("+n.name+")");let s=e[n.name];void 0===s&&(s=n.value),t+=r(s),"TABLE"===n.type&&(t+=2)}return t},A.RECORD=A.TABLE,F.RECORD=F.TABLE,A.LITERAL=function(e){return e},F.LITERAL=function(e){return e.length},Z.prototype.encode=function(){return A.TABLE(this)},Z.prototype.sizeOf=function(){return F.TABLE(this)},J.prototype=Object.create(Z.prototype),J.prototype.constructor=J,ee.prototype=Object.create(Z.prototype),ee.prototype.constructor=ee,te.prototype=Object.create(Z.prototype),te.prototype.constructor=te,ne.prototype=Object.create(Z.prototype),ne.prototype.constructor=ne;var oe={Table:Z,Record:Z,Coverage:J,ScriptList:ee,FeatureList:te,LookupList:ne,ushortList:$,tableList:Q,recordList:K};function re(e,t){return e.getUint8(t)}function se(e,t){return e.getUint16(t,!1)}function ae(e,t){return e.getUint32(t,!1)}function ie(e,t){return e.getInt16(t,!1)+e.getUint16(t+2,!1)/65535}const le={byte:1,uShort:2,short:2,uLong:4,fixed:4,longDateTime:8,tag:4};function ce(e,t){this.data=e,this.offset=t,this.relativeOffset=0}ce.prototype.parseByte=function(){const e=this.data.getUint8(this.offset+this.relativeOffset);return this.relativeOffset+=1,e},ce.prototype.parseChar=function(){const e=this.data.getInt8(this.offset+this.relativeOffset);return this.relativeOffset+=1,e},ce.prototype.parseCard8=ce.prototype.parseByte,ce.prototype.parseUShort=function(){const e=this.data.getUint16(this.offset+this.relativeOffset);return this.relativeOffset+=2,e},ce.prototype.parseCard16=ce.prototype.parseUShort,ce.prototype.parseSID=ce.prototype.parseUShort,ce.prototype.parseOffset16=ce.prototype.parseUShort,ce.prototype.parseShort=function(){const e=this.data.getInt16(this.offset+this.relativeOffset);return this.relativeOffset+=2,e},ce.prototype.parseF2Dot14=function(){const e=this.data.getInt16(this.offset+this.relativeOffset)/16384;return this.relativeOffset+=2,e},ce.prototype.parseULong=function(){const e=ae(this.data,this.offset+this.relativeOffset);return this.relativeOffset+=4,e},ce.prototype.parseFixed=function(){const e=ie(this.data,this.offset+this.relativeOffset);return this.relativeOffset+=4,e},ce.prototype.parseString=function(e){const t=this.data,n=this.offset+this.relativeOffset;let o="";this.relativeOffset+=e;for(let r=0;r<e;r++)o+=String.fromCharCode(t.getUint8(n+r));return o},ce.prototype.parseTag=function(){return this.parseString(4)},ce.prototype.parseLongDateTime=function(){let e=ae(this.data,this.offset+this.relativeOffset+4);return e-=2082844800,this.relativeOffset+=8,e},ce.prototype.parseVersion=function(){const e=se(this.data,this.offset+this.relativeOffset),t=se(this.data,this.offset+this.relativeOffset+2);return this.relativeOffset+=4,e+t/4096/10},ce.prototype.skip=function(e,t){void 0===t&&(t=1),this.relativeOffset+=le[e]*t},ce.prototype.parseOffset16List=ce.prototype.parseUShortList=function(e){void 0===e&&(e=this.parseUShort());const t=new Array(e),n=this.data;let o=this.offset+this.relativeOffset;for(let r=0;r<e;r++)t[r]=n.getUint16(o),o+=2;return this.relativeOffset+=2*e,t},ce.prototype.parseShortList=function(e){const t=new Array(e),n=this.data;let o=this.offset+this.relativeOffset;for(let r=0;r<e;r++)t[r]=n.getInt16(o),o+=2;return this.relativeOffset+=2*e,t},ce.prototype.parseByteList=function(e){const t=new Array(e),n=this.data;let o=this.offset+this.relativeOffset;for(let r=0;r<e;r++)t[r]=n.getUint8(o++);return this.relativeOffset+=e,t},ce.prototype.parseList=function(e,t){t||(t=e,e=this.parseUShort());const n=new Array(e);for(let o=0;o<e;o++)n[o]=t.call(this);return n},ce.prototype.parseRecordList=function(e,t){t||(t=e,e=this.parseUShort());const n=new Array(e),o=Object.keys(t);for(let r=0;r<e;r++){const e={};for(let n=0;n<o.length;n++){const r=o[n],s=t[r];e[r]=s.call(this)}n[r]=e}return n},ce.prototype.parseStruct=function(e){if("function"==typeof e)return e.call(this);{const t=Object.keys(e),n={};for(let o=0;o<t.length;o++){const r=t[o],s=e[r];n[r]=s.call(this)}return n}},ce.prototype.parsePointer=function(e){const t=this.parseOffset16();if(t>0)return new ce(this.data,this.offset+t).parseStruct(e)},ce.prototype.parseListOfLists=function(e){const t=this.parseOffset16List(),n=t.length,o=this.relativeOffset,r=new Array(n);for(let o=0;o<n;o++){const n=t[o];if(0!==n)if(this.relativeOffset=n,e){const t=this.parseOffset16List(),s=new Array(t.length);for(let o=0;o<t.length;o++)this.relativeOffset=n+t[o],s[o]=e.call(this);r[o]=s}else r[o]=this.parseUShortList();else r[o]=void 0}return this.relativeOffset=o,r},ce.prototype.parseCoverage=function(){const e=this.offset+this.relativeOffset,t=this.parseUShort(),n=this.parseUShort();if(1===t)return{format:1,glyphs:this.parseUShortList(n)};if(2===t){const e=new Array(n);for(let t=0;t<n;t++)e[t]={start:this.parseUShort(),end:this.parseUShort(),index:this.parseUShort()};return{format:2,ranges:e}}throw new Error("0x"+e.toString(16)+": Coverage format must be 1 or 2.")},ce.prototype.parseClassDef=function(){const e=this.offset+this.relativeOffset,t=this.parseUShort();if(1===t)return{format:1,startGlyph:this.parseUShort(),classes:this.parseUShortList()};if(2===t)return{format:2,ranges:this.parseRecordList({start:ce.uShort,end:ce.uShort,classId:ce.uShort})};throw new Error("0x"+e.toString(16)+": ClassDef format must be 1 or 2.")},ce.list=function(e,t){return function(){return this.parseList(e,t)}},ce.recordList=function(e,t){return function(){return this.parseRecordList(e,t)}},ce.pointer=function(e){return function(){return this.parsePointer(e)}},ce.tag=ce.prototype.parseTag,ce.byte=ce.prototype.parseByte,ce.uShort=ce.offset16=ce.prototype.parseUShort,ce.uShortList=ce.prototype.parseUShortList,ce.struct=ce.prototype.parseStruct,ce.coverage=ce.prototype.parseCoverage,ce.classDef=ce.prototype.parseClassDef;const ue={reserved:ce.uShort,reqFeatureIndex:ce.uShort,featureIndexes:ce.uShortList};ce.prototype.parseScriptList=function(){return this.parsePointer(ce.recordList({tag:ce.tag,script:ce.pointer({defaultLangSys:ce.pointer(ue),langSysRecords:ce.recordList({tag:ce.tag,langSys:ce.pointer(ue)})})}))},ce.prototype.parseFeatureList=function(){return this.parsePointer(ce.recordList({tag:ce.tag,feature:ce.pointer({featureParams:ce.offset16,lookupListIndexes:ce.uShortList})}))},ce.prototype.parseLookupList=function(e){return this.parsePointer(ce.list(ce.pointer((function(){const t=this.parseUShort();G.argument(1<=t&&t<=8,"GSUB lookup type "+t+" unknown.");const n=this.parseUShort(),o=16&n;return{lookupType:t,lookupFlag:n,subtables:this.parseList(ce.pointer(e[t])),markFilteringSet:o?this.parseUShort():void 0}}))))};var pe={getByte:re,getCard8:re,getUShort:se,getCard16:se,getShort:function(e,t){return e.getInt16(t,!1)},getULong:ae,getFixed:ie,getTag:function(e,t){let n="";for(let o=t;o<t+4;o+=1)n+=String.fromCharCode(e.getInt8(o));return n},getOffset:function(e,t,n){let o=0;for(let r=0;r<n;r+=1)o<<=8,o+=e.getUint8(t+r);return o},getBytes:function(e,t,n){const o=[];for(let r=t;r<n;r+=1)o.push(e.getUint8(r));return o},bytesToString:function(e){let t="";for(let n=0;n<e.length;n+=1)t+=String.fromCharCode(e[n]);return t},Parser:ce};function he(e,t,n){e.segments.push({end:t,start:t,delta:-(t-n),offset:0})}var fe={parse:function(e,t){const n={};n.version=pe.getUShort(e,t),G.argument(0===n.version,"cmap table version should be 0."),n.numTables=pe.getUShort(e,t+2);let o=-1;for(let r=n.numTables-1;r>=0;r-=1){const n=pe.getUShort(e,t+4+8*r),s=pe.getUShort(e,t+4+8*r+2);if(3===n&&(0===s||1===s||10===s)){o=pe.getULong(e,t+4+8*r+4);break}}if(-1===o)throw new Error("No valid cmap sub-tables found.");const r=new pe.Parser(e,t+o);if(n.format=r.parseUShort(),12===n.format)!function(e,t){let n;t.parseUShort(),e.length=t.parseULong(),e.language=t.parseULong(),e.groupCount=n=t.parseULong(),e.glyphIndexMap={};for(let o=0;o<n;o+=1){const n=t.parseULong(),o=t.parseULong();let r=t.parseULong();for(let t=n;t<=o;t+=1)e.glyphIndexMap[t]=r,r++}}(n,r);else{if(4!==n.format)throw new Error("Only format 4 and 12 cmap tables are supported (found format "+n.format+").");!function(e,t,n,o,r){let s;e.length=t.parseUShort(),e.language=t.parseUShort(),e.segCount=s=t.parseUShort()>>1,t.skip("uShort",3),e.glyphIndexMap={};const a=new pe.Parser(n,o+r+14),i=new pe.Parser(n,o+r+16+2*s),l=new pe.Parser(n,o+r+16+4*s),c=new pe.Parser(n,o+r+16+6*s);let u=o+r+16+8*s;for(let t=0;t<s-1;t+=1){let t;const o=a.parseUShort(),r=i.parseUShort(),s=l.parseShort(),p=c.parseUShort();for(let a=r;a<=o;a+=1)0!==p?(u=c.offset+c.relativeOffset-2,u+=p,u+=2*(a-r),t=pe.getUShort(n,u),0!==t&&(t=t+s&65535)):t=a+s&65535,e.glyphIndexMap[a]=t}}(n,r,e,t,o)}return n},make:function(e){const t=new oe.Table("cmap",[{name:"version",type:"USHORT",value:0},{name:"numTables",type:"USHORT",value:1},{name:"platformID",type:"USHORT",value:3},{name:"encodingID",type:"USHORT",value:1},{name:"offset",type:"ULONG",value:12},{name:"format",type:"USHORT",value:4},{name:"length",type:"USHORT",value:0},{name:"language",type:"USHORT",value:0},{name:"segCountX2",type:"USHORT",value:0},{name:"searchRange",type:"USHORT",value:0},{name:"entrySelector",type:"USHORT",value:0},{name:"rangeShift",type:"USHORT",value:0}]);t.segments=[];for(let n=0;n<e.length;n+=1){const o=e.get(n);for(let e=0;e<o.unicodes.length;e+=1)he(t,o.unicodes[e],n);t.segments=t.segments.sort((function(e,t){return e.start-t.start}))}let n;!function(e){e.segments.push({end:65535,start:65535,delta:1,offset:0})}(t),n=t.segments.length,t.segCountX2=2*n,t.searchRange=2*Math.pow(2,Math.floor(Math.log(n)/Math.log(2))),t.entrySelector=Math.log(t.searchRange/2)/Math.log(2),t.rangeShift=t.segCountX2-t.searchRange;let o=[],r=[],s=[],a=[],i=[];for(let e=0;e<n;e+=1){const n=t.segments[e];o=o.concat({name:"end_"+e,type:"USHORT",value:n.end}),r=r.concat({name:"start_"+e,type:"USHORT",value:n.start}),s=s.concat({name:"idDelta_"+e,type:"SHORT",value:n.delta}),a=a.concat({name:"idRangeOffset_"+e,type:"USHORT",value:n.offset}),void 0!==n.glyphId&&(i=i.concat({name:"glyph_"+e,type:"USHORT",value:n.glyphId}))}return t.fields=t.fields.concat(o),t.fields.push({name:"reservedPad",type:"USHORT",value:0}),t.fields=t.fields.concat(r),t.fields=t.fields.concat(s),t.fields=t.fields.concat(a),t.fields=t.fields.concat(i),t.length=14+2*o.length+2+2*r.length+2*s.length+2*a.length+2*i.length,t}};const de=[".notdef","space","exclam","quotedbl","numbersign","dollar","percent","ampersand","quoteright","parenleft","parenright","asterisk","plus","comma","hyphen","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore","quoteleft","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde","exclamdown","cent","sterling","fraction","yen","florin","section","currency","quotesingle","quotedblleft","guillemotleft","guilsinglleft","guilsinglright","fi","fl","endash","dagger","daggerdbl","periodcentered","paragraph","bullet","quotesinglbase","quotedblbase","quotedblright","guillemotright","ellipsis","perthousand","questiondown","grave","acute","circumflex","tilde","macron","breve","dotaccent","dieresis","ring","cedilla","hungarumlaut","ogonek","caron","emdash","AE","ordfeminine","Lslash","Oslash","OE","ordmasculine","ae","dotlessi","lslash","oslash","oe","germandbls","onesuperior","logicalnot","mu","trademark","Eth","onehalf","plusminus","Thorn","onequarter","divide","brokenbar","degree","thorn","threequarters","twosuperior","registered","minus","eth","multiply","threesuperior","copyright","Aacute","Acircumflex","Adieresis","Agrave","Aring","Atilde","Ccedilla","Eacute","Ecircumflex","Edieresis","Egrave","Iacute","Icircumflex","Idieresis","Igrave","Ntilde","Oacute","Ocircumflex","Odieresis","Ograve","Otilde","Scaron","Uacute","Ucircumflex","Udieresis","Ugrave","Yacute","Ydieresis","Zcaron","aacute","acircumflex","adieresis","agrave","aring","atilde","ccedilla","eacute","ecircumflex","edieresis","egrave","iacute","icircumflex","idieresis","igrave","ntilde","oacute","ocircumflex","odieresis","ograve","otilde","scaron","uacute","ucircumflex","udieresis","ugrave","yacute","ydieresis","zcaron","exclamsmall","Hungarumlautsmall","dollaroldstyle","dollarsuperior","ampersandsmall","Acutesmall","parenleftsuperior","parenrightsuperior","266 ff","onedotenleader","zerooldstyle","oneoldstyle","twooldstyle","threeoldstyle","fouroldstyle","fiveoldstyle","sixoldstyle","sevenoldstyle","eightoldstyle","nineoldstyle","commasuperior","threequartersemdash","periodsuperior","questionsmall","asuperior","bsuperior","centsuperior","dsuperior","esuperior","isuperior","lsuperior","msuperior","nsuperior","osuperior","rsuperior","ssuperior","tsuperior","ff","ffi","ffl","parenleftinferior","parenrightinferior","Circumflexsmall","hyphensuperior","Gravesmall","Asmall","Bsmall","Csmall","Dsmall","Esmall","Fsmall","Gsmall","Hsmall","Ismall","Jsmall","Ksmall","Lsmall","Msmall","Nsmall","Osmall","Psmall","Qsmall","Rsmall","Ssmall","Tsmall","Usmall","Vsmall","Wsmall","Xsmall","Ysmall","Zsmall","colonmonetary","onefitted","rupiah","Tildesmall","exclamdownsmall","centoldstyle","Lslashsmall","Scaronsmall","Zcaronsmall","Dieresissmall","Brevesmall","Caronsmall","Dotaccentsmall","Macronsmall","figuredash","hypheninferior","Ogoneksmall","Ringsmall","Cedillasmall","questiondownsmall","oneeighth","threeeighths","fiveeighths","seveneighths","onethird","twothirds","zerosuperior","foursuperior","fivesuperior","sixsuperior","sevensuperior","eightsuperior","ninesuperior","zeroinferior","oneinferior","twoinferior","threeinferior","fourinferior","fiveinferior","sixinferior","seveninferior","eightinferior","nineinferior","centinferior","dollarinferior","periodinferior","commainferior","Agravesmall","Aacutesmall","Acircumflexsmall","Atildesmall","Adieresissmall","Aringsmall","AEsmall","Ccedillasmall","Egravesmall","Eacutesmall","Ecircumflexsmall","Edieresissmall","Igravesmall","Iacutesmall","Icircumflexsmall","Idieresissmall","Ethsmall","Ntildesmall","Ogravesmall","Oacutesmall","Ocircumflexsmall","Otildesmall","Odieresissmall","OEsmall","Oslashsmall","Ugravesmall","Uacutesmall","Ucircumflexsmall","Udieresissmall","Yacutesmall","Thornsmall","Ydieresissmall","001.000","001.001","001.002","001.003","Black","Bold","Book","Light","Medium","Regular","Roman","Semibold"],ge=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","exclam","quotedbl","numbersign","dollar","percent","ampersand","quoteright","parenleft","parenright","asterisk","plus","comma","hyphen","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore","quoteleft","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","exclamdown","cent","sterling","fraction","yen","florin","section","currency","quotesingle","quotedblleft","guillemotleft","guilsinglleft","guilsinglright","fi","fl","","endash","dagger","daggerdbl","periodcentered","","paragraph","bullet","quotesinglbase","quotedblbase","quotedblright","guillemotright","ellipsis","perthousand","","questiondown","","grave","acute","circumflex","tilde","macron","breve","dotaccent","dieresis","","ring","cedilla","","hungarumlaut","ogonek","caron","emdash","","","","","","","","","","","","","","","","","AE","","ordfeminine","","","","","Lslash","Oslash","OE","ordmasculine","","","","","","ae","","","","dotlessi","","","lslash","oslash","oe","germandbls"],me=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","exclamsmall","Hungarumlautsmall","","dollaroldstyle","dollarsuperior","ampersandsmall","Acutesmall","parenleftsuperior","parenrightsuperior","twodotenleader","onedotenleader","comma","hyphen","period","fraction","zerooldstyle","oneoldstyle","twooldstyle","threeoldstyle","fouroldstyle","fiveoldstyle","sixoldstyle","sevenoldstyle","eightoldstyle","nineoldstyle","colon","semicolon","commasuperior","threequartersemdash","periodsuperior","questionsmall","","asuperior","bsuperior","centsuperior","dsuperior","esuperior","","","isuperior","","","lsuperior","msuperior","nsuperior","osuperior","","","rsuperior","ssuperior","tsuperior","","ff","fi","fl","ffi","ffl","parenleftinferior","","parenrightinferior","Circumflexsmall","hyphensuperior","Gravesmall","Asmall","Bsmall","Csmall","Dsmall","Esmall","Fsmall","Gsmall","Hsmall","Ismall","Jsmall","Ksmall","Lsmall","Msmall","Nsmall","Osmall","Psmall","Qsmall","Rsmall","Ssmall","Tsmall","Usmall","Vsmall","Wsmall","Xsmall","Ysmall","Zsmall","colonmonetary","onefitted","rupiah","Tildesmall","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","exclamdownsmall","centoldstyle","Lslashsmall","","","Scaronsmall","Zcaronsmall","Dieresissmall","Brevesmall","Caronsmall","","Dotaccentsmall","","","Macronsmall","","","figuredash","hypheninferior","","","Ogoneksmall","Ringsmall","Cedillasmall","","","","onequarter","onehalf","threequarters","questiondownsmall","oneeighth","threeeighths","fiveeighths","seveneighths","onethird","twothirds","","","zerosuperior","onesuperior","twosuperior","threesuperior","foursuperior","fivesuperior","sixsuperior","sevensuperior","eightsuperior","ninesuperior","zeroinferior","oneinferior","twoinferior","threeinferior","fourinferior","fiveinferior","sixinferior","seveninferior","eightinferior","nineinferior","centinferior","dollarinferior","periodinferior","commainferior","Agravesmall","Aacutesmall","Acircumflexsmall","Atildesmall","Adieresissmall","Aringsmall","AEsmall","Ccedillasmall","Egravesmall","Eacutesmall","Ecircumflexsmall","Edieresissmall","Igravesmall","Iacutesmall","Icircumflexsmall","Idieresissmall","Ethsmall","Ntildesmall","Ogravesmall","Oacutesmall","Ocircumflexsmall","Otildesmall","Odieresissmall","OEsmall","Oslashsmall","Ugravesmall","Uacutesmall","Ucircumflexsmall","Udieresissmall","Yacutesmall","Thornsmall","Ydieresissmall"],ye=[".notdef",".null","nonmarkingreturn","space","exclam","quotedbl","numbersign","dollar","percent","ampersand","quotesingle","parenleft","parenright","asterisk","plus","comma","hyphen","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore","grave","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde","Adieresis","Aring","Ccedilla","Eacute","Ntilde","Odieresis","Udieresis","aacute","agrave","acircumflex","adieresis","atilde","aring","ccedilla","eacute","egrave","ecircumflex","edieresis","iacute","igrave","icircumflex","idieresis","ntilde","oacute","ograve","ocircumflex","odieresis","otilde","uacute","ugrave","ucircumflex","udieresis","dagger","degree","cent","sterling","section","bullet","paragraph","germandbls","registered","copyright","trademark","acute","dieresis","notequal","AE","Oslash","infinity","plusminus","lessequal","greaterequal","yen","mu","partialdiff","summation","product","pi","integral","ordfeminine","ordmasculine","Omega","ae","oslash","questiondown","exclamdown","logicalnot","radical","florin","approxequal","Delta","guillemotleft","guillemotright","ellipsis","nonbreakingspace","Agrave","Atilde","Otilde","OE","oe","endash","emdash","quotedblleft","quotedblright","quoteleft","quoteright","divide","lozenge","ydieresis","Ydieresis","fraction","currency","guilsinglleft","guilsinglright","fi","fl","daggerdbl","periodcentered","quotesinglbase","quotedblbase","perthousand","Acircumflex","Ecircumflex","Aacute","Edieresis","Egrave","Iacute","Icircumflex","Idieresis","Igrave","Oacute","Ocircumflex","apple","Ograve","Uacute","Ucircumflex","Ugrave","dotlessi","circumflex","tilde","macron","breve","dotaccent","ring","cedilla","hungarumlaut","ogonek","caron","Lslash","lslash","Scaron","scaron","Zcaron","zcaron","brokenbar","Eth","eth","Yacute","yacute","Thorn","thorn","minus","multiply","onesuperior","twosuperior","threesuperior","onehalf","onequarter","threequarters","franc","Gbreve","gbreve","Idotaccent","Scedilla","scedilla","Cacute","cacute","Ccaron","ccaron","dcroat"];function ve(e){this.font=e}function be(e){this.cmap=e}function xe(e,t){this.encoding=e,this.charset=t}function Se(e){switch(e.version){case 1:this.names=ye.slice();break;case 2:this.names=new Array(e.numberOfGlyphs);for(let t=0;t<e.numberOfGlyphs;t++)e.glyphNameIndex[t]<ye.length?this.names[t]=ye[e.glyphNameIndex[t]]:this.names[t]=e.names[e.glyphNameIndex[t]-ye.length];break;case 2.5:this.names=new Array(e.numberOfGlyphs);for(let t=0;t<e.numberOfGlyphs;t++)this.names[t]=ye[t+e.glyphNameIndex[t]];break;case 3:default:this.names=[]}}ve.prototype.charToGlyphIndex=function(e){const t=e.charCodeAt(0),n=this.font.glyphs;if(n)for(let e=0;e<n.length;e+=1){const o=n.get(e);for(let n=0;n<o.unicodes.length;n+=1)if(o.unicodes[n]===t)return e}return null},be.prototype.charToGlyphIndex=function(e){return this.cmap.glyphIndexMap[e.charCodeAt(0)]||0},xe.prototype.charToGlyphIndex=function(e){const t=e.charCodeAt(0),n=this.encoding[t];return this.charset.indexOf(n)},Se.prototype.nameToGlyphIndex=function(e){return this.names.indexOf(e)},Se.prototype.glyphIndexToName=function(e){return this.names[e]};var Ue={line:function(e,t,n,o,r){e.beginPath(),e.moveTo(t,n),e.lineTo(o,r),e.stroke()}};function Te(e,t,n,o,r){let s;return(t&o)>0?(s=e.parseByte(),0==(t&r)&&(s=-s),s=n+s):s=(t&r)>0?n:n+e.parseShort(),s}function we(e,t,n){const o=new pe.Parser(t,n);let r,s;if(e.numberOfContours=o.parseShort(),e._xMin=o.parseShort(),e._yMin=o.parseShort(),e._xMax=o.parseShort(),e._yMax=o.parseShort(),e.numberOfContours>0){const t=e.endPointIndices=[];for(let n=0;n<e.numberOfContours;n+=1)t.push(o.parseUShort());e.instructionLength=o.parseUShort(),e.instructions=[];for(let t=0;t<e.instructionLength;t+=1)e.instructions.push(o.parseByte());const n=t[t.length-1]+1;r=[];for(let e=0;e<n;e+=1)if(s=o.parseByte(),r.push(s),(8&s)>0){const t=o.parseByte();for(let n=0;n<t;n+=1)r.push(s),e+=1}if(G.argument(r.length===n,"Bad flags."),t.length>0){const a=[];let i;if(n>0){for(let e=0;e<n;e+=1)s=r[e],i={},i.onCurve=!!(1&s),i.lastPointOfContour=t.indexOf(e)>=0,a.push(i);let e=0;for(let t=0;t<n;t+=1)s=r[t],i=a[t],i.x=Te(o,s,e,2,16),e=i.x;let l=0;for(let e=0;e<n;e+=1)s=r[e],i=a[e],i.y=Te(o,s,l,4,32),l=i.y}e.points=a}else e.points=[]}else if(0===e.numberOfContours)e.points=[];else{e.isComposite=!0,e.points=[],e.components=[];let t=!0;for(;t;){r=o.parseUShort();const n={glyphIndex:o.parseUShort(),xScale:1,scale01:0,scale10:0,yScale:1,dx:0,dy:0};(1&r)>0?(2&r)>0?(n.dx=o.parseShort(),n.dy=o.parseShort()):n.matchedPoints=[o.parseUShort(),o.parseUShort()]:(2&r)>0?(n.dx=o.parseChar(),n.dy=o.parseChar()):n.matchedPoints=[o.parseByte(),o.parseByte()],(8&r)>0?n.xScale=n.yScale=o.parseF2Dot14():(64&r)>0?(n.xScale=o.parseF2Dot14(),n.yScale=o.parseF2Dot14()):(128&r)>0&&(n.xScale=o.parseF2Dot14(),n.scale01=o.parseF2Dot14(),n.scale10=o.parseF2Dot14(),n.yScale=o.parseF2Dot14()),e.components.push(n),t=!!(32&r)}if(256&r){e.instructionLength=o.parseUShort(),e.instructions=[];for(let t=0;t<e.instructionLength;t+=1)e.instructions.push(o.parseByte())}}}function Ee(e,t){const n=[];for(let o=0;o<e.length;o+=1){const r=e[o],s={x:t.xScale*r.x+t.scale01*r.y+t.dx,y:t.scale10*r.x+t.yScale*r.y+t.dy,onCurve:r.onCurve,lastPointOfContour:r.lastPointOfContour};n.push(s)}return n}function Oe(e){const t=new I;if(!e)return t;const n=function(e){const t=[];let n=[];for(let o=0;o<e.length;o+=1){const r=e[o];n.push(r),r.lastPointOfContour&&(t.push(n),n=[])}return G.argument(0===n.length,"There are still points left in the current contour."),t}(e);for(let e=0;e<n.length;++e){const o=n[e];let r=null,s=o[o.length-1],a=o[0];if(s.onCurve)t.moveTo(s.x,s.y);else if(a.onCurve)t.moveTo(a.x,a.y);else{const e={x:.5*(s.x+a.x),y:.5*(s.y+a.y)};t.moveTo(e.x,e.y)}for(let e=0;e<o.length;++e)if(r=s,s=a,a=o[(e+1)%o.length],s.onCurve)t.lineTo(s.x,s.y);else{let e=r,n=a;r.onCurve||(e={x:.5*(s.x+r.x),y:.5*(s.y+r.y)},t.lineTo(e.x,e.y)),a.onCurve||(n={x:.5*(s.x+a.x),y:.5*(s.y+a.y)}),t.lineTo(e.x,e.y),t.quadraticCurveTo(s.x,s.y,n.x,n.y)}t.closePath()}return t}function Le(e,t){if(t.isComposite)for(let n=0;n<t.components.length;n+=1){const o=t.components[n],r=e.get(o.glyphIndex);if(r.getPath(),r.points){let e;if(void 0===o.matchedPoints)e=Ee(r.points,o);else{if(o.matchedPoints[0]>t.points.length-1||o.matchedPoints[1]>r.points.length-1)throw Error("Matched points out of range in "+t.name);const n=t.points[o.matchedPoints[0]];let s=r.points[o.matchedPoints[1]];const a={xScale:o.xScale,scale01:o.scale01,scale10:o.scale10,yScale:o.yScale,dx:0,dy:0};s=Ee([s],a)[0],a.dx=n.x-s.x,a.dy=n.y-s.y,e=Ee(r.points,a)}t.points=t.points.concat(e)}}return Oe(t.points)}var ke={getPath:Oe,parse:function(e,t,n,o){const r=new Me.GlyphSet(o);for(let s=0;s<n.length-1;s+=1){const a=n[s];a!==n[s+1]?r.push(s,Me.ttfGlyphLoader(o,s,we,e,t+a,Le)):r.push(s,Me.glyphLoader(o,s))}return r}};function Re(e){this.bindConstructorValues(e)}function De(e,t,n){Object.defineProperty(e,t,{get:function(){return e.path,e[n]},set:function(t){e[n]=t},enumerable:!0,configurable:!0})}function Ce(e,t){if(this.font=e,this.glyphs={},Array.isArray(t))for(let e=0;e<t.length;e++)this.glyphs[e]=t[e];this.length=t&&t.length||0}Re.prototype.bindConstructorValues=function(e){this.index=e.index||0,this.name=e.name||null,this.unicode=e.unicode||void 0,this.unicodes=e.unicodes||void 0!==e.unicode?[e.unicode]:[],e.xMin&&(this.xMin=e.xMin),e.yMin&&(this.yMin=e.yMin),e.xMax&&(this.xMax=e.xMax),e.yMax&&(this.yMax=e.yMax),e.advanceWidth&&(this.advanceWidth=e.advanceWidth),Object.defineProperty(this,"path",function(e,t){let n=t||{commands:[]};return{configurable:!0,get:function(){return"function"==typeof n&&(n=n()),n},set:function(e){n=e}}}(0,e.path))},Re.prototype.addUnicode=function(e){0===this.unicodes.length&&(this.unicode=e),this.unicodes.push(e)},Re.prototype.getBoundingBox=function(){return this.path.getBoundingBox()},Re.prototype.getPath=function(e,t,n,o,r){let s,a;e=void 0!==e?e:0,t=void 0!==t?t:0,n=void 0!==n?n:72,o||(o={});let i=o.xScale,l=o.yScale;if(o.hinting&&r&&r.hinting&&(a=this.path&&r.hinting.exec(this,n)),a)s=ke.getPath(a).commands,e=Math.round(e),t=Math.round(t),i=l=1;else{s=this.path.commands;const e=1/this.path.unitsPerEm*n;void 0===i&&(i=e),void 0===l&&(l=e)}const c=new I;for(let n=0;n<s.length;n+=1){const o=s[n];"M"===o.type?c.moveTo(e+o.x*i,t+-o.y*l):"L"===o.type?c.lineTo(e+o.x*i,t+-o.y*l):"Q"===o.type?c.quadraticCurveTo(e+o.x1*i,t+-o.y1*l,e+o.x*i,t+-o.y*l):"C"===o.type?c.curveTo(e+o.x1*i,t+-o.y1*l,e+o.x2*i,t+-o.y2*l,e+o.x*i,t+-o.y*l):"Z"===o.type&&c.closePath()}return c},Re.prototype.getContours=function(){if(void 0===this.points)return[];const e=[];let t=[];for(let n=0;n<this.points.length;n+=1){const o=this.points[n];t.push(o),o.lastPointOfContour&&(e.push(t),t=[])}return G.argument(0===t.length,"There are still points left in the current contour."),e},Re.prototype.getMetrics=function(){const e=this.path.commands,t=[],n=[];for(let o=0;o<e.length;o+=1){const r=e[o];"Z"!==r.type&&(t.push(r.x),n.push(r.y)),"Q"!==r.type&&"C"!==r.type||(t.push(r.x1),n.push(r.y1)),"C"===r.type&&(t.push(r.x2),n.push(r.y2))}const o={xMin:Math.min.apply(null,t),yMin:Math.min.apply(null,n),xMax:Math.max.apply(null,t),yMax:Math.max.apply(null,n),leftSideBearing:this.leftSideBearing};return isFinite(o.xMin)||(o.xMin=0),isFinite(o.xMax)||(o.xMax=this.advanceWidth),isFinite(o.yMin)||(o.yMin=0),isFinite(o.yMax)||(o.yMax=0),o.rightSideBearing=this.advanceWidth-o.leftSideBearing-(o.xMax-o.xMin),o},Re.prototype.draw=function(e,t,n,o,r){this.getPath(t,n,o,r).draw(e)},Re.prototype.drawPoints=function(e,t,n,o){function r(t,n,o,r){const s=2*Math.PI;e.beginPath();for(let a=0;a<t.length;a+=1)e.moveTo(n+t[a].x*r,o+t[a].y*r),e.arc(n+t[a].x*r,o+t[a].y*r,2,0,s,!1);e.closePath(),e.fill()}t=void 0!==t?t:0,n=void 0!==n?n:0,o=void 0!==o?o:24;const s=1/this.path.unitsPerEm*o,a=[],i=[],l=this.path;for(let e=0;e<l.commands.length;e+=1){const t=l.commands[e];void 0!==t.x&&a.push({x:t.x,y:-t.y}),void 0!==t.x1&&i.push({x:t.x1,y:-t.y1}),void 0!==t.x2&&i.push({x:t.x2,y:-t.y2})}e.fillStyle="blue",r(a,t,n,s),e.fillStyle="red",r(i,t,n,s)},Re.prototype.drawMetrics=function(e,t,n,o){let r;t=void 0!==t?t:0,n=void 0!==n?n:0,o=void 0!==o?o:24,r=1/this.path.unitsPerEm*o,e.lineWidth=1,e.strokeStyle="black",Ue.line(e,t,-1e4,t,1e4),Ue.line(e,-1e4,n,1e4,n);const s=this.xMin||0;let a=this.yMin||0;const i=this.xMax||0;let l=this.yMax||0;const c=this.advanceWidth||0;e.strokeStyle="blue",Ue.line(e,t+s*r,-1e4,t+s*r,1e4),Ue.line(e,t+i*r,-1e4,t+i*r,1e4),Ue.line(e,-1e4,n+-a*r,1e4,n+-a*r),Ue.line(e,-1e4,n+-l*r,1e4,n+-l*r),e.strokeStyle="green",Ue.line(e,t+c*r,-1e4,t+c*r,1e4)},Ce.prototype.get=function(e){return"function"==typeof this.glyphs[e]&&(this.glyphs[e]=this.glyphs[e]()),this.glyphs[e]},Ce.prototype.push=function(e,t){this.glyphs[e]=t,this.length++};var Me={GlyphSet:Ce,glyphLoader:function(e,t){return new Re({index:t,font:e})},ttfGlyphLoader:function(e,t,n,o,r,s){return function(){const a=new Re({index:t,font:e});return a.path=function(){n(a,o,r);const t=s(e.glyphs,a);return t.unitsPerEm=e.unitsPerEm,t},De(a,"xMin","_xMin"),De(a,"xMax","_xMax"),De(a,"yMin","_yMin"),De(a,"yMax","_yMax"),a}},cffGlyphLoader:function(e,t,n,o){return function(){const r=new Re({index:t,font:e});return r.path=function(){const t=n(e,r,o);return t.unitsPerEm=e.unitsPerEm,t},r}}};function Ie(e,t){if(e===t)return!0;if(Array.isArray(e)&&Array.isArray(t)){if(e.length!==t.length)return!1;for(let n=0;n<e.length;n+=1)if(!Ie(e[n],t[n]))return!1;return!0}return!1}function Be(e){let t;return t=e.length<1240?107:e.length<33900?1131:32768,t}function Ne(e,t,n){const o=[],r=[],s=pe.getCard16(e,t);let a,i;if(0!==s){const n=pe.getByte(e,t+2);a=t+(s+1)*n+2;let r=t+3;for(let t=0;t<s+1;t+=1)o.push(pe.getOffset(e,r,n)),r+=n;i=a+o[s]}else i=t+2;for(let t=0;t<o.length-1;t+=1){let s=pe.getBytes(e,a+o[t],a+o[t+1]);n&&(s=n(s)),r.push(s)}return{objects:r,startOffset:t,endOffset:i}}function Ge(e,t){let n,o,r,s;if(28===t)return n=e.parseByte(),o=e.parseByte(),n<<8|o;if(29===t)return n=e.parseByte(),o=e.parseByte(),r=e.parseByte(),s=e.parseByte(),n<<24|o<<16|r<<8|s;if(30===t)return function(e){let t="";const n=["0","1","2","3","4","5","6","7","8","9",".","E","E-",null,"-"];for(;;){const o=e.parseByte(),r=o>>4,s=15&o;if(15===r)break;if(t+=n[r],15===s)break;t+=n[s]}return parseFloat(t)}(e);if(t>=32&&t<=246)return t-139;if(t>=247&&t<=250)return n=e.parseByte(),256*(t-247)+n+108;if(t>=251&&t<=254)return n=e.parseByte(),256*-(t-251)-n-108;throw new Error("Invalid b0 "+t)}function Pe(e,t,n){t=void 0!==t?t:0;const o=new pe.Parser(e,t),r=[];let s=[];for(n=void 0!==n?n:e.length;o.relativeOffset<n;){let e=o.parseByte();e<=21?(12===e&&(e=1200+o.parseByte()),r.push([e,s]),s=[]):s.push(Ge(o,e))}return function(e){const t={};for(let n=0;n<e.length;n+=1){const o=e[n][0],r=e[n][1];let s;if(s=1===r.length?r[0]:r,t.hasOwnProperty(o)&&!isNaN(t[o]))throw new Error("Object "+t+" already has key "+o);t[o]=s}return t}(r)}function Ae(e,t){return t=t<=390?de[t]:e[t-391]}function Fe(e,t,n){const o={};let r;for(let s=0;s<t.length;s+=1){const a=t[s];if(Array.isArray(a.type)){const t=[];t.length=a.type.length;for(let o=0;o<a.type.length;o++)r=void 0!==e[a.op]?e[a.op][o]:void 0,void 0===r&&(r=void 0!==a.value&&void 0!==a.value[o]?a.value[o]:null),"SID"===a.type[o]&&(r=Ae(n,r)),t[o]=r;o[a.name]=t}else r=e[a.op],void 0===r&&(r=void 0!==a.value?a.value:null),"SID"===a.type&&(r=Ae(n,r)),o[a.name]=r}return o}const He=[{name:"version",op:0,type:"SID"},{name:"notice",op:1,type:"SID"},{name:"copyright",op:1200,type:"SID"},{name:"fullName",op:2,type:"SID"},{name:"familyName",op:3,type:"SID"},{name:"weight",op:4,type:"SID"},{name:"isFixedPitch",op:1201,type:"number",value:0},{name:"italicAngle",op:1202,type:"number",value:0},{name:"underlinePosition",op:1203,type:"number",value:-100},{name:"underlineThickness",op:1204,type:"number",value:50},{name:"paintType",op:1205,type:"number",value:0},{name:"charstringType",op:1206,type:"number",value:2},{name:"fontMatrix",op:1207,type:["real","real","real","real","real","real"],value:[.001,0,0,.001,0,0]},{name:"uniqueId",op:13,type:"number"},{name:"fontBBox",op:5,type:["number","number","number","number"],value:[0,0,0,0]},{name:"strokeWidth",op:1208,type:"number",value:0},{name:"xuid",op:14,type:[],value:null},{name:"charset",op:15,type:"offset",value:0},{name:"encoding",op:16,type:"offset",value:0},{name:"charStrings",op:17,type:"offset",value:0},{name:"private",op:18,type:["number","offset"],value:[0,0]},{name:"ros",op:1230,type:["SID","SID","number"]},{name:"cidFontVersion",op:1231,type:"number",value:0},{name:"cidFontRevision",op:1232,type:"number",value:0},{name:"cidFontType",op:1233,type:"number",value:0},{name:"cidCount",op:1234,type:"number",value:8720},{name:"uidBase",op:1235,type:"number"},{name:"fdArray",op:1236,type:"offset"},{name:"fdSelect",op:1237,type:"offset"},{name:"fontName",op:1238,type:"SID"}],ze=[{name:"subrs",op:19,type:"offset",value:0},{name:"defaultWidthX",op:20,type:"number",value:0},{name:"nominalWidthX",op:21,type:"number",value:0}];function We(e,t){return Fe(Pe(e,0,e.byteLength),He,t)}function _e(e,t,n,o){return Fe(Pe(e,t,n),ze,o)}function qe(e,t,n,o){const r=[];for(let s=0;s<n.length;s+=1){const a=We(new DataView(new Uint8Array(n[s]).buffer),o);a._subrs=[],a._subrsBias=0;const i=a.private[0],l=a.private[1];if(0!==i&&0!==l){const n=_e(e,l+t,i,o);if(a._defaultWidthX=n.defaultWidthX,a._nominalWidthX=n.nominalWidthX,0!==n.subrs){const o=Ne(e,l+n.subrs+t);a._subrs=o.objects,a._subrsBias=Be(a._subrs)}a._privateDict=n}r.push(a)}return r}function Xe(e,t,n){let o,r,s,a;const i=new I,l=[];let c,u,p,h,f=0,d=!1,g=!1,m=0,y=0;if(e.isCIDFont){const n=e.tables.cff.topDict._fdSelect[t.index],o=e.tables.cff.topDict._fdArray[n];c=o._subrs,u=o._subrsBias,p=o._defaultWidthX,h=o._nominalWidthX}else c=e.tables.cff.topDict._subrs,u=e.tables.cff.topDict._subrsBias,p=e.tables.cff.topDict._defaultWidthX,h=e.tables.cff.topDict._nominalWidthX;let v=p;function b(e,t){g&&i.closePath(),i.moveTo(e,t),g=!0}function x(){let e;e=l.length%2!=0,e&&!d&&(v=l.shift()+h),f+=l.length>>1,l.length=0,d=!0}return function n(p){let S,U,T,w,E,O,L,k,R,D,C,M,I=0;for(;I<p.length;){let B=p[I];switch(I+=1,B){case 1:case 3:x();break;case 4:l.length>1&&!d&&(v=l.shift()+h,d=!0),y+=l.pop(),b(m,y);break;case 5:for(;l.length>0;)m+=l.shift(),y+=l.shift(),i.lineTo(m,y);break;case 6:for(;l.length>0&&(m+=l.shift(),i.lineTo(m,y),0!==l.length);)y+=l.shift(),i.lineTo(m,y);break;case 7:for(;l.length>0&&(y+=l.shift(),i.lineTo(m,y),0!==l.length);)m+=l.shift(),i.lineTo(m,y);break;case 8:for(;l.length>0;)o=m+l.shift(),r=y+l.shift(),s=o+l.shift(),a=r+l.shift(),m=s+l.shift(),y=a+l.shift(),i.curveTo(o,r,s,a,m,y);break;case 10:E=l.pop()+u,O=c[E],O&&n(O);break;case 11:return;case 12:switch(B=p[I],I+=1,B){case 35:o=m+l.shift(),r=y+l.shift(),s=o+l.shift(),a=r+l.shift(),L=s+l.shift(),k=a+l.shift(),R=L+l.shift(),D=k+l.shift(),C=R+l.shift(),M=D+l.shift(),m=C+l.shift(),y=M+l.shift(),l.shift(),i.curveTo(o,r,s,a,L,k),i.curveTo(R,D,C,M,m,y);break;case 34:o=m+l.shift(),r=y,s=o+l.shift(),a=r+l.shift(),L=s+l.shift(),k=a,R=L+l.shift(),D=a,C=R+l.shift(),M=y,m=C+l.shift(),i.curveTo(o,r,s,a,L,k),i.curveTo(R,D,C,M,m,y);break;case 36:o=m+l.shift(),r=y+l.shift(),s=o+l.shift(),a=r+l.shift(),L=s+l.shift(),k=a,R=L+l.shift(),D=a,C=R+l.shift(),M=D+l.shift(),m=C+l.shift(),i.curveTo(o,r,s,a,L,k),i.curveTo(R,D,C,M,m,y);break;case 37:o=m+l.shift(),r=y+l.shift(),s=o+l.shift(),a=r+l.shift(),L=s+l.shift(),k=a+l.shift(),R=L+l.shift(),D=k+l.shift(),C=R+l.shift(),M=D+l.shift(),Math.abs(C-m)>Math.abs(M-y)?m=C+l.shift():y=M+l.shift(),i.curveTo(o,r,s,a,L,k),i.curveTo(R,D,C,M,m,y);break;default:console.log("Glyph "+t.index+": unknown operator 1200"+B),l.length=0}break;case 14:l.length>0&&!d&&(v=l.shift()+h,d=!0),g&&(i.closePath(),g=!1);break;case 18:x();break;case 19:case 20:x(),I+=f+7>>3;break;case 21:l.length>2&&!d&&(v=l.shift()+h,d=!0),y+=l.pop(),m+=l.pop(),b(m,y);break;case 22:l.length>1&&!d&&(v=l.shift()+h,d=!0),m+=l.pop(),b(m,y);break;case 23:x();break;case 24:for(;l.length>2;)o=m+l.shift(),r=y+l.shift(),s=o+l.shift(),a=r+l.shift(),m=s+l.shift(),y=a+l.shift(),i.curveTo(o,r,s,a,m,y);m+=l.shift(),y+=l.shift(),i.lineTo(m,y);break;case 25:for(;l.length>6;)m+=l.shift(),y+=l.shift(),i.lineTo(m,y);o=m+l.shift(),r=y+l.shift(),s=o+l.shift(),a=r+l.shift(),m=s+l.shift(),y=a+l.shift(),i.curveTo(o,r,s,a,m,y);break;case 26:for(l.length%2&&(m+=l.shift());l.length>0;)o=m,r=y+l.shift(),s=o+l.shift(),a=r+l.shift(),m=s,y=a+l.shift(),i.curveTo(o,r,s,a,m,y);break;case 27:for(l.length%2&&(y+=l.shift());l.length>0;)o=m+l.shift(),r=y,s=o+l.shift(),a=r+l.shift(),m=s+l.shift(),y=a,i.curveTo(o,r,s,a,m,y);break;case 28:S=p[I],U=p[I+1],l.push((S<<24|U<<16)>>16),I+=2;break;case 29:E=l.pop()+e.gsubrsBias,O=e.gsubrs[E],O&&n(O);break;case 30:for(;l.length>0&&(o=m,r=y+l.shift(),s=o+l.shift(),a=r+l.shift(),m=s+l.shift(),y=a+(1===l.length?l.shift():0),i.curveTo(o,r,s,a,m,y),0!==l.length);)o=m+l.shift(),r=y,s=o+l.shift(),a=r+l.shift(),y=a+l.shift(),m=s+(1===l.length?l.shift():0),i.curveTo(o,r,s,a,m,y);break;case 31:for(;l.length>0&&(o=m+l.shift(),r=y,s=o+l.shift(),a=r+l.shift(),y=a+l.shift(),m=s+(1===l.length?l.shift():0),i.curveTo(o,r,s,a,m,y),0!==l.length);)o=m,r=y+l.shift(),s=o+l.shift(),a=r+l.shift(),m=s+l.shift(),y=a+(1===l.length?l.shift():0),i.curveTo(o,r,s,a,m,y);break;default:B<32?console.log("Glyph "+t.index+": unknown operator "+B):B<247?l.push(B-139):B<251?(S=p[I],I+=1,l.push(256*(B-247)+S+108)):B<255?(S=p[I],I+=1,l.push(256*-(B-251)-S-108)):(S=p[I],U=p[I+1],T=p[I+2],w=p[I+3],I+=4,l.push((S<<24|U<<16|T<<8|w)/65536))}}}(n),t.advanceWidth=v,i}function Ve(e,t){let n,o=de.indexOf(e);return o>=0&&(n=o),o=t.indexOf(e),o>=0?n=o+de.length:(n=de.length+t.length,t.push(e)),n}function je(e,t,n){const o={};for(let r=0;r<e.length;r+=1){const s=e[r];let a=t[s.name];void 0===a||Ie(a,s.value)||("SID"===s.type&&(a=Ve(a,n)),o[s.op]={name:s.name,type:s.type,value:a})}return o}function Ye(e,t){const n=new oe.Record("Top DICT",[{name:"dict",type:"DICT",value:{}}]);return n.dict=je(He,e,t),n}function Ze(e){const t=new oe.Record("Top DICT INDEX",[{name:"topDicts",type:"INDEX",value:[]}]);return t.topDicts=[{name:"topDict_0",type:"TABLE",value:e}],t}function $e(e){const t=[],n=e.path;t.push({name:"width",type:"NUMBER",value:e.advanceWidth});let o=0,r=0;for(let e=0;e<n.commands.length;e+=1){let s,a,i=n.commands[e];if("Q"===i.type){const e=1/3,t=2/3;i={type:"C",x:i.x,y:i.y,x1:e*o+t*i.x1,y1:e*r+t*i.y1,x2:e*i.x+t*i.x1,y2:e*i.y+t*i.y1}}if("M"===i.type)s=Math.round(i.x-o),a=Math.round(i.y-r),t.push({name:"dx",type:"NUMBER",value:s}),t.push({name:"dy",type:"NUMBER",value:a}),t.push({name:"rmoveto",type:"OP",value:21}),o=Math.round(i.x),r=Math.round(i.y);else if("L"===i.type)s=Math.round(i.x-o),a=Math.round(i.y-r),t.push({name:"dx",type:"NUMBER",value:s}),t.push({name:"dy",type:"NUMBER",value:a}),t.push({name:"rlineto",type:"OP",value:5}),o=Math.round(i.x),r=Math.round(i.y);else if("C"===i.type){const e=Math.round(i.x1-o),n=Math.round(i.y1-r),l=Math.round(i.x2-i.x1),c=Math.round(i.y2-i.y1);s=Math.round(i.x-i.x2),a=Math.round(i.y-i.y2),t.push({name:"dx1",type:"NUMBER",value:e}),t.push({name:"dy1",type:"NUMBER",value:n}),t.push({name:"dx2",type:"NUMBER",value:l}),t.push({name:"dy2",type:"NUMBER",value:c}),t.push({name:"dx",type:"NUMBER",value:s}),t.push({name:"dy",type:"NUMBER",value:a}),t.push({name:"rrcurveto",type:"OP",value:8}),o=Math.round(i.x),r=Math.round(i.y)}}return t.push({name:"endchar",type:"OP",value:14}),t}var Qe={parse:function(e,t,n){n.tables.cff={};const o=Ne(e,function(e,t){const n={};return n.formatMajor=pe.getCard8(e,t),n.formatMinor=pe.getCard8(e,t+1),n.size=pe.getCard8(e,t+2),n.offsetSize=pe.getCard8(e,t+3),n.startOffset=t,n.endOffset=t+4,n}(e,t).endOffset,pe.bytesToString),r=Ne(e,o.endOffset),s=Ne(e,r.endOffset,pe.bytesToString),a=Ne(e,s.endOffset);n.gsubrs=a.objects,n.gsubrsBias=Be(n.gsubrs);const i=qe(e,t,r.objects,s.objects);if(1!==i.length)throw new Error("CFF table has too many fonts in 'FontSet' - count of fonts NameIndex.length = "+i.length);const l=i[0];if(n.tables.cff.topDict=l,l._privateDict&&(n.defaultWidthX=l._privateDict.defaultWidthX,n.nominalWidthX=l._privateDict.nominalWidthX),void 0!==l.ros[0]&&void 0!==l.ros[1]&&(n.isCIDFont=!0),n.isCIDFont){let o=l.fdArray,r=l.fdSelect;if(0===o||0===r)throw new Error("Font is marked as a CID font, but FDArray and/or FDSelect information is missing");o+=t;const a=qe(e,t,Ne(e,o).objects,s.objects);l._fdArray=a,r+=t,l._fdSelect=function(e,t,n,o){const r=[];let s;const a=new pe.Parser(e,t),i=a.parseCard8();if(0===i)for(let e=0;e<n;e++){if(s=a.parseCard8(),s>=o)throw new Error("CFF table CID Font FDSelect has bad FD index value "+s+" (FD count "+o+")");r.push(s)}else{if(3!==i)throw new Error("CFF Table CID Font FDSelect table has unsupported format "+i);{const e=a.parseCard16();let t,i=a.parseCard16();if(0!==i)throw new Error("CFF Table CID Font FDSelect format 3 range has bad initial GID "+i);for(let l=0;l<e;l++){if(s=a.parseCard8(),t=a.parseCard16(),s>=o)throw new Error("CFF table CID Font FDSelect has bad FD index value "+s+" (FD count "+o+")");if(t>n)throw new Error("CFF Table CID Font FDSelect format 3 range has bad GID "+t);for(;i<t;i++)r.push(s);i=t}if(t!==n)throw new Error("CFF Table CID Font FDSelect format 3 range has bad final GID "+t)}}return r}(e,r,n.numGlyphs,a.length)}const c=t+l.private[1],u=_e(e,c,l.private[0],s.objects);if(n.defaultWidthX=u.defaultWidthX,n.nominalWidthX=u.nominalWidthX,0!==u.subrs){const t=Ne(e,c+u.subrs);n.subrs=t.objects,n.subrsBias=Be(n.subrs)}else n.subrs=[],n.subrsBias=0;const p=Ne(e,t+l.charStrings);n.nGlyphs=p.objects.length;const h=function(e,t,n,o){let r,s;const a=new pe.Parser(e,t);n-=1;const i=[".notdef"],l=a.parseCard8();if(0===l)for(let e=0;e<n;e+=1)r=a.parseSID(),i.push(Ae(o,r));else if(1===l)for(;i.length<=n;){r=a.parseSID(),s=a.parseCard8();for(let e=0;e<=s;e+=1)i.push(Ae(o,r)),r+=1}else{if(2!==l)throw new Error("Unknown charset format "+l);for(;i.length<=n;){r=a.parseSID(),s=a.parseCard16();for(let e=0;e<=s;e+=1)i.push(Ae(o,r)),r+=1}}return i}(e,t+l.charset,n.nGlyphs,s.objects);0===l.encoding?n.cffEncoding=new xe(ge,h):1===l.encoding?n.cffEncoding=new xe(me,h):n.cffEncoding=function(e,t,n){let o;const r={},s=new pe.Parser(e,t),a=s.parseCard8();if(0===a){const e=s.parseCard8();for(let t=0;t<e;t+=1)o=s.parseCard8(),r[o]=t}else{if(1!==a)throw new Error("Unknown encoding format "+a);{const e=s.parseCard8();o=1;for(let t=0;t<e;t+=1){const e=s.parseCard8(),t=s.parseCard8();for(let n=e;n<=e+t;n+=1)r[n]=o,o+=1}}}return new xe(r,n)}(e,t+l.encoding,h),n.encoding=n.encoding||n.cffEncoding,n.glyphs=new Me.GlyphSet(n);for(let e=0;e<n.nGlyphs;e+=1){const t=p.objects[e];n.glyphs.push(e,Me.cffGlyphLoader(n,e,Xe,t))}},make:function(e,t){const n=new oe.Table("CFF ",[{name:"header",type:"RECORD"},{name:"nameIndex",type:"RECORD"},{name:"topDictIndex",type:"RECORD"},{name:"stringIndex",type:"RECORD"},{name:"globalSubrIndex",type:"RECORD"},{name:"charsets",type:"RECORD"},{name:"charStringsIndex",type:"RECORD"},{name:"privateDict",type:"RECORD"}]),o=1/t.unitsPerEm,r={version:t.version,fullName:t.fullName,familyName:t.familyName,weight:t.weightName,fontBBox:t.fontBBox||[0,0,0,0],fontMatrix:[o,0,0,o,0,0],charset:999,encoding:0,charStrings:999,private:[0,999]},s=[];let a;for(let t=1;t<e.length;t+=1)a=e.get(t),s.push(a.name);const i=[];n.header=new oe.Record("Header",[{name:"major",type:"Card8",value:1},{name:"minor",type:"Card8",value:0},{name:"hdrSize",type:"Card8",value:4},{name:"major",type:"Card8",value:1}]),n.nameIndex=function(e){const t=new oe.Record("Name INDEX",[{name:"names",type:"INDEX",value:[]}]);t.names=[];for(let n=0;n<e.length;n+=1)t.names.push({name:"name_"+n,type:"NAME",value:e[n]});return t}([t.postScriptName]);let l=Ye(r,i);n.topDictIndex=Ze(l),n.globalSubrIndex=new oe.Record("Global Subr INDEX",[{name:"subrs",type:"INDEX",value:[]}]),n.charsets=function(e,t){const n=new oe.Record("Charsets",[{name:"format",type:"Card8",value:0}]);for(let o=0;o<e.length;o+=1){const r=Ve(e[o],t);n.fields.push({name:"glyph_"+o,type:"SID",value:r})}return n}(s,i),n.charStringsIndex=function(e){const t=new oe.Record("CharStrings INDEX",[{name:"charStrings",type:"INDEX",value:[]}]);for(let n=0;n<e.length;n+=1){const o=e.get(n),r=$e(o);t.charStrings.push({name:o.name,type:"CHARSTRING",value:r})}return t}(e),n.privateDict=function(e,t){const n=new oe.Record("Private DICT",[{name:"dict",type:"DICT",value:{}}]);return n.dict=je(ze,e,t),n}({},i),n.stringIndex=function(e){const t=new oe.Record("String INDEX",[{name:"strings",type:"INDEX",value:[]}]);t.strings=[];for(let n=0;n<e.length;n+=1)t.strings.push({name:"string_"+n,type:"STRING",value:e[n]});return t}(i);const c=n.header.sizeOf()+n.nameIndex.sizeOf()+n.topDictIndex.sizeOf()+n.stringIndex.sizeOf()+n.globalSubrIndex.sizeOf();return r.charset=c,r.encoding=0,r.charStrings=r.charset+n.charsets.sizeOf(),r.private[1]=r.charStrings+n.charStringsIndex.sizeOf(),l=Ye(r,i),n.topDictIndex=Ze(l),n}};var Ke={parse:function(e,t){const n={},o=new pe.Parser(e,t);return n.version=o.parseVersion(),n.fontRevision=Math.round(1e3*o.parseFixed())/1e3,n.checkSumAdjustment=o.parseULong(),n.magicNumber=o.parseULong(),G.argument(1594834165===n.magicNumber,"Font header has wrong magic number."),n.flags=o.parseUShort(),n.unitsPerEm=o.parseUShort(),n.created=o.parseLongDateTime(),n.modified=o.parseLongDateTime(),n.xMin=o.parseShort(),n.yMin=o.parseShort(),n.xMax=o.parseShort(),n.yMax=o.parseShort(),n.macStyle=o.parseUShort(),n.lowestRecPPEM=o.parseUShort(),n.fontDirectionHint=o.parseShort(),n.indexToLocFormat=o.parseShort(),n.glyphDataFormat=o.parseShort(),n},make:function(e){const t=Math.round((new Date).getTime()/1e3)+2082844800;let n=t;return e.createdTimestamp&&(n=e.createdTimestamp+2082844800),new oe.Table("head",[{name:"version",type:"FIXED",value:65536},{name:"fontRevision",type:"FIXED",value:65536},{name:"checkSumAdjustment",type:"ULONG",value:0},{name:"magicNumber",type:"ULONG",value:1594834165},{name:"flags",type:"USHORT",value:0},{name:"unitsPerEm",type:"USHORT",value:1e3},{name:"created",type:"LONGDATETIME",value:n},{name:"modified",type:"LONGDATETIME",value:t},{name:"xMin",type:"SHORT",value:0},{name:"yMin",type:"SHORT",value:0},{name:"xMax",type:"SHORT",value:0},{name:"yMax",type:"SHORT",value:0},{name:"macStyle",type:"USHORT",value:0},{name:"lowestRecPPEM",type:"USHORT",value:0},{name:"fontDirectionHint",type:"SHORT",value:2},{name:"indexToLocFormat",type:"SHORT",value:0},{name:"glyphDataFormat",type:"SHORT",value:0}],e)}};var Je={parse:function(e,t){const n={},o=new pe.Parser(e,t);return n.version=o.parseVersion(),n.ascender=o.parseShort(),n.descender=o.parseShort(),n.lineGap=o.parseShort(),n.advanceWidthMax=o.parseUShort(),n.minLeftSideBearing=o.parseShort(),n.minRightSideBearing=o.parseShort(),n.xMaxExtent=o.parseShort(),n.caretSlopeRise=o.parseShort(),n.caretSlopeRun=o.parseShort(),n.caretOffset=o.parseShort(),o.relativeOffset+=8,n.metricDataFormat=o.parseShort(),n.numberOfHMetrics=o.parseUShort(),n},make:function(e){return new oe.Table("hhea",[{name:"version",type:"FIXED",value:65536},{name:"ascender",type:"FWORD",value:0},{name:"descender",type:"FWORD",value:0},{name:"lineGap",type:"FWORD",value:0},{name:"advanceWidthMax",type:"UFWORD",value:0},{name:"minLeftSideBearing",type:"FWORD",value:0},{name:"minRightSideBearing",type:"FWORD",value:0},{name:"xMaxExtent",type:"FWORD",value:0},{name:"caretSlopeRise",type:"SHORT",value:1},{name:"caretSlopeRun",type:"SHORT",value:0},{name:"caretOffset",type:"SHORT",value:0},{name:"reserved1",type:"SHORT",value:0},{name:"reserved2",type:"SHORT",value:0},{name:"reserved3",type:"SHORT",value:0},{name:"reserved4",type:"SHORT",value:0},{name:"metricDataFormat",type:"SHORT",value:0},{name:"numberOfHMetrics",type:"USHORT",value:0}],e)}};var et={parse:function(e,t,n,o,r){let s,a;const i=new pe.Parser(e,t);for(let e=0;e<o;e+=1){e<n&&(s=i.parseUShort(),a=i.parseShort());const t=r.get(e);t.advanceWidth=s,t.leftSideBearing=a}},make:function(e){const t=new oe.Table("hmtx",[]);for(let n=0;n<e.length;n+=1){const o=e.get(n),r=o.advanceWidth||0,s=o.leftSideBearing||0;t.fields.push({name:"advanceWidth_"+n,type:"USHORT",value:r}),t.fields.push({name:"leftSideBearing_"+n,type:"SHORT",value:s})}return t}};var tt={make:function(e){const t=new oe.Table("ltag",[{name:"version",type:"ULONG",value:1},{name:"flags",type:"ULONG",value:0},{name:"numTags",type:"ULONG",value:e.length}]);let n="";const o=12+4*e.length;for(let r=0;r<e.length;++r){let s=n.indexOf(e[r]);s<0&&(s=n.length,n+=e[r]),t.fields.push({name:"offset "+r,type:"USHORT",value:o+s}),t.fields.push({name:"length "+r,type:"USHORT",value:e[r].length})}return t.fields.push({name:"stringPool",type:"CHARARRAY",value:n}),t},parse:function(e,t){const n=new pe.Parser(e,t),o=n.parseULong();G.argument(1===o,"Unsupported ltag table version."),n.skip("uLong",1);const r=n.parseULong(),s=[];for(let o=0;o<r;o++){let o="";const r=t+n.parseUShort(),a=n.parseUShort();for(let t=r;t<r+a;++t)o+=String.fromCharCode(e.getInt8(t));s.push(o)}return s}};var nt={parse:function(e,t){const n={},o=new pe.Parser(e,t);return n.version=o.parseVersion(),n.numGlyphs=o.parseUShort(),1===n.version&&(n.maxPoints=o.parseUShort(),n.maxContours=o.parseUShort(),n.maxCompositePoints=o.parseUShort(),n.maxCompositeContours=o.parseUShort(),n.maxZones=o.parseUShort(),n.maxTwilightPoints=o.parseUShort(),n.maxStorage=o.parseUShort(),n.maxFunctionDefs=o.parseUShort(),n.maxInstructionDefs=o.parseUShort(),n.maxStackElements=o.parseUShort(),n.maxSizeOfInstructions=o.parseUShort(),n.maxComponentElements=o.parseUShort(),n.maxComponentDepth=o.parseUShort()),n},make:function(e){return new oe.Table("maxp",[{name:"version",type:"FIXED",value:20480},{name:"numGlyphs",type:"USHORT",value:e}])}};const ot=["copyright","fontFamily","fontSubfamily","uniqueID","fullName","version","postScriptName","trademark","manufacturer","designer","description","manufacturerURL","designerURL","license","licenseURL","reserved","preferredFamily","preferredSubfamily","compatibleFullName","sampleText","postScriptFindFontName","wwsFamily","wwsSubfamily"],rt={0:"en",1:"fr",2:"de",3:"it",4:"nl",5:"sv",6:"es",7:"da",8:"pt",9:"no",10:"he",11:"ja",12:"ar",13:"fi",14:"el",15:"is",16:"mt",17:"tr",18:"hr",19:"zh-Hant",20:"ur",21:"hi",22:"th",23:"ko",24:"lt",25:"pl",26:"hu",27:"es",28:"lv",29:"se",30:"fo",31:"fa",32:"ru",33:"zh",34:"nl-BE",35:"ga",36:"sq",37:"ro",38:"cz",39:"sk",40:"si",41:"yi",42:"sr",43:"mk",44:"bg",45:"uk",46:"be",47:"uz",48:"kk",49:"az-Cyrl",50:"az-Arab",51:"hy",52:"ka",53:"mo",54:"ky",55:"tg",56:"tk",57:"mn-CN",58:"mn",59:"ps",60:"ks",61:"ku",62:"sd",63:"bo",64:"ne",65:"sa",66:"mr",67:"bn",68:"as",69:"gu",70:"pa",71:"or",72:"ml",73:"kn",74:"ta",75:"te",76:"si",77:"my",78:"km",79:"lo",80:"vi",81:"id",82:"tl",83:"ms",84:"ms-Arab",85:"am",86:"ti",87:"om",88:"so",89:"sw",90:"rw",91:"rn",92:"ny",93:"mg",94:"eo",128:"cy",129:"eu",130:"ca",131:"la",132:"qu",133:"gn",134:"ay",135:"tt",136:"ug",137:"dz",138:"jv",139:"su",140:"gl",141:"af",142:"br",143:"iu",144:"gd",145:"gv",146:"ga",147:"to",148:"el-polyton",149:"kl",150:"az",151:"nn"},st={0:0,1:0,2:0,3:0,4:0,5:0,6:0,7:0,8:0,9:0,10:5,11:1,12:4,13:0,14:6,15:0,16:0,17:0,18:0,19:2,20:4,21:9,22:21,23:3,24:29,25:29,26:29,27:29,28:29,29:0,30:0,31:4,32:7,33:25,34:0,35:0,36:0,37:0,38:29,39:29,40:0,41:5,42:7,43:7,44:7,45:7,46:7,47:7,48:7,49:7,50:4,51:24,52:23,53:7,54:7,55:7,56:7,57:27,58:7,59:4,60:4,61:4,62:4,63:26,64:9,65:9,66:9,67:13,68:13,69:11,70:10,71:12,72:17,73:16,74:14,75:15,76:18,77:19,78:20,79:22,80:30,81:0,82:0,83:0,84:4,85:28,86:28,87:28,88:0,89:0,90:0,91:0,92:0,93:0,94:0,128:0,129:0,130:0,131:0,132:0,133:0,134:0,135:7,136:4,137:26,138:0,139:0,140:0,141:0,142:0,143:28,144:0,145:0,146:0,147:0,148:6,149:0,150:0,151:0},at={1078:"af",1052:"sq",1156:"gsw",1118:"am",5121:"ar-DZ",15361:"ar-BH",3073:"ar",2049:"ar-IQ",11265:"ar-JO",13313:"ar-KW",12289:"ar-LB",4097:"ar-LY",6145:"ary",8193:"ar-OM",16385:"ar-QA",1025:"ar-SA",10241:"ar-SY",7169:"aeb",14337:"ar-AE",9217:"ar-YE",1067:"hy",1101:"as",2092:"az-Cyrl",1068:"az",1133:"ba",1069:"eu",1059:"be",2117:"bn",1093:"bn-IN",8218:"bs-Cyrl",5146:"bs",1150:"br",1026:"bg",1027:"ca",3076:"zh-HK",5124:"zh-MO",2052:"zh",4100:"zh-SG",1028:"zh-TW",1155:"co",1050:"hr",4122:"hr-BA",1029:"cs",1030:"da",1164:"prs",1125:"dv",2067:"nl-BE",1043:"nl",3081:"en-AU",10249:"en-BZ",4105:"en-CA",9225:"en-029",16393:"en-IN",6153:"en-IE",8201:"en-JM",17417:"en-MY",5129:"en-NZ",13321:"en-PH",18441:"en-SG",7177:"en-ZA",11273:"en-TT",2057:"en-GB",1033:"en",12297:"en-ZW",1061:"et",1080:"fo",1124:"fil",1035:"fi",2060:"fr-BE",3084:"fr-CA",1036:"fr",5132:"fr-LU",6156:"fr-MC",4108:"fr-CH",1122:"fy",1110:"gl",1079:"ka",3079:"de-AT",1031:"de",5127:"de-LI",4103:"de-LU",2055:"de-CH",1032:"el",1135:"kl",1095:"gu",1128:"ha",1037:"he",1081:"hi",1038:"hu",1039:"is",1136:"ig",1057:"id",1117:"iu",2141:"iu-Latn",2108:"ga",1076:"xh",1077:"zu",1040:"it",2064:"it-CH",1041:"ja",1099:"kn",1087:"kk",1107:"km",1158:"quc",1159:"rw",1089:"sw",1111:"kok",1042:"ko",1088:"ky",1108:"lo",1062:"lv",1063:"lt",2094:"dsb",1134:"lb",1071:"mk",2110:"ms-BN",1086:"ms",1100:"ml",1082:"mt",1153:"mi",1146:"arn",1102:"mr",1148:"moh",1104:"mn",2128:"mn-CN",1121:"ne",1044:"nb",2068:"nn",1154:"oc",1096:"or",1123:"ps",1045:"pl",1046:"pt",2070:"pt-PT",1094:"pa",1131:"qu-BO",2155:"qu-EC",3179:"qu",1048:"ro",1047:"rm",1049:"ru",9275:"smn",4155:"smj-NO",5179:"smj",3131:"se-FI",1083:"se",2107:"se-SE",8251:"sms",6203:"sma-NO",7227:"sms",1103:"sa",7194:"sr-Cyrl-BA",3098:"sr",6170:"sr-Latn-BA",2074:"sr-Latn",1132:"nso",1074:"tn",1115:"si",1051:"sk",1060:"sl",11274:"es-AR",16394:"es-BO",13322:"es-CL",9226:"es-CO",5130:"es-CR",7178:"es-DO",12298:"es-EC",17418:"es-SV",4106:"es-GT",18442:"es-HN",2058:"es-MX",19466:"es-NI",6154:"es-PA",15370:"es-PY",10250:"es-PE",20490:"es-PR",3082:"es",1034:"es",21514:"es-US",14346:"es-UY",8202:"es-VE",2077:"sv-FI",1053:"sv",1114:"syr",1064:"tg",2143:"tzm",1097:"ta",1092:"tt",1098:"te",1054:"th",1105:"bo",1055:"tr",1090:"tk",1152:"ug",1058:"uk",1070:"hsb",1056:"ur",2115:"uz-Cyrl",1091:"uz",1066:"vi",1106:"cy",1160:"wo",1157:"sah",1144:"ii",1130:"yo"};function it(e,t,n){switch(e){case 0:if(65535===t)return"und";if(n)return n[t];break;case 1:return rt[t];case 3:return at[t]}}const lt={0:"macintosh",1:"x-mac-japanese",2:"x-mac-chinesetrad",3:"x-mac-korean",6:"x-mac-greek",7:"x-mac-cyrillic",9:"x-mac-devanagai",10:"x-mac-gurmukhi",11:"x-mac-gujarati",12:"x-mac-oriya",13:"x-mac-bengali",14:"x-mac-tamil",15:"x-mac-telugu",16:"x-mac-kannada",17:"x-mac-malayalam",18:"x-mac-sinhalese",19:"x-mac-burmese",20:"x-mac-khmer",21:"x-mac-thai",22:"x-mac-lao",23:"x-mac-georgian",24:"x-mac-armenian",25:"x-mac-chinesesimp",26:"x-mac-tibetan",27:"x-mac-mongolian",28:"x-mac-ethiopic",29:"x-mac-ce",30:"x-mac-vietnamese",31:"x-mac-extarabic"},ct={15:"x-mac-icelandic",17:"x-mac-turkish",18:"x-mac-croatian",24:"x-mac-ce",25:"x-mac-ce",26:"x-mac-ce",27:"x-mac-ce",28:"x-mac-ce",30:"x-mac-icelandic",37:"x-mac-romanian",38:"x-mac-ce",39:"x-mac-ce",40:"x-mac-ce",143:"x-mac-inuit",146:"x-mac-gaelic"};function ut(e,t,n){switch(e){case 0:return"utf-16";case 1:return ct[n]||lt[t];case 3:if(1===t||10===t)return"utf-16"}}function pt(e){const t={};for(let n in e)t[e[n]]=parseInt(n);return t}function ht(e,t,n,o,r,s){return new oe.Record("NameRecord",[{name:"platformID",type:"USHORT",value:e},{name:"encodingID",type:"USHORT",value:t},{name:"languageID",type:"USHORT",value:n},{name:"nameID",type:"USHORT",value:o},{name:"length",type:"USHORT",value:r},{name:"offset",type:"USHORT",value:s}])}function ft(e,t){let n=function(e,t){const n=e.length,o=t.length-n+1;e:for(let r=0;r<o;r++)for(;r<o;r++){for(let o=0;o<n;o++)if(t[r+o]!==e[o])continue e;return r}return-1}(e,t);if(n<0){n=t.length;let o=0;const r=e.length;for(;o<r;++o)t.push(e[o])}return n}var dt={parse:function(e,t,n){const o={},r=new pe.Parser(e,t),s=r.parseUShort(),a=r.parseUShort(),i=r.offset+r.parseUShort();for(let t=0;t<a;t++){const t=r.parseUShort(),s=r.parseUShort(),a=r.parseUShort(),l=r.parseUShort(),c=ot[l]||l,u=r.parseUShort(),p=r.parseUShort(),h=it(t,a,n),f=ut(t,s,a);if(void 0!==f&&void 0!==h){let t;if(t="utf-16"===f?P.UTF16(e,i+p,u):P.MACSTRING(e,i+p,u,f),t){let e=o[c];void 0===e&&(e=o[c]={}),e[h]=t}}}return 1===s&&r.parseUShort(),o},make:function(e,t){let n;const o=[],r={},s=pt(ot);for(let t in e){let a=s[t];if(void 0===a&&(a=t),n=parseInt(a),isNaN(n))throw new Error('Name table entry "'+t+'" does not exist, see nameTableNames for complete list.');r[n]=e[t],o.push(n)}const a=pt(rt),i=pt(at),l=[],c=[];for(let e=0;e<o.length;e++){n=o[e];const s=r[n];for(let e in s){const o=s[e];let r=1,u=a[e],p=st[u];const h=ut(r,p,u);let f=A.MACSTRING(o,h);void 0===f&&(r=0,u=t.indexOf(e),u<0&&(u=t.length,t.push(e)),p=4,f=A.UTF16(o));const d=ft(f,c);l.push(ht(r,p,u,n,f.length,d));const g=i[e];if(void 0!==g){const e=A.UTF16(o),t=ft(e,c);l.push(ht(3,1,g,n,e.length,t))}}}l.sort((function(e,t){return e.platformID-t.platformID||e.encodingID-t.encodingID||e.languageID-t.languageID||e.nameID-t.nameID}));const u=new oe.Table("name",[{name:"format",type:"USHORT",value:0},{name:"count",type:"USHORT",value:l.length},{name:"stringOffset",type:"USHORT",value:6+12*l.length}]);for(let e=0;e<l.length;e++)u.fields.push({name:"record_"+e,type:"RECORD",value:l[e]});return u.fields.push({name:"strings",type:"LITERAL",value:c}),u}};const gt=[{begin:0,end:127},{begin:128,end:255},{begin:256,end:383},{begin:384,end:591},{begin:592,end:687},{begin:688,end:767},{begin:768,end:879},{begin:880,end:1023},{begin:11392,end:11519},{begin:1024,end:1279},{begin:1328,end:1423},{begin:1424,end:1535},{begin:42240,end:42559},{begin:1536,end:1791},{begin:1984,end:2047},{begin:2304,end:2431},{begin:2432,end:2559},{begin:2560,end:2687},{begin:2688,end:2815},{begin:2816,end:2943},{begin:2944,end:3071},{begin:3072,end:3199},{begin:3200,end:3327},{begin:3328,end:3455},{begin:3584,end:3711},{begin:3712,end:3839},{begin:4256,end:4351},{begin:6912,end:7039},{begin:4352,end:4607},{begin:7680,end:7935},{begin:7936,end:8191},{begin:8192,end:8303},{begin:8304,end:8351},{begin:8352,end:8399},{begin:8400,end:8447},{begin:8448,end:8527},{begin:8528,end:8591},{begin:8592,end:8703},{begin:8704,end:8959},{begin:8960,end:9215},{begin:9216,end:9279},{begin:9280,end:9311},{begin:9312,end:9471},{begin:9472,end:9599},{begin:9600,end:9631},{begin:9632,end:9727},{begin:9728,end:9983},{begin:9984,end:10175},{begin:12288,end:12351},{begin:12352,end:12447},{begin:12448,end:12543},{begin:12544,end:12591},{begin:12592,end:12687},{begin:43072,end:43135},{begin:12800,end:13055},{begin:13056,end:13311},{begin:44032,end:55215},{begin:55296,end:57343},{begin:67840,end:67871},{begin:19968,end:40959},{begin:57344,end:63743},{begin:12736,end:12783},{begin:64256,end:64335},{begin:64336,end:65023},{begin:65056,end:65071},{begin:65040,end:65055},{begin:65104,end:65135},{begin:65136,end:65279},{begin:65280,end:65519},{begin:65520,end:65535},{begin:3840,end:4095},{begin:1792,end:1871},{begin:1920,end:1983},{begin:3456,end:3583},{begin:4096,end:4255},{begin:4608,end:4991},{begin:5024,end:5119},{begin:5120,end:5759},{begin:5760,end:5791},{begin:5792,end:5887},{begin:6016,end:6143},{begin:6144,end:6319},{begin:10240,end:10495},{begin:40960,end:42127},{begin:5888,end:5919},{begin:66304,end:66351},{begin:66352,end:66383},{begin:66560,end:66639},{begin:118784,end:119039},{begin:119808,end:120831},{begin:1044480,end:1048573},{begin:65024,end:65039},{begin:917504,end:917631},{begin:6400,end:6479},{begin:6480,end:6527},{begin:6528,end:6623},{begin:6656,end:6687},{begin:11264,end:11359},{begin:11568,end:11647},{begin:19904,end:19967},{begin:43008,end:43055},{begin:65536,end:65663},{begin:65856,end:65935},{begin:66432,end:66463},{begin:66464,end:66527},{begin:66640,end:66687},{begin:66688,end:66735},{begin:67584,end:67647},{begin:68096,end:68191},{begin:119552,end:119647},{begin:73728,end:74751},{begin:119648,end:119679},{begin:7040,end:7103},{begin:7168,end:7247},{begin:7248,end:7295},{begin:43136,end:43231},{begin:43264,end:43311},{begin:43312,end:43359},{begin:43520,end:43615},{begin:65936,end:65999},{begin:66e3,end:66047},{begin:66208,end:66271},{begin:127024,end:127135}];var mt={parse:function(e,t){const n={},o=new pe.Parser(e,t);n.version=o.parseUShort(),n.xAvgCharWidth=o.parseShort(),n.usWeightClass=o.parseUShort(),n.usWidthClass=o.parseUShort(),n.fsType=o.parseUShort(),n.ySubscriptXSize=o.parseShort(),n.ySubscriptYSize=o.parseShort(),n.ySubscriptXOffset=o.parseShort(),n.ySubscriptYOffset=o.parseShort(),n.ySuperscriptXSize=o.parseShort(),n.ySuperscriptYSize=o.parseShort(),n.ySuperscriptXOffset=o.parseShort(),n.ySuperscriptYOffset=o.parseShort(),n.yStrikeoutSize=o.parseShort(),n.yStrikeoutPosition=o.parseShort(),n.sFamilyClass=o.parseShort(),n.panose=[];for(let e=0;e<10;e++)n.panose[e]=o.parseByte();return n.ulUnicodeRange1=o.parseULong(),n.ulUnicodeRange2=o.parseULong(),n.ulUnicodeRange3=o.parseULong(),n.ulUnicodeRange4=o.parseULong(),n.achVendID=String.fromCharCode(o.parseByte(),o.parseByte(),o.parseByte(),o.parseByte()),n.fsSelection=o.parseUShort(),n.usFirstCharIndex=o.parseUShort(),n.usLastCharIndex=o.parseUShort(),n.sTypoAscender=o.parseShort(),n.sTypoDescender=o.parseShort(),n.sTypoLineGap=o.parseShort(),n.usWinAscent=o.parseUShort(),n.usWinDescent=o.parseUShort(),n.version>=1&&(n.ulCodePageRange1=o.parseULong(),n.ulCodePageRange2=o.parseULong()),n.version>=2&&(n.sxHeight=o.parseShort(),n.sCapHeight=o.parseShort(),n.usDefaultChar=o.parseUShort(),n.usBreakChar=o.parseUShort(),n.usMaxContent=o.parseUShort()),n},make:function(e){return new oe.Table("OS/2",[{name:"version",type:"USHORT",value:3},{name:"xAvgCharWidth",type:"SHORT",value:0},{name:"usWeightClass",type:"USHORT",value:0},{name:"usWidthClass",type:"USHORT",value:0},{name:"fsType",type:"USHORT",value:0},{name:"ySubscriptXSize",type:"SHORT",value:650},{name:"ySubscriptYSize",type:"SHORT",value:699},{name:"ySubscriptXOffset",type:"SHORT",value:0},{name:"ySubscriptYOffset",type:"SHORT",value:140},{name:"ySuperscriptXSize",type:"SHORT",value:650},{name:"ySuperscriptYSize",type:"SHORT",value:699},{name:"ySuperscriptXOffset",type:"SHORT",value:0},{name:"ySuperscriptYOffset",type:"SHORT",value:479},{name:"yStrikeoutSize",type:"SHORT",value:49},{name:"yStrikeoutPosition",type:"SHORT",value:258},{name:"sFamilyClass",type:"SHORT",value:0},{name:"bFamilyType",type:"BYTE",value:0},{name:"bSerifStyle",type:"BYTE",value:0},{name:"bWeight",type:"BYTE",value:0},{name:"bProportion",type:"BYTE",value:0},{name:"bContrast",type:"BYTE",value:0},{name:"bStrokeVariation",type:"BYTE",value:0},{name:"bArmStyle",type:"BYTE",value:0},{name:"bLetterform",type:"BYTE",value:0},{name:"bMidline",type:"BYTE",value:0},{name:"bXHeight",type:"BYTE",value:0},{name:"ulUnicodeRange1",type:"ULONG",value:0},{name:"ulUnicodeRange2",type:"ULONG",value:0},{name:"ulUnicodeRange3",type:"ULONG",value:0},{name:"ulUnicodeRange4",type:"ULONG",value:0},{name:"achVendID",type:"CHARARRAY",value:"XXXX"},{name:"fsSelection",type:"USHORT",value:0},{name:"usFirstCharIndex",type:"USHORT",value:0},{name:"usLastCharIndex",type:"USHORT",value:0},{name:"sTypoAscender",type:"SHORT",value:0},{name:"sTypoDescender",type:"SHORT",value:0},{name:"sTypoLineGap",type:"SHORT",value:0},{name:"usWinAscent",type:"USHORT",value:0},{name:"usWinDescent",type:"USHORT",value:0},{name:"ulCodePageRange1",type:"ULONG",value:0},{name:"ulCodePageRange2",type:"ULONG",value:0},{name:"sxHeight",type:"SHORT",value:0},{name:"sCapHeight",type:"SHORT",value:0},{name:"usDefaultChar",type:"USHORT",value:0},{name:"usBreakChar",type:"USHORT",value:0},{name:"usMaxContext",type:"USHORT",value:0}],e)},unicodeRanges:gt,getUnicodeRange:function(e){for(let t=0;t<gt.length;t+=1){const n=gt[t];if(e>=n.begin&&e<n.end)return t}return-1}};var yt={parse:function(e,t){const n={},o=new pe.Parser(e,t);switch(n.version=o.parseVersion(),n.italicAngle=o.parseFixed(),n.underlinePosition=o.parseShort(),n.underlineThickness=o.parseShort(),n.isFixedPitch=o.parseULong(),n.minMemType42=o.parseULong(),n.maxMemType42=o.parseULong(),n.minMemType1=o.parseULong(),n.maxMemType1=o.parseULong(),n.version){case 1:n.names=ye.slice();break;case 2:n.numberOfGlyphs=o.parseUShort(),n.glyphNameIndex=new Array(n.numberOfGlyphs);for(let e=0;e<n.numberOfGlyphs;e++)n.glyphNameIndex[e]=o.parseUShort();n.names=[];for(let e=0;e<n.numberOfGlyphs;e++)if(n.glyphNameIndex[e]>=ye.length){const e=o.parseChar();n.names.push(o.parseString(e))}break;case 2.5:n.numberOfGlyphs=o.parseUShort(),n.offset=new Array(n.numberOfGlyphs);for(let e=0;e<n.numberOfGlyphs;e++)n.offset[e]=o.parseChar()}return n},make:function(){return new oe.Table("post",[{name:"version",type:"FIXED",value:196608},{name:"italicAngle",type:"FIXED",value:0},{name:"underlinePosition",type:"FWORD",value:0},{name:"underlineThickness",type:"FWORD",value:0},{name:"isFixedPitch",type:"ULONG",value:0},{name:"minMemType42",type:"ULONG",value:0},{name:"maxMemType42",type:"ULONG",value:0},{name:"minMemType1",type:"ULONG",value:0},{name:"maxMemType1",type:"ULONG",value:0}])}};const vt=new Array(9);vt[1]=function(){const e=this.offset+this.relativeOffset,t=this.parseUShort();return 1===t?{substFormat:1,coverage:this.parsePointer(ce.coverage),deltaGlyphId:this.parseUShort()}:2===t?{substFormat:2,coverage:this.parsePointer(ce.coverage),substitute:this.parseOffset16List()}:void G.assert(!1,"0x"+e.toString(16)+": lookup type 1 format must be 1 or 2.")},vt[2]=function(){const e=this.parseUShort();return G.argument(1===e,"GSUB Multiple Substitution Subtable identifier-format must be 1"),{substFormat:e,coverage:this.parsePointer(ce.coverage),sequences:this.parseListOfLists()}},vt[3]=function(){const e=this.parseUShort();return G.argument(1===e,"GSUB Alternate Substitution Subtable identifier-format must be 1"),{substFormat:e,coverage:this.parsePointer(ce.coverage),alternateSets:this.parseListOfLists()}},vt[4]=function(){const e=this.parseUShort();return G.argument(1===e,"GSUB ligature table identifier-format must be 1"),{substFormat:e,coverage:this.parsePointer(ce.coverage),ligatureSets:this.parseListOfLists((function(){return{ligGlyph:this.parseUShort(),components:this.parseUShortList(this.parseUShort()-1)}}))}};const bt={sequenceIndex:ce.uShort,lookupListIndex:ce.uShort};vt[5]=function(){const e=this.offset+this.relativeOffset,t=this.parseUShort();if(1===t)return{substFormat:t,coverage:this.parsePointer(ce.coverage),ruleSets:this.parseListOfLists((function(){const e=this.parseUShort(),t=this.parseUShort();return{input:this.parseUShortList(e-1),lookupRecords:this.parseRecordList(t,bt)}}))};if(2===t)return{substFormat:t,coverage:this.parsePointer(ce.coverage),classDef:this.parsePointer(ce.classDef),classSets:this.parseListOfLists((function(){const e=this.parseUShort(),t=this.parseUShort();return{classes:this.parseUShortList(e-1),lookupRecords:this.parseRecordList(t,bt)}}))};if(3===t){const e=this.parseUShort(),n=this.parseUShort();return{substFormat:t,coverages:this.parseList(e,ce.pointer(ce.coverage)),lookupRecords:this.parseRecordList(n,bt)}}G.assert(!1,"0x"+e.toString(16)+": lookup type 5 format must be 1, 2 or 3.")},vt[6]=function(){const e=this.offset+this.relativeOffset,t=this.parseUShort();return 1===t?{substFormat:1,coverage:this.parsePointer(ce.coverage),chainRuleSets:this.parseListOfLists((function(){return{backtrack:this.parseUShortList(),input:this.parseUShortList(this.parseShort()-1),lookahead:this.parseUShortList(),lookupRecords:this.parseRecordList(bt)}}))}:2===t?{substFormat:2,coverage:this.parsePointer(ce.coverage),backtrackClassDef:this.parsePointer(ce.classDef),inputClassDef:this.parsePointer(ce.classDef),lookaheadClassDef:this.parsePointer(ce.classDef),chainClassSet:this.parseListOfLists((function(){return{backtrack:this.parseUShortList(),input:this.parseUShortList(this.parseShort()-1),lookahead:this.parseUShortList(),lookupRecords:this.parseRecordList(bt)}}))}:3===t?{substFormat:3,backtrackCoverage:this.parseList(ce.pointer(ce.coverage)),inputCoverage:this.parseList(ce.pointer(ce.coverage)),lookaheadCoverage:this.parseList(ce.pointer(ce.coverage)),lookupRecords:this.parseRecordList(bt)}:void G.assert(!1,"0x"+e.toString(16)+": lookup type 6 format must be 1, 2 or 3.")},vt[7]=function(){const e=this.parseUShort();G.argument(1===e,"GSUB Extension Substitution subtable identifier-format must be 1");const t=this.parseUShort(),n=new ce(this.data,this.offset+this.parseULong());return{substFormat:1,lookupType:t,extension:vt[t].call(n)}},vt[8]=function(){const e=this.parseUShort();return G.argument(1===e,"GSUB Reverse Chaining Contextual Single Substitution Subtable identifier-format must be 1"),{substFormat:e,coverage:this.parsePointer(ce.coverage),backtrackCoverage:this.parseList(ce.pointer(ce.coverage)),lookaheadCoverage:this.parseList(ce.pointer(ce.coverage)),substitutes:this.parseUShortList()}};const xt=new Array(9);xt[1]=function(e){return 1===e.substFormat?new oe.Table("substitutionTable",[{name:"substFormat",type:"USHORT",value:1},{name:"coverage",type:"TABLE",value:new oe.Coverage(e.coverage)},{name:"deltaGlyphID",type:"USHORT",value:e.deltaGlyphId}]):new oe.Table("substitutionTable",[{name:"substFormat",type:"USHORT",value:2},{name:"coverage",type:"TABLE",value:new oe.Coverage(e.coverage)}].concat(oe.ushortList("substitute",e.substitute)))},xt[3]=function(e){return G.assert(1===e.substFormat,"Lookup type 3 substFormat must be 1."),new oe.Table("substitutionTable",[{name:"substFormat",type:"USHORT",value:1},{name:"coverage",type:"TABLE",value:new oe.Coverage(e.coverage)}].concat(oe.tableList("altSet",e.alternateSets,(function(e){return new oe.Table("alternateSetTable",oe.ushortList("alternate",e))}))))},xt[4]=function(e){return G.assert(1===e.substFormat,"Lookup type 4 substFormat must be 1."),new oe.Table("substitutionTable",[{name:"substFormat",type:"USHORT",value:1},{name:"coverage",type:"TABLE",value:new oe.Coverage(e.coverage)}].concat(oe.tableList("ligSet",e.ligatureSets,(function(e){return new oe.Table("ligatureSetTable",oe.tableList("ligature",e,(function(e){return new oe.Table("ligatureTable",[{name:"ligGlyph",type:"USHORT",value:e.ligGlyph}].concat(oe.ushortList("component",e.components,e.components.length+1)))})))}))))};var St={parse:function(e,t){const n=new ce(e,t=t||0),o=n.parseVersion();return G.argument(1===o,"Unsupported GSUB table version."),{version:o,scripts:n.parseScriptList(),features:n.parseFeatureList(),lookups:n.parseLookupList(vt)}},make:function(e){return new oe.Table("GSUB",[{name:"version",type:"ULONG",value:65536},{name:"scripts",type:"TABLE",value:new oe.ScriptList(e.scripts)},{name:"features",type:"TABLE",value:new oe.FeatureList(e.features)},{name:"lookups",type:"TABLE",value:new oe.LookupList(e.lookups,xt)}])}};var Ut={parse:function(e,t){const n=new pe.Parser(e,t),o=n.parseULong();G.argument(1===o,"Unsupported META table version."),n.parseULong(),n.parseULong();const r=n.parseULong(),s={};for(let o=0;o<r;o++){const o=n.parseTag(),r=n.parseULong(),a=n.parseULong(),i=P.UTF8(e,t+r,a);s[o]=i}return s},make:function(e){const t=Object.keys(e).length;let n="";const o=16+12*t,r=new oe.Table("meta",[{name:"version",type:"ULONG",value:1},{name:"flags",type:"ULONG",value:0},{name:"offset",type:"ULONG",value:o},{name:"numTags",type:"ULONG",value:t}]);for(let t in e){const s=n.length;n+=e[t],r.fields.push({name:"tag "+t,type:"TAG",value:t}),r.fields.push({name:"offset "+t,type:"ULONG",value:o+s}),r.fields.push({name:"length "+t,type:"ULONG",value:e[t].length})}return r.fields.push({name:"stringPool",type:"CHARARRAY",value:n}),r}};function Tt(e){return Math.log(e)/Math.log(2)|0}function wt(e){for(;e.length%4!=0;)e.push(0);let t=0;for(let n=0;n<e.length;n+=4)t+=(e[n]<<24)+(e[n+1]<<16)+(e[n+2]<<8)+e[n+3];return t%=Math.pow(2,32),t}function Et(e,t,n,o){return new oe.Record("Table Record",[{name:"tag",type:"TAG",value:void 0!==e?e:""},{name:"checkSum",type:"ULONG",value:void 0!==t?t:0},{name:"offset",type:"ULONG",value:void 0!==n?n:0},{name:"length",type:"ULONG",value:void 0!==o?o:0}])}function Ot(e){const t=new oe.Table("sfnt",[{name:"version",type:"TAG",value:"OTTO"},{name:"numTables",type:"USHORT",value:0},{name:"searchRange",type:"USHORT",value:0},{name:"entrySelector",type:"USHORT",value:0},{name:"rangeShift",type:"USHORT",value:0}]);t.tables=e,t.numTables=e.length;const n=Math.pow(2,Tt(t.numTables));t.searchRange=16*n,t.entrySelector=Tt(n),t.rangeShift=16*t.numTables-t.searchRange;const o=[],r=[];let s=t.sizeOf()+Et().sizeOf()*t.numTables;for(;s%4!=0;)s+=1,r.push({name:"padding",type:"BYTE",value:0});for(let t=0;t<e.length;t+=1){const n=e[t];G.argument(4===n.tableName.length,"Table name"+n.tableName+" is invalid.");const a=n.sizeOf(),i=Et(n.tableName,wt(n.encode()),s,a);for(o.push({name:i.tag+" Table Record",type:"RECORD",value:i}),r.push({name:n.tableName+" table",type:"RECORD",value:n}),s+=a,G.argument(!isNaN(s),"Something went wrong calculating the offset.");s%4!=0;)s+=1,r.push({name:"padding",type:"BYTE",value:0})}return o.sort((function(e,t){return e.value.tag>t.value.tag?1:-1})),t.fields=t.fields.concat(o),t.fields=t.fields.concat(r),t}function Lt(e,t,n){for(let n=0;n<t.length;n+=1){const o=e.charToGlyphIndex(t[n]);if(o>0){return e.glyphs.get(o).getMetrics()}}return n}function kt(e){let t=0;for(let n=0;n<e.length;n+=1)t+=e[n];return t/e.length}var Rt={make:Ot,fontToTable:function(e){const t=[],n=[],o=[],r=[],s=[],a=[],i=[];let l,c=0,u=0,p=0,h=0,f=0;for(let d=0;d<e.glyphs.length;d+=1){const g=e.glyphs.get(d),m=0|g.unicode;if(isNaN(g.advanceWidth))throw new Error("Glyph "+g.name+" ("+d+"): advanceWidth is not a number.");(l>m||void 0===l)&&m>0&&(l=m),c<m&&(c=m);const y=mt.getUnicodeRange(m);if(y<32)u|=1<<y;else if(y<64)p|=1<<y-32;else if(y<96)h|=1<<y-64;else{if(!(y<123))throw new Error("Unicode ranges bits > 123 are reserved for internal usage");f|=1<<y-96}if(".notdef"===g.name)continue;const v=g.getMetrics();t.push(v.xMin),n.push(v.yMin),o.push(v.xMax),r.push(v.yMax),a.push(v.leftSideBearing),i.push(v.rightSideBearing),s.push(g.advanceWidth)}const d={xMin:Math.min.apply(null,t),yMin:Math.min.apply(null,n),xMax:Math.max.apply(null,o),yMax:Math.max.apply(null,r),advanceWidthMax:Math.max.apply(null,s),advanceWidthAvg:kt(s),minLeftSideBearing:Math.min.apply(null,a),maxLeftSideBearing:Math.max.apply(null,a),minRightSideBearing:Math.min.apply(null,i)};d.ascender=e.ascender,d.descender=e.descender;const g=Ke.make({flags:3,unitsPerEm:e.unitsPerEm,xMin:d.xMin,yMin:d.yMin,xMax:d.xMax,yMax:d.yMax,lowestRecPPEM:3,createdTimestamp:e.createdTimestamp}),m=Je.make({ascender:d.ascender,descender:d.descender,advanceWidthMax:d.advanceWidthMax,minLeftSideBearing:d.minLeftSideBearing,minRightSideBearing:d.minRightSideBearing,xMaxExtent:d.maxLeftSideBearing+(d.xMax-d.xMin),numberOfHMetrics:e.glyphs.length}),y=nt.make(e.glyphs.length),v=mt.make({xAvgCharWidth:Math.round(d.advanceWidthAvg),usWeightClass:e.tables.os2.usWeightClass,usWidthClass:e.tables.os2.usWidthClass,usFirstCharIndex:l,usLastCharIndex:c,ulUnicodeRange1:u,ulUnicodeRange2:p,ulUnicodeRange3:h,ulUnicodeRange4:f,fsSelection:e.tables.os2.fsSelection,sTypoAscender:d.ascender,sTypoDescender:d.descender,sTypoLineGap:0,usWinAscent:d.yMax,usWinDescent:Math.abs(d.yMin),ulCodePageRange1:1,sxHeight:Lt(e,"xyvw",{yMax:Math.round(d.ascender/2)}).yMax,sCapHeight:Lt(e,"HIKLEFJMNTZBDPRAGOQSUVWXY",d).yMax,usDefaultChar:e.hasChar(" ")?32:0,usBreakChar:e.hasChar(" ")?32:0}),b=et.make(e.glyphs),x=fe.make(e.glyphs),S=e.getEnglishName("fontFamily"),U=e.getEnglishName("fontSubfamily"),T=S+" "+U;let w=e.getEnglishName("postScriptName");w||(w=S.replace(/\s/g,"")+"-"+U);const E={};for(let t in e.names)E[t]=e.names[t];E.uniqueID||(E.uniqueID={en:e.getEnglishName("manufacturer")+":"+T}),E.postScriptName||(E.postScriptName={en:w}),E.preferredFamily||(E.preferredFamily=e.names.fontFamily),E.preferredSubfamily||(E.preferredSubfamily=e.names.fontSubfamily);const O=[],L=dt.make(E,O),k=O.length>0?tt.make(O):void 0,R=yt.make(),D=Qe.make(e.glyphs,{version:e.getEnglishName("version"),fullName:T,familyName:S,weightName:U,postScriptName:w,unitsPerEm:e.unitsPerEm,fontBBox:[0,d.yMin,d.ascender,d.advanceWidthMax]}),C=e.metas&&Object.keys(e.metas).length>0?Ut.make(e.metas):void 0,M=[g,m,y,v,L,x,R,D,b];k&&M.push(k),e.tables.gsub&&M.push(St.make(e.tables.gsub)),C&&M.push(C);const I=Ot(M),B=wt(I.encode()),N=I.fields;let G=!1;for(let e=0;e<N.length;e+=1)if("head table"===N[e].name){N[e].value.checkSumAdjustment=2981146554-B,G=!0;break}if(!G)throw new Error("Could not find head table with checkSum to adjust.");return I},computeCheckSum:wt};function Dt(e,t){let n=0,o=e.length-1;for(;n<=o;){const r=n+o>>>1,s=e[r].tag;if(s===t)return r;s<t?n=r+1:o=r-1}return-n-1}function Ct(e,t){this.font=e,this.tableName=t}function Mt(e){Ct.call(this,e,"gsub")}function It(e,t){const n=e.length;if(n!==t.length)return!1;for(let o=0;o<n;o++)if(e[o]!==t[o])return!1;return!0}function Bt(e,t,n){const o=e.subtables;for(let e=0;e<o.length;e++){const n=o[e];if(n.substFormat===t)return n}if(n)return o.push(n),n}function Nt(e){const t=new ArrayBuffer(e.length),n=new Uint8Array(t);for(let t=0;t<e.length;++t)n[t]=e[t];return t}function Gt(e,t){if(!e)throw t}let Pt,At,Ft,Ht;function zt(e){this.font=e,this._fpgmState=this._prepState=void 0,this._errorState=0}function Wt(e){return e}function _t(e){return Math.sign(e)*Math.round(Math.abs(e))}function qt(e){return Math.sign(e)*Math.round(Math.abs(2*e))/2}function Xt(e){return Math.sign(e)*(Math.round(Math.abs(e)+.5)-.5)}function Vt(e){return Math.sign(e)*Math.ceil(Math.abs(e))}function jt(e){return Math.sign(e)*Math.floor(Math.abs(e))}Ct.prototype={searchTag:Dt,binSearch:function(e,t){let n=0,o=e.length-1;for(;n<=o;){const r=n+o>>>1,s=e[r];if(s===t)return r;s<t?n=r+1:o=r-1}return-n-1},getTable:function(e){let t=this.font.tables[this.tableName];return!t&&e&&(t=this.font.tables[this.tableName]=this.createDefaultTable()),t},getScriptNames:function(){let e=this.getTable();return e?e.scripts.map((function(e){return e.tag})):[]},getDefaultScriptName:function(){let e=this.getTable();if(!e)return;let t=!1;for(let n=0;n<e.scripts.length;n++){const o=e.scripts[n].tag;if("DFLT"===o)return o;"latn"===o&&(t=!0)}return t?"latn":void 0},getScriptTable:function(e,t){const n=this.getTable(t);if(n){e=e||"DFLT";const o=n.scripts,r=Dt(n.scripts,e);if(r>=0)return o[r].script;if(t){const t={tag:e,script:{defaultLangSys:{reserved:0,reqFeatureIndex:65535,featureIndexes:[]},langSysRecords:[]}};return o.splice(-1-r,0,t),t.script}}},getLangSysTable:function(e,t,n){const o=this.getScriptTable(e,n);if(o){if(!t||"dflt"===t||"DFLT"===t)return o.defaultLangSys;const e=Dt(o.langSysRecords,t);if(e>=0)return o.langSysRecords[e].langSys;if(n){const n={tag:t,langSys:{reserved:0,reqFeatureIndex:65535,featureIndexes:[]}};return o.langSysRecords.splice(-1-e,0,n),n.langSys}}},getFeatureTable:function(e,t,n,o){const r=this.getLangSysTable(e,t,o);if(r){let e;const t=r.featureIndexes,s=this.font.tables[this.tableName].features;for(let o=0;o<t.length;o++)if(e=s[t[o]],e.tag===n)return e.feature;if(o){const o=s.length;return G.assert(0===o||n>=s[o-1].tag,"Features must be added in alphabetical order."),e={tag:n,feature:{params:0,lookupListIndexes:[]}},s.push(e),t.push(o),e.feature}}},getLookupTables:function(e,t,n,o,r){const s=this.getFeatureTable(e,t,n,r),a=[];if(s){let e;const t=s.lookupListIndexes,n=this.font.tables[this.tableName].lookups;for(let r=0;r<t.length;r++)e=n[t[r]],e.lookupType===o&&a.push(e);if(0===a.length&&r){e={lookupType:o,lookupFlag:0,subtables:[],markFilteringSet:void 0};const r=n.length;return n.push(e),t.push(r),[e]}}return a},expandCoverage:function(e){if(1===e.format)return e.glyphs;{const t=[],n=e.ranges;for(let e=0;e<n.length;e++){const o=n[e],r=o.start,s=o.end;for(let e=r;e<=s;e++)t.push(e)}return t}}},Mt.prototype=Ct.prototype,Mt.prototype.createDefaultTable=function(){return{version:1,scripts:[{tag:"DFLT",script:{defaultLangSys:{reserved:0,reqFeatureIndex:65535,featureIndexes:[]},langSysRecords:[]}}],features:[],lookups:[]}},Mt.prototype.getSingle=function(e,t,n){const o=[],r=this.getLookupTables(t,n,e,1);for(let e=0;e<r.length;e++){const t=r[e].subtables;for(let e=0;e<t.length;e++){const n=t[e],r=this.expandCoverage(n.coverage);let s;if(1===n.substFormat){const e=n.deltaGlyphId;for(s=0;s<r.length;s++){const t=r[s];o.push({sub:t,by:t+e})}}else{const e=n.substitute;for(s=0;s<r.length;s++)o.push({sub:r[s],by:e[s]})}}}return o},Mt.prototype.getAlternates=function(e,t,n){const o=[],r=this.getLookupTables(t,n,e,3);for(let e=0;e<r.length;e++){const t=r[e].subtables;for(let e=0;e<t.length;e++){const n=t[e],r=this.expandCoverage(n.coverage),s=n.alternateSets;for(let e=0;e<r.length;e++)o.push({sub:r[e],by:s[e]})}}return o},Mt.prototype.getLigatures=function(e,t,n){const o=[],r=this.getLookupTables(t,n,e,4);for(let e=0;e<r.length;e++){const t=r[e].subtables;for(let e=0;e<t.length;e++){const n=t[e],r=this.expandCoverage(n.coverage),s=n.ligatureSets;for(let e=0;e<r.length;e++){const t=r[e],n=s[e];for(let e=0;e<n.length;e++){const r=n[e];o.push({sub:[t].concat(r.components),by:r.ligGlyph})}}}}return o},Mt.prototype.addSingle=function(e,t,n,o){const r=Bt(this.getLookupTables(n,o,e,1,!0)[0],2,{substFormat:2,coverage:{format:1,glyphs:[]},substitute:[]});G.assert(1===r.coverage.format,"Ligature: unable to modify coverage table format "+r.coverage.format);const s=t.sub;let a=this.binSearch(r.coverage.glyphs,s);a<0&&(a=-1-a,r.coverage.glyphs.splice(a,0,s),r.substitute.splice(a,0,0)),r.substitute[a]=t.by},Mt.prototype.addAlternate=function(e,t,n,o){const r=Bt(this.getLookupTables(n,o,e,3,!0)[0],1,{substFormat:1,coverage:{format:1,glyphs:[]},alternateSets:[]});G.assert(1===r.coverage.format,"Ligature: unable to modify coverage table format "+r.coverage.format);const s=t.sub;let a=this.binSearch(r.coverage.glyphs,s);a<0&&(a=-1-a,r.coverage.glyphs.splice(a,0,s),r.alternateSets.splice(a,0,0)),r.alternateSets[a]=t.by},Mt.prototype.addLigature=function(e,t,n,o){const r=this.getLookupTables(n,o,e,4,!0)[0];let s=r.subtables[0];s||(s={substFormat:1,coverage:{format:1,glyphs:[]},ligatureSets:[]},r.subtables[0]=s),G.assert(1===s.coverage.format,"Ligature: unable to modify coverage table format "+s.coverage.format);const a=t.sub[0],i=t.sub.slice(1),l={ligGlyph:t.by,components:i};let c=this.binSearch(s.coverage.glyphs,a);if(c>=0){const e=s.ligatureSets[c];for(let t=0;t<e.length;t++)if(It(e[t].components,i))return;e.push(l)}else c=-1-c,s.coverage.glyphs.splice(c,0,a),s.ligatureSets.splice(c,0,[l])},Mt.prototype.getFeature=function(e,t,n){if(/ss\d\d/.test(e))return this.getSingle(e,t,n);switch(e){case"aalt":case"salt":return this.getSingle(e,t,n).concat(this.getAlternates(e,t,n));case"dlig":case"liga":case"rlig":return this.getLigatures(e,t,n)}},Mt.prototype.add=function(e,t,n,o){if(/ss\d\d/.test(e))return this.addSingle(e,t,n,o);switch(e){case"aalt":case"salt":return"number"==typeof t.by?this.addSingle(e,t,n,o):this.addAlternate(e,t,n,o);case"dlig":case"liga":case"rlig":return this.addLigature(e,t,n,o)}};const Yt=function(e){const t=this.srPeriod;let n=this.srPhase;let o=1;return e<0&&(e=-e,o=-1),e+=this.srThreshold-n,e=Math.trunc(e/t)*t,e+=n,o>0&&e<0?n:o<0&&e>0?-n:e*o},Zt={x:1,y:0,axis:"x",distance:function(e,t,n,o){return(n?e.xo:e.x)-(o?t.xo:t.x)},interpolate:function(e,t,n,o){let r,s,a,i,l,c,u;if(!o||o===this)return r=e.xo-t.xo,s=e.xo-n.xo,l=t.x-t.xo,c=n.x-n.xo,a=Math.abs(r),i=Math.abs(s),u=a+i,0===u?void(e.x=e.xo+(l+c)/2):void(e.x=e.xo+(l*i+c*a)/u);r=o.distance(e,t,!0,!0),s=o.distance(e,n,!0,!0),l=o.distance(t,t,!1,!0),c=o.distance(n,n,!1,!0),a=Math.abs(r),i=Math.abs(s),u=a+i,0!==u?Zt.setRelative(e,e,(l*i+c*a)/u,o,!0):Zt.setRelative(e,e,(l+c)/2,o,!0)},normalSlope:Number.NEGATIVE_INFINITY,setRelative:function(e,t,n,o,r){if(!o||o===this)return void(e.x=(r?t.xo:t.x)+n);const s=r?t.xo:t.x,a=r?t.yo:t.y,i=s+n*o.x,l=a+n*o.y;e.x=i+(e.y-l)/o.normalSlope},slope:0,touch:function(e){e.xTouched=!0},touched:function(e){return e.xTouched},untouch:function(e){e.xTouched=!1}},$t={x:0,y:1,axis:"y",distance:function(e,t,n,o){return(n?e.yo:e.y)-(o?t.yo:t.y)},interpolate:function(e,t,n,o){let r,s,a,i,l,c,u;if(!o||o===this)return r=e.yo-t.yo,s=e.yo-n.yo,l=t.y-t.yo,c=n.y-n.yo,a=Math.abs(r),i=Math.abs(s),u=a+i,0===u?void(e.y=e.yo+(l+c)/2):void(e.y=e.yo+(l*i+c*a)/u);r=o.distance(e,t,!0,!0),s=o.distance(e,n,!0,!0),l=o.distance(t,t,!1,!0),c=o.distance(n,n,!1,!0),a=Math.abs(r),i=Math.abs(s),u=a+i,0!==u?$t.setRelative(e,e,(l*i+c*a)/u,o,!0):$t.setRelative(e,e,(l+c)/2,o,!0)},normalSlope:0,setRelative:function(e,t,n,o,r){if(!o||o===this)return void(e.y=(r?t.yo:t.y)+n);const s=r?t.xo:t.x,a=r?t.yo:t.y,i=s+n*o.x,l=a+n*o.y;e.y=l+o.normalSlope*(e.x-i)},slope:Number.POSITIVE_INFINITY,touch:function(e){e.yTouched=!0},touched:function(e){return e.yTouched},untouch:function(e){e.yTouched=!1}};function Qt(e,t){this.x=e,this.y=t,this.axis=void 0,this.slope=t/e,this.normalSlope=-e/t,Object.freeze(this)}function Kt(e,t){const n=Math.sqrt(e*e+t*t);return t/=n,1===(e/=n)&&0===t?Zt:0===e&&1===t?$t:new Qt(e,t)}function Jt(e,t,n,o){this.x=this.xo=Math.round(64*e)/64,this.y=this.yo=Math.round(64*t)/64,this.lastPointOfContour=n,this.onCurve=o,this.prevPointOnContour=void 0,this.nextPointOnContour=void 0,this.xTouched=!1,this.yTouched=!1,Object.preventExtensions(this)}Object.freeze(Zt),Object.freeze($t),Qt.prototype.distance=function(e,t,n,o){return this.x*Zt.distance(e,t,n,o)+this.y*$t.distance(e,t,n,o)},Qt.prototype.interpolate=function(e,t,n,o){let r,s,a,i,l,c,u;a=o.distance(e,t,!0,!0),i=o.distance(e,n,!0,!0),r=o.distance(t,t,!1,!0),s=o.distance(n,n,!1,!0),l=Math.abs(a),c=Math.abs(i),u=l+c,0!==u?this.setRelative(e,e,(r*c+s*l)/u,o,!0):this.setRelative(e,e,(r+s)/2,o,!0)},Qt.prototype.setRelative=function(e,t,n,o,r){o=o||this;const s=r?t.xo:t.x,a=r?t.yo:t.y,i=s+n*o.x,l=a+n*o.y,c=o.normalSlope,u=this.slope,p=e.x,h=e.y;e.x=(u*p-c*i+l-h)/(u-c),e.y=u*(e.x-p)+h},Qt.prototype.touch=function(e){e.xTouched=!0,e.yTouched=!0},Jt.prototype.nextTouched=function(e){let t=this.nextPointOnContour;for(;!e.touched(t)&&t!==this;)t=t.nextPointOnContour;return t},Jt.prototype.prevTouched=function(e){let t=this.prevPointOnContour;for(;!e.touched(t)&&t!==this;)t=t.prevPointOnContour;return t};const en=Object.freeze(new Jt(0,0)),tn={cvCutIn:17/16,deltaBase:9,deltaShift:.125,loop:1,minDis:1,autoFlip:!0};function nn(e,t){switch(this.env=e,this.stack=[],this.prog=t,e){case"glyf":this.zp0=this.zp1=this.zp2=1,this.rp0=this.rp1=this.rp2=0;case"prep":this.fv=this.pv=this.dpv=Zt,this.round=_t}}function on(e){const t=e.tZone=new Array(e.gZone.length);for(let e=0;e<t.length;e++)t[e]=new Jt(0,0)}function rn(e,t){const n=e.prog;let o,r=e.ip,s=1;do{if(o=n[++r],88===o)s++;else if(89===o)s--;else if(64===o)r+=n[r+1]+1;else if(65===o)r+=2*n[r+1]+1;else if(o>=176&&o<=183)r+=o-176+1;else if(o>=184&&o<=191)r+=2*(o-184+1);else if(t&&1===s&&27===o)break}while(s>0);e.ip=r}function sn(e,t){exports.DEBUG&&console.log(t.step,"SVTCA["+e.axis+"]"),t.fv=t.pv=t.dpv=e}function an(e,t){exports.DEBUG&&console.log(t.step,"SPVTCA["+e.axis+"]"),t.pv=t.dpv=e}function ln(e,t){exports.DEBUG&&console.log(t.step,"SFVTCA["+e.axis+"]"),t.fv=e}function cn(e,t){const n=t.stack,o=n.pop(),r=n.pop(),s=t.z2[o],a=t.z1[r];let i,l;exports.DEBUG&&console.log("SPVTL["+e+"]",o,r),e?(i=s.y-a.y,l=a.x-s.x):(i=a.x-s.x,l=a.y-s.y),t.pv=t.dpv=Kt(i,l)}function un(e,t){const n=t.stack,o=n.pop(),r=n.pop(),s=t.z2[o],a=t.z1[r];let i,l;exports.DEBUG&&console.log("SFVTL["+e+"]",o,r),e?(i=s.y-a.y,l=a.x-s.x):(i=a.x-s.x,l=a.y-s.y),t.fv=Kt(i,l)}function pn(e){exports.DEBUG&&console.log(e.step,"POP[]"),e.stack.pop()}function hn(e,t){const n=t.stack.pop(),o=t.z0[n],r=t.fv,s=t.pv;exports.DEBUG&&console.log(t.step,"MDAP["+e+"]",n);let a=s.distance(o,en);e&&(a=t.round(a)),r.setRelative(o,en,a,s),r.touch(o),t.rp0=t.rp1=n}function fn(e,t){const n=t.z2,o=n.length-2;let r,s,a;exports.DEBUG&&console.log(t.step,"IUP["+e.axis+"]");for(let t=0;t<o;t++)r=n[t],e.touched(r)||(s=r.prevTouched(e),s!==r&&(a=r.nextTouched(e),s===a&&e.setRelative(r,r,e.distance(s,s,!1,!0),e,!0),e.interpolate(r,s,a,e)))}function dn(e,t){const n=t.stack,o=e?t.rp1:t.rp2,r=(e?t.z0:t.z1)[o],s=t.fv,a=t.pv;let i=t.loop;const l=t.z2;for(;i--;){const o=n.pop(),c=l[o],u=a.distance(r,r,!1,!0);s.setRelative(c,c,u,a),s.touch(c),exports.DEBUG&&console.log(t.step,(t.loop>1?"loop "+(t.loop-i)+": ":"")+"SHP["+(e?"rp1":"rp2")+"]",o)}t.loop=1}function gn(e,t){const n=t.stack,o=e?t.rp1:t.rp2,r=(e?t.z0:t.z1)[o],s=t.fv,a=t.pv,i=n.pop(),l=t.z2[t.contours[i]];let c=l;exports.DEBUG&&console.log(t.step,"SHC["+e+"]",i);const u=a.distance(r,r,!1,!0);do{c!==r&&s.setRelative(c,c,u,a),c=c.nextPointOnContour}while(c!==l)}function mn(e,t){const n=t.stack,o=e?t.rp1:t.rp2,r=(e?t.z0:t.z1)[o],s=t.fv,a=t.pv,i=n.pop();let l,c;switch(exports.DEBUG&&console.log(t.step,"SHZ["+e+"]",i),i){case 0:l=t.tZone;break;case 1:l=t.gZone;break;default:throw new Error("Invalid zone")}const u=a.distance(r,r,!1,!0),p=l.length-2;for(let e=0;e<p;e++)c=l[e],c!==r&&s.setRelative(c,c,u,a)}function yn(e,t){const n=t.stack,o=n.pop()/64,r=n.pop(),s=t.z1[r],a=t.z0[t.rp0],i=t.fv,l=t.pv;i.setRelative(s,a,o,l),i.touch(s),exports.DEBUG&&console.log(t.step,"MSIRP["+e+"]",o,r),t.rp1=t.rp0,t.rp2=r,e&&(t.rp0=r)}function vn(e,t){const n=t.stack,o=n.pop(),r=n.pop(),s=t.z0[r],a=t.fv,i=t.pv;let l=t.cvt[o];e&&(l=t.round(l)),exports.DEBUG&&console.log(t.step,"MIAP["+e+"]",o,"(",l,")",r),a.setRelative(s,en,l,i),0===t.zp0&&(s.xo=s.x,s.yo=s.y),a.touch(s),t.rp0=t.rp1=r}function bn(e,t){const n=t.stack,o=n.pop(),r=t.z2[o];exports.DEBUG&&console.log(t.step,"GC["+e+"]",o),n.push(64*t.dpv.distance(r,en,e,!1))}function xn(e,t){const n=t.stack,o=n.pop(),r=n.pop(),s=t.z1[o],a=t.z0[r],i=t.dpv.distance(a,s,e,e);exports.DEBUG&&console.log(t.step,"MD["+e+"]",o,r,"->",i),t.stack.push(Math.round(64*i))}function Sn(e,t){const n=t.stack,o=n.pop(),r=t.fv,s=t.pv,a=t.ppem,i=t.deltaBase+16*(e-1),l=t.deltaShift,c=t.z0;exports.DEBUG&&console.log(t.step,"DELTAP["+e+"]",o,n);for(let e=0;e<o;e++){const e=n.pop(),o=n.pop();if(i+((240&o)>>4)!==a)continue;let u=(15&o)-8;u>=0&&u++,exports.DEBUG&&console.log(t.step,"DELTAPFIX",e,"by",u*l);const p=c[e];r.setRelative(p,p,u*l,s)}}function Un(e,t){const n=t.stack,o=n.pop();exports.DEBUG&&console.log(t.step,"ROUND[]"),n.push(64*t.round(o/64))}function Tn(e,t){const n=t.stack,o=n.pop(),r=t.ppem,s=t.deltaBase+16*(e-1),a=t.deltaShift;exports.DEBUG&&console.log(t.step,"DELTAC["+e+"]",o,n);for(let e=0;e<o;e++){const e=n.pop(),o=n.pop();if(s+((240&o)>>4)!==r)continue;let i=(15&o)-8;i>=0&&i++;const l=i*a;exports.DEBUG&&console.log(t.step,"DELTACFIX",e,"by",l),t.cvt[e]+=l}}function wn(e,t){const n=t.stack,o=n.pop(),r=n.pop(),s=t.z2[o],a=t.z1[r];let i,l;exports.DEBUG&&console.log("SDPVTL["+e+"]",o,r),e?(i=s.y-a.y,l=a.x-s.x):(i=a.x-s.x,l=a.y-s.y),t.dpv=Kt(i,l)}function En(e,t){const n=t.stack,o=t.prog;let r=t.ip;exports.DEBUG&&console.log(t.step,"PUSHB["+e+"]");for(let t=0;t<e;t++)n.push(o[++r]);t.ip=r}function On(e,t){let n=t.ip;const o=t.prog,r=t.stack;exports.DEBUG&&console.log(t.ip,"PUSHW["+e+"]");for(let t=0;t<e;t++){let e=o[++n]<<8|o[++n];32768&e&&(e=-(1+(65535^e))),r.push(e)}t.ip=n}function Ln(e,t,n,o,r,s){const a=s.stack,i=e&&a.pop(),l=a.pop(),c=s.rp0,u=s.z0[c],p=s.z1[l],h=s.minDis,f=s.fv,d=s.dpv;let g,m,y,v;m=g=d.distance(p,u,!0,!0),y=m>=0?1:-1,m=Math.abs(m),e&&(v=s.cvt[i],o&&Math.abs(m-v)<s.cvCutIn&&(m=v)),n&&m<h&&(m=h),o&&(m=s.round(m)),f.setRelative(p,u,y*m,d),f.touch(p),exports.DEBUG&&console.log(s.step,(e?"MIRP[":"MDRP[")+(t?"M":"m")+(n?">":"_")+(o?"R":"_")+(0===r?"Gr":1===r?"Bl":2===r?"Wh":"")+"]",e?i+"("+s.cvt[i]+","+v+")":"",l,"(d =",g,"->",y*m,")"),s.rp1=s.rp0,s.rp2=l,t&&(s.rp0=l)}function kn(e){(e=e||{}).empty||(Gt(e.familyName,"When creating a new Font object, familyName is required."),Gt(e.styleName,"When creating a new Font object, styleName is required."),Gt(e.unitsPerEm,"When creating a new Font object, unitsPerEm is required."),Gt(e.ascender,"When creating a new Font object, ascender is required."),Gt(e.descender,"When creating a new Font object, descender is required."),Gt(e.descender<0,"Descender should be negative (e.g. -512)."),this.names={fontFamily:{en:e.familyName||" "},fontSubfamily:{en:e.styleName||" "},fullName:{en:e.fullName||e.familyName+" "+e.styleName},postScriptName:{en:e.postScriptName||e.familyName+e.styleName},designer:{en:e.designer||" "},designerURL:{en:e.designerURL||" "},manufacturer:{en:e.manufacturer||" "},manufacturerURL:{en:e.manufacturerURL||" "},license:{en:e.license||" "},licenseURL:{en:e.licenseURL||" "},version:{en:e.version||"Version 0.1"},description:{en:e.description||" "},copyright:{en:e.copyright||" "},trademark:{en:e.trademark||" "}},this.unitsPerEm=e.unitsPerEm||1e3,this.ascender=e.ascender,this.descender=e.descender,this.createdTimestamp=e.createdTimestamp,this.tables={os2:{usWeightClass:e.weightClass||this.usWeightClasses.MEDIUM,usWidthClass:e.widthClass||this.usWidthClasses.MEDIUM,fsSelection:e.fsSelection||this.fsSelectionValues.REGULAR}}),this.supported=!0,this.glyphs=new Me.GlyphSet(this,e.glyphs||[]),this.encoding=new ve(this),this.substitution=new Mt(this),this.tables=this.tables||{},Object.defineProperty(this,"hinting",{get:function(){return this._hinting?this._hinting:"truetype"===this.outlinesFormat?this._hinting=new zt(this):void 0}})}function Rn(e,t){const n=JSON.stringify(e);let o=256;for(let e in t){let r=parseInt(e);if(r&&!(r<256)){if(JSON.stringify(t[e])===n)return r;o<=r&&(o=r+1)}}return t[o]=e,o}function Dn(e,t,n){const o=Rn(t.name,n);return[{name:"tag_"+e,type:"TAG",value:t.tag},{name:"minValue_"+e,type:"FIXED",value:t.minValue<<16},{name:"defaultValue_"+e,type:"FIXED",value:t.defaultValue<<16},{name:"maxValue_"+e,type:"FIXED",value:t.maxValue<<16},{name:"flags_"+e,type:"USHORT",value:0},{name:"nameID_"+e,type:"USHORT",value:o}]}function Cn(e,t,n){const o={},r=new pe.Parser(e,t);return o.tag=r.parseTag(),o.minValue=r.parseFixed(),o.defaultValue=r.parseFixed(),o.maxValue=r.parseFixed(),r.skip("uShort",1),o.name=n[r.parseUShort()]||{},o}function Mn(e,t,n,o){const r=[{name:"nameID_"+e,type:"USHORT",value:Rn(t.name,o)},{name:"flags_"+e,type:"USHORT",value:0}];for(let o=0;o<n.length;++o){const s=n[o].tag;r.push({name:"axis_"+e+" "+s,type:"FIXED",value:t.coordinates[s]<<16})}return r}function In(e,t,n,o){const r={},s=new pe.Parser(e,t);r.name=o[s.parseUShort()]||{},s.skip("uShort",1),r.coordinates={};for(let e=0;e<n.length;++e)r.coordinates[n[e].tag]=s.parseFixed();return r}zt.prototype.exec=function(e,t){if("number"!=typeof t)throw new Error("Point size is not a number!");if(this._errorState>2)return;const n=this.font;let o=this._prepState;if(!o||o.ppem!==t){let e=this._fpgmState;if(!e){nn.prototype=tn,e=this._fpgmState=new nn("fpgm",n.tables.fpgm),e.funcs=[],e.font=n,exports.DEBUG&&(console.log("---EXEC FPGM---"),e.step=-1);try{At(e)}catch(e){return console.log("Hinting error in FPGM:"+e),void(this._errorState=3)}}nn.prototype=e,o=this._prepState=new nn("prep",n.tables.prep),o.ppem=t;const r=n.tables.cvt;if(r){const e=o.cvt=new Array(r.length),s=t/n.unitsPerEm;for(let t=0;t<r.length;t++)e[t]=r[t]*s}else o.cvt=[];exports.DEBUG&&(console.log("---EXEC PREP---"),o.step=-1);try{At(o)}catch(e){this._errorState<2&&console.log("Hinting error in PREP:"+e),this._errorState=2}}if(!(this._errorState>1))try{return Ft(e,o)}catch(e){return this._errorState<1&&(console.log("Hinting error:"+e),console.log("Note: further hinting errors are silenced")),void(this._errorState=1)}},Ft=function(e,t){const n=t.ppem/t.font.unitsPerEm,o=n;let r,s,a,i=e.components;if(nn.prototype=t,i){const l=t.font;s=[],r=[];for(let e=0;e<i.length;e++){const t=i[e],c=l.glyphs.get(t.glyphIndex);a=new nn("glyf",c.instructions),exports.DEBUG&&(console.log("---EXEC COMP "+e+"---"),a.step=-1),Ht(c,a,n,o);const u=Math.round(t.dx*n),p=Math.round(t.dy*o),h=a.gZone,f=a.contours;for(let e=0;e<h.length;e++){const t=h[e];t.xTouched=t.yTouched=!1,t.xo=t.x=t.x+u,t.yo=t.y=t.y+p}const d=s.length;s.push.apply(s,h);for(let e=0;e<f.length;e++)r.push(f[e]+d)}e.instructions&&!a.inhibitGridFit&&(a=new nn("glyf",e.instructions),a.gZone=a.z0=a.z1=a.z2=s,a.contours=r,s.push(new Jt(0,0),new Jt(Math.round(e.advanceWidth*n),0)),exports.DEBUG&&(console.log("---EXEC COMPOSITE---"),a.step=-1),At(a),s.length-=2)}else a=new nn("glyf",e.instructions),exports.DEBUG&&(console.log("---EXEC GLYPH---"),a.step=-1),Ht(e,a,n,o),s=a.gZone;return s},Ht=function(e,t,n,o){const r=e.points||[],s=r.length,a=t.gZone=t.z0=t.z1=t.z2=[],i=t.contours=[];let l,c,u;for(let e=0;e<s;e++)l=r[e],a[e]=new Jt(l.x*n,l.y*o,l.lastPointOfContour,l.onCurve);for(let e=0;e<s;e++)l=a[e],c||(c=l,i.push(e)),l.lastPointOfContour?(l.nextPointOnContour=c,c.prevPointOnContour=l,c=void 0):(u=a[e+1],l.nextPointOnContour=u,u.prevPointOnContour=l);if(!t.inhibitGridFit&&(a.push(new Jt(0,0),new Jt(Math.round(e.advanceWidth*n),0)),At(t),a.length-=2,exports.DEBUG)){console.log("FINISHED GLYPH",t.stack);for(let e=0;e<s;e++)console.log(e,a[e].x,a[e].y)}},At=function(e){let t=e.prog;if(!t)return;const n=t.length;let o;for(e.ip=0;e.ip<n;e.ip++){if(exports.DEBUG&&e.step++,o=Pt[t[e.ip]],!o)throw new Error("unknown instruction: 0x"+Number(t[e.ip]).toString(16));o(e)}},Pt=[sn.bind(void 0,$t),sn.bind(void 0,Zt),an.bind(void 0,$t),an.bind(void 0,Zt),ln.bind(void 0,$t),ln.bind(void 0,Zt),cn.bind(void 0,0),cn.bind(void 0,1),un.bind(void 0,0),un.bind(void 0,1),function(e){const t=e.stack,n=t.pop(),o=t.pop();exports.DEBUG&&console.log(e.step,"SPVFS[]",n,o),e.pv=e.dpv=Kt(o,n)},function(e){const t=e.stack,n=t.pop(),o=t.pop();exports.DEBUG&&console.log(e.step,"SPVFS[]",n,o),e.fv=Kt(o,n)},function(e){const t=e.stack,n=e.pv;exports.DEBUG&&console.log(e.step,"GPV[]"),t.push(16384*n.x),t.push(16384*n.y)},function(e){const t=e.stack,n=e.fv;exports.DEBUG&&console.log(e.step,"GFV[]"),t.push(16384*n.x),t.push(16384*n.y)},function(e){e.fv=e.pv,exports.DEBUG&&console.log(e.step,"SFVTPV[]")},function(e){const t=e.stack,n=t.pop(),o=t.pop(),r=t.pop(),s=t.pop(),a=t.pop(),i=e.z0,l=e.z1,c=i[n],u=i[o],p=l[r],h=l[s],f=e.z2[a];exports.DEBUG&&console.log("ISECT[], ",n,o,r,s,a);const d=c.x,g=c.y,m=u.x,y=u.y,v=p.x,b=p.y,x=h.x,S=h.y,U=(d-m)*(b-S)-(g-y)*(v-x),T=d*y-g*m,w=v*S-b*x;f.x=(T*(v-x)-w*(d-m))/U,f.y=(T*(b-S)-w*(g-y))/U},function(e){e.rp0=e.stack.pop(),exports.DEBUG&&console.log(e.step,"SRP0[]",e.rp0)},function(e){e.rp1=e.stack.pop(),exports.DEBUG&&console.log(e.step,"SRP1[]",e.rp1)},function(e){e.rp2=e.stack.pop(),exports.DEBUG&&console.log(e.step,"SRP2[]",e.rp2)},function(e){const t=e.stack.pop();switch(exports.DEBUG&&console.log(e.step,"SZP0[]",t),e.zp0=t,t){case 0:e.tZone||on(e),e.z0=e.tZone;break;case 1:e.z0=e.gZone;break;default:throw new Error("Invalid zone pointer")}},function(e){const t=e.stack.pop();switch(exports.DEBUG&&console.log(e.step,"SZP1[]",t),e.zp1=t,t){case 0:e.tZone||on(e),e.z1=e.tZone;break;case 1:e.z1=e.gZone;break;default:throw new Error("Invalid zone pointer")}},function(e){const t=e.stack.pop();switch(exports.DEBUG&&console.log(e.step,"SZP2[]",t),e.zp2=t,t){case 0:e.tZone||on(e),e.z2=e.tZone;break;case 1:e.z2=e.gZone;break;default:throw new Error("Invalid zone pointer")}},function(e){const t=e.stack.pop();switch(exports.DEBUG&&console.log(e.step,"SZPS[]",t),e.zp0=e.zp1=e.zp2=t,t){case 0:e.tZone||on(e),e.z0=e.z1=e.z2=e.tZone;break;case 1:e.z0=e.z1=e.z2=e.gZone;break;default:throw new Error("Invalid zone pointer")}},function(e){e.loop=e.stack.pop(),exports.DEBUG&&console.log(e.step,"SLOOP[]",e.loop)},function(e){exports.DEBUG&&console.log(e.step,"RTG[]"),e.round=_t},function(e){exports.DEBUG&&console.log(e.step,"RTHG[]"),e.round=Xt},function(e){const t=e.stack.pop();exports.DEBUG&&console.log(e.step,"SMD[]",t),e.minDis=t/64},function(e){exports.DEBUG&&console.log(e.step,"ELSE[]"),rn(e,!1)},function(e){const t=e.stack.pop();exports.DEBUG&&console.log(e.step,"JMPR[]",t),e.ip+=t-1},function(e){const t=e.stack.pop();exports.DEBUG&&console.log(e.step,"SCVTCI[]",t),e.cvCutIn=t/64},void 0,void 0,function(e){const t=e.stack;exports.DEBUG&&console.log(e.step,"DUP[]"),t.push(t[t.length-1])},pn,function(e){exports.DEBUG&&console.log(e.step,"CLEAR[]"),e.stack.length=0},function(e){const t=e.stack,n=t.pop(),o=t.pop();exports.DEBUG&&console.log(e.step,"SWAP[]"),t.push(n),t.push(o)},function(e){const t=e.stack;exports.DEBUG&&console.log(e.step,"DEPTH[]"),t.push(t.length)},function(e){const t=e.stack,n=t.pop();exports.DEBUG&&console.log(e.step,"CINDEX[]",n),t.push(t[t.length-n])},function(e){const t=e.stack,n=t.pop();exports.DEBUG&&console.log(e.step,"MINDEX[]",n),t.push(t.splice(t.length-n,1)[0])},void 0,void 0,void 0,function(e){const t=e.stack,n=t.pop(),o=t.pop();exports.DEBUG&&console.log(e.step,"LOOPCALL[]",n,o);const r=e.ip,s=e.prog;e.prog=e.funcs[n];for(let t=0;t<o;t++)At(e),exports.DEBUG&&console.log(++e.step,t+1<o?"next loopcall":"done loopcall",t);e.ip=r,e.prog=s},function(e){const t=e.stack.pop();exports.DEBUG&&console.log(e.step,"CALL[]",t);const n=e.ip,o=e.prog;e.prog=e.funcs[t],At(e),e.ip=n,e.prog=o,exports.DEBUG&&console.log(++e.step,"returning from",t)},function(e){if("fpgm"!==e.env)throw new Error("FDEF not allowed here");const t=e.stack,n=e.prog;let o=e.ip;const r=t.pop(),s=o;for(exports.DEBUG&&console.log(e.step,"FDEF[]",r);45!==n[++o];);e.ip=o,e.funcs[r]=n.slice(s+1,o)},void 0,hn.bind(void 0,0),hn.bind(void 0,1),fn.bind(void 0,$t),fn.bind(void 0,Zt),dn.bind(void 0,0),dn.bind(void 0,1),gn.bind(void 0,0),gn.bind(void 0,1),mn.bind(void 0,0),mn.bind(void 0,1),function(e){const t=e.stack;let n=e.loop;const o=e.fv,r=t.pop()/64,s=e.z2;for(;n--;){const a=t.pop(),i=s[a];exports.DEBUG&&console.log(e.step,(e.loop>1?"loop "+(e.loop-n)+": ":"")+"SHPIX[]",a,r),o.setRelative(i,i,r),o.touch(i)}e.loop=1},function(e){const t=e.stack,n=e.rp1,o=e.rp2;let r=e.loop;const s=e.z0[n],a=e.z1[o],i=e.fv,l=e.dpv,c=e.z2;for(;r--;){const u=t.pop(),p=c[u];exports.DEBUG&&console.log(e.step,(e.loop>1?"loop "+(e.loop-r)+": ":"")+"IP[]",u,n,"<->",o),i.interpolate(p,s,a,l),i.touch(p)}e.loop=1},yn.bind(void 0,0),yn.bind(void 0,1),function(e){const t=e.stack,n=e.rp0,o=e.z0[n];let r=e.loop;const s=e.fv,a=e.pv,i=e.z1;for(;r--;){const n=t.pop(),l=i[n];exports.DEBUG&&console.log(e.step,(e.loop>1?"loop "+(e.loop-r)+": ":"")+"ALIGNRP[]",n),s.setRelative(l,o,0,a),s.touch(l)}e.loop=1},function(e){exports.DEBUG&&console.log(e.step,"RTDG[]"),e.round=qt},vn.bind(void 0,0),vn.bind(void 0,1),function(e){const t=e.prog;let n=e.ip;const o=e.stack,r=t[++n];exports.DEBUG&&console.log(e.step,"NPUSHB[]",r);for(let e=0;e<r;e++)o.push(t[++n]);e.ip=n},function(e){let t=e.ip;const n=e.prog,o=e.stack,r=n[++t];exports.DEBUG&&console.log(e.step,"NPUSHW[]",r);for(let e=0;e<r;e++){let e=n[++t]<<8|n[++t];32768&e&&(e=-(1+(65535^e))),o.push(e)}e.ip=t},function(e){const t=e.stack;let n=e.store;n||(n=e.store=[]);const o=t.pop(),r=t.pop();exports.DEBUG&&console.log(e.step,"WS",o,r),n[r]=o},function(e){const t=e.stack,n=e.store,o=t.pop();exports.DEBUG&&console.log(e.step,"RS",o);const r=n&&n[o]||0;t.push(r)},function(e){const t=e.stack,n=t.pop(),o=t.pop();exports.DEBUG&&console.log(e.step,"WCVTP",n,o),e.cvt[o]=n/64},function(e){const t=e.stack,n=t.pop();exports.DEBUG&&console.log(e.step,"RCVT",n),t.push(64*e.cvt[n])},bn.bind(void 0,0),bn.bind(void 0,1),void 0,xn.bind(void 0,0),xn.bind(void 0,1),function(e){exports.DEBUG&&console.log(e.step,"MPPEM[]"),e.stack.push(e.ppem)},void 0,function(e){exports.DEBUG&&console.log(e.step,"FLIPON[]"),e.autoFlip=!0},void 0,void 0,function(e){const t=e.stack,n=t.pop(),o=t.pop();exports.DEBUG&&console.log(e.step,"LT[]",n,o),t.push(o<n?1:0)},function(e){const t=e.stack,n=t.pop(),o=t.pop();exports.DEBUG&&console.log(e.step,"LTEQ[]",n,o),t.push(o<=n?1:0)},function(e){const t=e.stack,n=t.pop(),o=t.pop();exports.DEBUG&&console.log(e.step,"GT[]",n,o),t.push(o>n?1:0)},function(e){const t=e.stack,n=t.pop(),o=t.pop();exports.DEBUG&&console.log(e.step,"GTEQ[]",n,o),t.push(o>=n?1:0)},function(e){const t=e.stack,n=t.pop(),o=t.pop();exports.DEBUG&&console.log(e.step,"EQ[]",n,o),t.push(n===o?1:0)},function(e){const t=e.stack,n=t.pop(),o=t.pop();exports.DEBUG&&console.log(e.step,"NEQ[]",n,o),t.push(n!==o?1:0)},function(e){const t=e.stack,n=t.pop();exports.DEBUG&&console.log(e.step,"ODD[]",n),t.push(Math.trunc(n)%2?1:0)},function(e){const t=e.stack,n=t.pop();exports.DEBUG&&console.log(e.step,"EVEN[]",n),t.push(Math.trunc(n)%2?0:1)},function(e){let t=e.stack.pop();exports.DEBUG&&console.log(e.step,"IF[]",t),t||(rn(e,!0),exports.DEBUG&&console.log(e.step,"EIF[]"))},function(e){exports.DEBUG&&console.log(e.step,"EIF[]")},function(e){const t=e.stack,n=t.pop(),o=t.pop();exports.DEBUG&&console.log(e.step,"AND[]",n,o),t.push(n&&o?1:0)},function(e){const t=e.stack,n=t.pop(),o=t.pop();exports.DEBUG&&console.log(e.step,"OR[]",n,o),t.push(n||o?1:0)},function(e){const t=e.stack,n=t.pop();exports.DEBUG&&console.log(e.step,"NOT[]",n),t.push(n?0:1)},Sn.bind(void 0,1),function(e){const t=e.stack.pop();exports.DEBUG&&console.log(e.step,"SDB[]",t),e.deltaBase=t},function(e){const t=e.stack.pop();exports.DEBUG&&console.log(e.step,"SDS[]",t),e.deltaShift=Math.pow(.5,t)},function(e){const t=e.stack,n=t.pop(),o=t.pop();exports.DEBUG&&console.log(e.step,"ADD[]",n,o),t.push(o+n)},function(e){const t=e.stack,n=t.pop(),o=t.pop();exports.DEBUG&&console.log(e.step,"SUB[]",n,o),t.push(o-n)},function(e){const t=e.stack,n=t.pop(),o=t.pop();exports.DEBUG&&console.log(e.step,"DIV[]",n,o),t.push(64*o/n)},function(e){const t=e.stack,n=t.pop(),o=t.pop();exports.DEBUG&&console.log(e.step,"MUL[]",n,o),t.push(o*n/64)},function(e){const t=e.stack,n=t.pop();exports.DEBUG&&console.log(e.step,"ABS[]",n),t.push(Math.abs(n))},function(e){const t=e.stack;let n=t.pop();exports.DEBUG&&console.log(e.step,"NEG[]",n),t.push(-n)},function(e){const t=e.stack,n=t.pop();exports.DEBUG&&console.log(e.step,"FLOOR[]",n),t.push(64*Math.floor(n/64))},function(e){const t=e.stack,n=t.pop();exports.DEBUG&&console.log(e.step,"CEILING[]",n),t.push(64*Math.ceil(n/64))},Un.bind(void 0,0),Un.bind(void 0,1),Un.bind(void 0,2),Un.bind(void 0,3),void 0,void 0,void 0,void 0,function(e){const t=e.stack,n=t.pop(),o=t.pop();exports.DEBUG&&console.log(e.step,"WCVTF[]",n,o),e.cvt[o]=n*e.ppem/e.font.unitsPerEm},Sn.bind(void 0,2),Sn.bind(void 0,3),Tn.bind(void 0,1),Tn.bind(void 0,2),Tn.bind(void 0,3),function(e){let t,n=e.stack.pop();switch(exports.DEBUG&&console.log(e.step,"SROUND[]",n),e.round=Yt,192&n){case 0:t=.5;break;case 64:t=1;break;case 128:t=2;break;default:throw new Error("invalid SROUND value")}switch(e.srPeriod=t,48&n){case 0:e.srPhase=0;break;case 16:e.srPhase=.25*t;break;case 32:e.srPhase=.5*t;break;case 48:e.srPhase=.75*t;break;default:throw new Error("invalid SROUND value")}n&=15,e.srThreshold=0===n?0:(n/8-.5)*t},function(e){let t,n=e.stack.pop();switch(exports.DEBUG&&console.log(e.step,"S45ROUND[]",n),e.round=Yt,192&n){case 0:t=Math.sqrt(2)/2;break;case 64:t=Math.sqrt(2);break;case 128:t=2*Math.sqrt(2);break;default:throw new Error("invalid S45ROUND value")}switch(e.srPeriod=t,48&n){case 0:e.srPhase=0;break;case 16:e.srPhase=.25*t;break;case 32:e.srPhase=.5*t;break;case 48:e.srPhase=.75*t;break;default:throw new Error("invalid S45ROUND value")}n&=15,e.srThreshold=0===n?0:(n/8-.5)*t},void 0,void 0,function(e){exports.DEBUG&&console.log(e.step,"ROFF[]"),e.round=Wt},void 0,function(e){exports.DEBUG&&console.log(e.step,"RUTG[]"),e.round=Vt},function(e){exports.DEBUG&&console.log(e.step,"RDTG[]"),e.round=jt},pn,pn,void 0,void 0,void 0,void 0,void 0,function(e){const t=e.stack.pop();exports.DEBUG&&console.log(e.step,"SCANCTRL[]",t)},wn.bind(void 0,0),wn.bind(void 0,1),function(e){const t=e.stack,n=t.pop();let o=0;exports.DEBUG&&console.log(e.step,"GETINFO[]",n),1&n&&(o=35),32&n&&(o|=4096),t.push(o)},void 0,function(e){const t=e.stack,n=t.pop(),o=t.pop(),r=t.pop();exports.DEBUG&&console.log(e.step,"ROLL[]"),t.push(o),t.push(n),t.push(r)},function(e){const t=e.stack,n=t.pop(),o=t.pop();exports.DEBUG&&console.log(e.step,"MAX[]",n,o),t.push(Math.max(o,n))},function(e){const t=e.stack,n=t.pop(),o=t.pop();exports.DEBUG&&console.log(e.step,"MIN[]",n,o),t.push(Math.min(o,n))},function(e){const t=e.stack.pop();exports.DEBUG&&console.log(e.step,"SCANTYPE[]",t)},function(e){const t=e.stack.pop();let n=e.stack.pop();switch(exports.DEBUG&&console.log(e.step,"INSTCTRL[]",t,n),t){case 1:return void(e.inhibitGridFit=!!n);case 2:return void(e.ignoreCvt=!!n);default:throw new Error("invalid INSTCTRL[] selector")}},void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,En.bind(void 0,1),En.bind(void 0,2),En.bind(void 0,3),En.bind(void 0,4),En.bind(void 0,5),En.bind(void 0,6),En.bind(void 0,7),En.bind(void 0,8),On.bind(void 0,1),On.bind(void 0,2),On.bind(void 0,3),On.bind(void 0,4),On.bind(void 0,5),On.bind(void 0,6),On.bind(void 0,7),On.bind(void 0,8),Ln.bind(void 0,0,0,0,0,0),Ln.bind(void 0,0,0,0,0,1),Ln.bind(void 0,0,0,0,0,2),Ln.bind(void 0,0,0,0,0,3),Ln.bind(void 0,0,0,0,1,0),Ln.bind(void 0,0,0,0,1,1),Ln.bind(void 0,0,0,0,1,2),Ln.bind(void 0,0,0,0,1,3),Ln.bind(void 0,0,0,1,0,0),Ln.bind(void 0,0,0,1,0,1),Ln.bind(void 0,0,0,1,0,2),Ln.bind(void 0,0,0,1,0,3),Ln.bind(void 0,0,0,1,1,0),Ln.bind(void 0,0,0,1,1,1),Ln.bind(void 0,0,0,1,1,2),Ln.bind(void 0,0,0,1,1,3),Ln.bind(void 0,0,1,0,0,0),Ln.bind(void 0,0,1,0,0,1),Ln.bind(void 0,0,1,0,0,2),Ln.bind(void 0,0,1,0,0,3),Ln.bind(void 0,0,1,0,1,0),Ln.bind(void 0,0,1,0,1,1),Ln.bind(void 0,0,1,0,1,2),Ln.bind(void 0,0,1,0,1,3),Ln.bind(void 0,0,1,1,0,0),Ln.bind(void 0,0,1,1,0,1),Ln.bind(void 0,0,1,1,0,2),Ln.bind(void 0,0,1,1,0,3),Ln.bind(void 0,0,1,1,1,0),Ln.bind(void 0,0,1,1,1,1),Ln.bind(void 0,0,1,1,1,2),Ln.bind(void 0,0,1,1,1,3),Ln.bind(void 0,1,0,0,0,0),Ln.bind(void 0,1,0,0,0,1),Ln.bind(void 0,1,0,0,0,2),Ln.bind(void 0,1,0,0,0,3),Ln.bind(void 0,1,0,0,1,0),Ln.bind(void 0,1,0,0,1,1),Ln.bind(void 0,1,0,0,1,2),Ln.bind(void 0,1,0,0,1,3),Ln.bind(void 0,1,0,1,0,0),Ln.bind(void 0,1,0,1,0,1),Ln.bind(void 0,1,0,1,0,2),Ln.bind(void 0,1,0,1,0,3),Ln.bind(void 0,1,0,1,1,0),Ln.bind(void 0,1,0,1,1,1),Ln.bind(void 0,1,0,1,1,2),Ln.bind(void 0,1,0,1,1,3),Ln.bind(void 0,1,1,0,0,0),Ln.bind(void 0,1,1,0,0,1),Ln.bind(void 0,1,1,0,0,2),Ln.bind(void 0,1,1,0,0,3),Ln.bind(void 0,1,1,0,1,0),Ln.bind(void 0,1,1,0,1,1),Ln.bind(void 0,1,1,0,1,2),Ln.bind(void 0,1,1,0,1,3),Ln.bind(void 0,1,1,1,0,0),Ln.bind(void 0,1,1,1,0,1),Ln.bind(void 0,1,1,1,0,2),Ln.bind(void 0,1,1,1,0,3),Ln.bind(void 0,1,1,1,1,0),Ln.bind(void 0,1,1,1,1,1),Ln.bind(void 0,1,1,1,1,2),Ln.bind(void 0,1,1,1,1,3)],kn.prototype.hasChar=function(e){return null!==this.encoding.charToGlyphIndex(e)},kn.prototype.charToGlyphIndex=function(e){return this.encoding.charToGlyphIndex(e)},kn.prototype.charToGlyph=function(e){const t=this.charToGlyphIndex(e);let n=this.glyphs.get(t);return n||(n=this.glyphs.get(0)),n},kn.prototype.stringToGlyphs=function(e,t){t=t||this.defaultRenderOptions;const n=[];for(let t=0;t<e.length;t+=1){const o=e[t];n.push(this.charToGlyphIndex(o))}let o=n.length;if(t.features){const e=t.script||this.substitution.getDefaultScriptName();let r=[];t.features.liga&&(r=r.concat(this.substitution.getFeature("liga",e,t.language))),t.features.rlig&&(r=r.concat(this.substitution.getFeature("rlig",e,t.language)));for(let e=0;e<o;e+=1)for(let t=0;t<r.length;t++){const s=r[t],a=s.sub,i=a.length;let l=0;for(;l<i&&a[l]===n[e+l];)l++;l===i&&(n.splice(e,i,s.by),o=o-i+1)}}const r=new Array(o),s=this.glyphs.get(0);for(let e=0;e<o;e+=1)r[e]=this.glyphs.get(n[e])||s;return r},kn.prototype.nameToGlyphIndex=function(e){return this.glyphNames.nameToGlyphIndex(e)},kn.prototype.nameToGlyph=function(e){const t=this.nameToGlyphIndex(e);let n=this.glyphs.get(t);return n||(n=this.glyphs.get(0)),n},kn.prototype.glyphIndexToName=function(e){return this.glyphNames.glyphIndexToName?this.glyphNames.glyphIndexToName(e):""},kn.prototype.getKerningValue=function(e,t){e=e.index||e,t=t.index||t;const n=this.getGposKerningValue;return n?n(e,t):this.kerningPairs[e+","+t]||0},kn.prototype.defaultRenderOptions={kerning:!0,features:{liga:!0,rlig:!0}},kn.prototype.forEachGlyph=function(e,t,n,o,r,s){t=void 0!==t?t:0,n=void 0!==n?n:0,o=void 0!==o?o:72,r=r||this.defaultRenderOptions;const a=1/this.unitsPerEm*o,i=this.stringToGlyphs(e,r);for(let e=0;e<i.length;e+=1){const l=i[e];if(s.call(this,l,t,n,o,r),l.advanceWidth&&(t+=l.advanceWidth*a),r.kerning&&e<i.length-1){t+=this.getKerningValue(l,i[e+1])*a}r.letterSpacing?t+=r.letterSpacing*o:r.tracking&&(t+=r.tracking/1e3*o)}return t},kn.prototype.getPath=function(e,t,n,o,r){const s=new I;return this.forEachGlyph(e,t,n,o,r,(function(e,t,n,o){const a=e.getPath(t,n,o,r,this);s.extend(a)})),s},kn.prototype.getPaths=function(e,t,n,o,r){const s=[];return this.forEachGlyph(e,t,n,o,r,(function(e,t,n,o){const a=e.getPath(t,n,o,r,this);s.push(a)})),s},kn.prototype.getAdvanceWidth=function(e,t,n){return this.forEachGlyph(e,0,0,t,n,(function(){}))},kn.prototype.draw=function(e,t,n,o,r,s){this.getPath(t,n,o,r,s).draw(e)},kn.prototype.drawPoints=function(e,t,n,o,r,s){this.forEachGlyph(t,n,o,r,s,(function(t,n,o,r){t.drawPoints(e,n,o,r)}))},kn.prototype.drawMetrics=function(e,t,n,o,r,s){this.forEachGlyph(t,n,o,r,s,(function(t,n,o,r){t.drawMetrics(e,n,o,r)}))},kn.prototype.getEnglishName=function(e){const t=this.names[e];if(t)return t.en},kn.prototype.validate=function(){const e=this;function t(t){const n=e.getEnglishName(t);n&&n.trim().length}t("fontFamily"),t("weightName"),t("manufacturer"),t("copyright"),t("version"),this.unitsPerEm},kn.prototype.toTables=function(){return Rt.fontToTable(this)},kn.prototype.toBuffer=function(){return console.warn("Font.toBuffer is deprecated. Use Font.toArrayBuffer instead."),this.toArrayBuffer()},kn.prototype.toArrayBuffer=function(){const e=this.toTables().encode(),t=new ArrayBuffer(e.length),n=new Uint8Array(t);for(let t=0;t<e.length;t++)n[t]=e[t];return t},kn.prototype.download=function(e){const t=this.getEnglishName("fontFamily"),n=this.getEnglishName("fontSubfamily");e=e||t.replace(/\s/g,"")+"-"+n+".otf";const o=this.toArrayBuffer();if("undefined"!=typeof window)window.requestFileSystem=window.requestFileSystem||window.webkitRequestFileSystem,window.requestFileSystem(window.TEMPORARY,o.byteLength,(function(t){t.root.getFile(e,{create:!0},(function(e){e.createWriter((function(t){const n=new DataView(o),r=new Blob([n],{type:"font/opentype"});t.write(r),t.addEventListener("writeend",(function(){location.href=e.toURL()}),!1)}))}))}),(function(e){throw new Error(e.name+": "+e.message)}));else{const t=require("fs"),n=function(e){const t=new Buffer(e.byteLength),n=new Uint8Array(e);for(let e=0;e<t.length;++e)t[e]=n[e];return t}(o);t.writeFileSync(e,n)}},kn.prototype.fsSelectionValues={ITALIC:1,UNDERSCORE:2,NEGATIVE:4,OUTLINED:8,STRIKEOUT:16,BOLD:32,REGULAR:64,USER_TYPO_METRICS:128,WWS:256,OBLIQUE:512},kn.prototype.usWidthClasses={ULTRA_CONDENSED:1,EXTRA_CONDENSED:2,CONDENSED:3,SEMI_CONDENSED:4,MEDIUM:5,SEMI_EXPANDED:6,EXPANDED:7,EXTRA_EXPANDED:8,ULTRA_EXPANDED:9},kn.prototype.usWeightClasses={THIN:100,EXTRA_LIGHT:200,LIGHT:300,NORMAL:400,MEDIUM:500,SEMI_BOLD:600,BOLD:700,EXTRA_BOLD:800,BLACK:900};var Bn={make:function(e,t){const n=new oe.Table("fvar",[{name:"version",type:"ULONG",value:65536},{name:"offsetToData",type:"USHORT",value:0},{name:"countSizePairs",type:"USHORT",value:2},{name:"axisCount",type:"USHORT",value:e.axes.length},{name:"axisSize",type:"USHORT",value:20},{name:"instanceCount",type:"USHORT",value:e.instances.length},{name:"instanceSize",type:"USHORT",value:4+4*e.axes.length}]);n.offsetToData=n.sizeOf();for(let o=0;o<e.axes.length;o++)n.fields=n.fields.concat(Dn(o,e.axes[o],t));for(let o=0;o<e.instances.length;o++)n.fields=n.fields.concat(Mn(o,e.instances[o],e.axes,t));return n},parse:function(e,t,n){const o=new pe.Parser(e,t),r=o.parseULong();G.argument(65536===r,"Unsupported fvar table version.");const s=o.parseOffset16();o.skip("uShort",1);const a=o.parseUShort(),i=o.parseUShort(),l=o.parseUShort(),c=o.parseUShort(),u=[];for(let o=0;o<a;o++)u.push(Cn(e,t+s+o*i,n));const p=[],h=t+s+a*i;for(let t=0;t<l;t++)p.push(In(e,h+t*c,u,n));return{axes:u,instances:p}}};function Nn(e,t){const n=new pe.Parser(e,t),o=n.parseUShort(),r=[];for(let e=0;e<o;e++)r[n.parseTag()]={offset:n.parseUShort()};return r}function Gn(e,t){const n=new pe.Parser(e,t),o=n.parseUShort();if(1===o){const e=n.parseUShort(),t=n.parseUShort(),o=n.parseUShortList(t);return function(t){return o[t-e]||0}}if(2===o){const e=n.parseUShort(),t=[],o=[],r=[];for(let s=0;s<e;s++)t[s]=n.parseUShort(),o[s]=n.parseUShort(),r[s]=n.parseUShort();return function(e){let n=0,s=t.length-1;for(;n<s;){const o=n+s+1>>1;e<t[o]?s=o-1:n=o}return t[n]<=e&&e<=o[n]&&r[n]||0}}}function Pn(e,t){const n=new pe.Parser(e,t),o=n.parseUShort(),r=function(e,t){const n=new pe.Parser(e,t),o=n.parseUShort();let r=n.parseUShort();if(1===o)return n.parseUShortList(r);if(2===o){const e=[];for(;r--;){const t=n.parseUShort(),o=n.parseUShort();let r=n.parseUShort();for(let n=t;n<=o;n++)e[r++]=n}return e}}(e,t+n.parseUShort()),s=n.parseUShort(),a=n.parseUShort();let i;if(4!==s||0!==a)return;const l={};if(1===o){const e=n.parseUShort(),t=[],o=n.parseOffset16List(e);for(let c=0;c<e;c++){const e=o[c];let u=l[e];if(!u){u={},n.relativeOffset=e;let t=n.parseUShort();for(;t--;){const e=n.parseUShort();s&&(i=n.parseShort()),a&&n.parseShort(),u[e]=i}}t[r[c]]=u}return function(e,n){const o=t[e];if(o)return o[n]}}if(2===o){const o=n.parseUShort(),l=n.parseUShort(),c=n.parseUShort(),u=n.parseUShort(),p=Gn(e,t+o),h=Gn(e,t+l),f=[];for(let e=0;e<c;e++){const t=f[e]=[];for(let e=0;e<u;e++)s&&(i=n.parseShort()),a&&n.parseShort(),t[e]=i}const d={};for(let e=0;e<r.length;e++)d[r[e]]=1;return function(e,t){if(!d[e])return;const n=p(e),o=h(t),r=f[n];return r?r[o]:void 0}}}function An(e,t){const n=new pe.Parser(e,t),o=n.parseUShort(),r=n.parseUShort(),s=16&r,a=n.parseUShort(),i=n.parseOffset16List(a),l={lookupType:o,lookupFlag:r,markFilteringSet:s?n.parseUShort():-1};if(2===o){const n=[];for(let o=0;o<a;o++){const r=Pn(e,t+i[o]);r&&n.push(r)}l.getKerningValue=function(e,t){for(let o=n.length;o--;){const r=n[o](e,t);if(void 0!==r)return r}return 0}}return l}var Fn={parse:function(e,t,n){const o=new pe.Parser(e,t),r=o.parseFixed();G.argument(1===r,"Unsupported GPOS table version."),Nn(e,t+o.parseUShort()),Nn(e,t+o.parseUShort());const s=o.parseUShort();o.relativeOffset=s;const a=o.parseUShort(),i=o.parseOffset16List(a),l=t+s;for(let t=0;t<a;t++){const o=An(e,l+i[t]);2!==o.lookupType||n.getGposKerningValue||(n.getGposKerningValue=o.getKerningValue)}}};var Hn={parse:function(e,t){const n=new pe.Parser(e,t),o=n.parseUShort();if(0===o)return function(e){const t={};e.skip("uShort");const n=e.parseUShort();G.argument(0===n,"Unsupported kern sub-table version."),e.skip("uShort",2);const o=e.parseUShort();e.skip("uShort",3);for(let n=0;n<o;n+=1){const n=e.parseUShort(),o=e.parseUShort(),r=e.parseShort();t[n+","+o]=r}return t}(n);if(1===o)return function(e){const t={};e.skip("uShort"),e.parseULong()>1&&console.warn("Only the first kern subtable is supported."),e.skip("uLong");const n=255&e.parseUShort();if(e.skip("uShort"),0===n){const n=e.parseUShort();e.skip("uShort",3);for(let o=0;o<n;o+=1){const n=e.parseUShort(),o=e.parseUShort(),r=e.parseShort();t[n+","+o]=r}}return t}(n);throw new Error("Unsupported kern table version ("+o+").")}};var zn={parse:function(e,t,n,o){const r=new pe.Parser(e,t),s=o?r.parseUShort:r.parseULong,a=[];for(let e=0;e<n+1;e+=1){let e=s.call(r);o&&(e*=2),a.push(e)}return a}};function Wn(e,t){require("fs").readFile(e,(function(e,n){if(e)return t(e.message);t(null,Nt(n))}))}function _n(e,t){const n=new XMLHttpRequest;n.open("get",e,!0),n.responseType="arraybuffer",n.onload=function(){return 200!==n.status?t("Font could not be loaded: "+n.statusText):t(null,n.response)},n.onerror=function(){t("Font could not be loaded")},n.send()}function qn(e,t){const n=[];let o=12;for(let r=0;r<t;r+=1){const t=pe.getTag(e,o),r=pe.getULong(e,o+4),s=pe.getULong(e,o+8),a=pe.getULong(e,o+12);n.push({tag:t,checksum:r,offset:s,length:a,compression:!1}),o+=16}return n}function Xn(e,t){if("WOFF"===t.compression){const n=new Uint8Array(e.buffer,t.offset+2,t.compressedLength-2),o=new Uint8Array(t.length);if(D(n,o),o.byteLength!==t.length)throw new Error("Decompression error: "+t.tag+" decompressed length doesn't match recorded length");return{data:new DataView(o.buffer,0),offset:0}}return{data:e,offset:t.offset}}function Vn(e){let t,n;const o=new kn({empty:!0}),r=new DataView(e,0);let s,a=[];const i=pe.getTag(r,0);if(i===String.fromCharCode(0,1,0,0)||"true"===i||"typ1"===i)o.outlinesFormat="truetype",s=pe.getUShort(r,4),a=qn(r,s);else if("OTTO"===i)o.outlinesFormat="cff",s=pe.getUShort(r,4),a=qn(r,s);else{if("wOFF"!==i)throw new Error("Unsupported OpenType signature "+i);{const e=pe.getTag(r,4);if(e===String.fromCharCode(0,1,0,0))o.outlinesFormat="truetype";else{if("OTTO"!==e)throw new Error("Unsupported OpenType flavor "+i);o.outlinesFormat="cff"}s=pe.getUShort(r,12),a=function(e,t){const n=[];let o=44;for(let r=0;r<t;r+=1){const t=pe.getTag(e,o),r=pe.getULong(e,o+4),s=pe.getULong(e,o+8),a=pe.getULong(e,o+12);let i;i=s<a&&"WOFF",n.push({tag:t,offset:r,compression:i,compressedLength:s,length:a}),o+=20}return n}(r,s)}}let l,c,u,p,h,f,d,g,m,y,v;for(let e=0;e<s;e+=1){const s=a[e];let i;switch(s.tag){case"cmap":i=Xn(r,s),o.tables.cmap=fe.parse(i.data,i.offset),o.encoding=new be(o.tables.cmap);break;case"cvt ":i=Xn(r,s),v=new pe.Parser(i.data,i.offset),o.tables.cvt=v.parseShortList(s.length/2);break;case"fvar":c=s;break;case"fpgm":i=Xn(r,s),v=new pe.Parser(i.data,i.offset),o.tables.fpgm=v.parseByteList(s.length);break;case"head":i=Xn(r,s),o.tables.head=Ke.parse(i.data,i.offset),o.unitsPerEm=o.tables.head.unitsPerEm,t=o.tables.head.indexToLocFormat;break;case"hhea":i=Xn(r,s),o.tables.hhea=Je.parse(i.data,i.offset),o.ascender=o.tables.hhea.ascender,o.descender=o.tables.hhea.descender,o.numberOfHMetrics=o.tables.hhea.numberOfHMetrics;break;case"hmtx":f=s;break;case"ltag":i=Xn(r,s),n=tt.parse(i.data,i.offset);break;case"maxp":i=Xn(r,s),o.tables.maxp=nt.parse(i.data,i.offset),o.numGlyphs=o.tables.maxp.numGlyphs;break;case"name":m=s;break;case"OS/2":i=Xn(r,s),o.tables.os2=mt.parse(i.data,i.offset);break;case"post":i=Xn(r,s),o.tables.post=yt.parse(i.data,i.offset),o.glyphNames=new Se(o.tables.post);break;case"prep":i=Xn(r,s),v=new pe.Parser(i.data,i.offset),o.tables.prep=v.parseByteList(s.length);break;case"glyf":u=s;break;case"loca":g=s;break;case"CFF ":l=s;break;case"kern":d=s;break;case"GPOS":p=s;break;case"GSUB":h=s;break;case"meta":y=s}}const b=Xn(r,m);if(o.tables.name=dt.parse(b.data,b.offset,n),o.names=o.tables.name,u&&g){const e=0===t,n=Xn(r,g),s=zn.parse(n.data,n.offset,o.numGlyphs,e),a=Xn(r,u);o.glyphs=ke.parse(a.data,a.offset,s,o)}else{if(!l)throw new Error("Font doesn't contain TrueType or CFF outlines.");{const e=Xn(r,l);Qe.parse(e.data,e.offset,o)}}const x=Xn(r,f);if(et.parse(x.data,x.offset,o.numberOfHMetrics,o.numGlyphs,o.glyphs),function(e){let t;const n=e.tables.cmap.glyphIndexMap,o=Object.keys(n);for(let r=0;r<o.length;r+=1){const s=o[r],a=n[s];t=e.glyphs.get(a),t.addUnicode(parseInt(s))}for(let n=0;n<e.glyphs.length;n+=1)t=e.glyphs.get(n),e.cffEncoding?e.isCIDFont?t.name="gid"+n:t.name=e.cffEncoding.charset[n]:e.glyphNames.names&&(t.name=e.glyphNames.glyphIndexToName(n))}(o),d){const e=Xn(r,d);o.kerningPairs=Hn.parse(e.data,e.offset)}else o.kerningPairs={};if(p){const e=Xn(r,p);Fn.parse(e.data,e.offset,o)}if(h){const e=Xn(r,h);o.tables.gsub=St.parse(e.data,e.offset)}if(c){const e=Xn(r,c);o.tables.fvar=Bn.parse(e.data,e.offset,o.names)}if(y){const e=Xn(r,y);o.tables.meta=Ut.parse(e.data,e.offset),o.metas=o.tables.meta}return o}var jn=Object.freeze({__proto__:null,Font:kn,Glyph:Re,Path:I,BoundingBox:M,_parse:pe,parse:Vn,load:function(e,t){("undefined"==typeof window?Wn:_n)(e,(function(e,n){if(e)return t(e);let o;try{o=Vn(n)}catch(e){return t(e,null)}return t(null,o)}))},loadSync:function(e){return Vn(Nt(require("fs").readFileSync(e)))}});const Yn=r.default.join(__dirname,"./fonts/font.ttf"),Zn=jn.loadSync(Yn),$n={width:150,height:50,noise:4,color:!1,background:"#FFFAE8",size:4,ignoreChars:"",fontSize:40,charPreset:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",font:Zn,ascender:Zn.ascender,descender:Zn.descender,truncateLineProbability:.5,truncateCurveProbability:.5,truncateCurvePositionMin:.4,truncateCurvePositionMax:.6};var Qn={options:$n,loadFont:e=>{const t=jn.loadSync(e);$n.font=t,$n.ascender=t.ascender,$n.descender=t.descender}};const Kn=Qn.options,Jn=function(e,t){return Math.round(e+Math.random()*(t-e))};const eo=function(e,t){return{text:(e+t).toString(),equation:e+"+"+t}},to=function(e,t){return{text:(e-t).toString(),equation:e+"-"+t}};function no(e,t,n){return 6*(n=(n+1)%1)<1?e+(t-e)*n*6:2*n<1?t:3*n<2?e+(t-e)*(2/3-n)*6:e}var oo={int:Jn,greyColor:function(e,t){const n=Jn(e=e||1,t=t||9).toString(16);return`#${n}${n}${n}`},captchaText:function(e){"number"==typeof e&&(e={size:e});const t=(e=e||{}).size||4,n=e.ignoreChars||"";let o=-1,r="",s=e.charPreset||Kn.charPreset;n&&(s=function(e,t){return e.split("").filter(e=>-1===t.indexOf(e))}(s,n));const a=s.length-1;for(;++o<t;)r+=s[Jn(0,a)];return r},mathExpr:function(e,t,n){const o=Jn(e=e||1,t=t||9),r=Jn(e,t);switch(n){case"+":return eo(o,r);case"-":return to(o,r);default:return Jn(1,2)%2?eo(o,r):to(o,r)}},color:function(e){const t=Jn(0,24)/24,n=Jn(60,80)/100,o=e?function(e){if("#"!==e[0])return 1;3===(e=e.slice(1)).length&&(e=e[0]+e[0]+e[1]+e[1]+e[2]+e[2]);const t=parseInt(e,16),n=t>>16,o=t>>8&255,r=255&t,s=Math.max(n,o,r),a=Math.min(n,o,r);return(s+a)/510}(e):1;let r,s;o>=.5?(r=Math.round(100*o)-45,s=Math.round(100*o)-25):(r=Math.round(100*o)+25,s=Math.round(100*o)+45);const a=Jn(r,s)/100,i=a<.5?a*(a+n):a+n-a*n,l=2*a-i,c=Math.floor(255*no(l,i,t+1/3)),u=Math.floor(255*no(l,i,t));return"#"+(Math.floor(255*no(l,i,t-1/3))|u<<8|c<<16|1<<24).toString(16).slice(1)}};const ro=Qn.options,so=function(e,t){e=e||oo.captchaText();const n=(t=Object.assign({},ro,t)).width,o=t.height,r=t.background||t.backgroundColor;r&&(t.color=!0);const s=r?`<rect width="100%" height="100%" fill="${r}"/>`:"",a=[].concat(function(e,t,n){const o=n.color,r=[],s=n.inverse?7:1,a=n.inverse?15:9;let i=-1;for(;++i<n.noise;){const n=`${oo.int(1,21)} ${oo.int(1,t-1)}`,i=`${oo.int(e-21,e-1)} ${oo.int(1,t-1)}`,l=`${oo.int(e/2-21,e/2+21)} ${oo.int(1,t-1)}`,c=`${oo.int(e/2-21,e/2+21)} ${oo.int(1,t-1)}`,u=o?oo.color():oo.greyColor(s,a);r.push(`<path d="M${n} C${l},${c},${i}" stroke="${u}" fill="none"/>`)}return r}(n,o,t)).concat(function(e,t,n,o,r){const s=e.length,a=(t-2)/(s+1),i=o.inverse?10:0,l=o.inverse?14:4;let u=-1;const p=[],h=r||o.color?oo.color(o.background):oo.greyColor(i,l);for(;++u<s;){const t=a*(u+1),r=n/2,s=c(e[u],Object.assign({x:t,y:r},o));p.push(`<path fill="${h}" d="${s}"/>`)}return p}(e,n,o,t)).sort(()=>Math.random()-.5).join("");return`${`<svg xmlns="http://www.w3.org/2000/svg" width="${n}" height="${o}" viewBox="0,0,${n},${o}">`}${s}${a}</svg>`};var ao=so,io=oo.captchaText,lo=function(e){const t=e.text||oo.captchaText(e);return{text:t,data:so(t,e)}},co=function(e){const t=oo.mathExpr(e.mathMin,e.mathMax,e.mathOperator);return{text:t.text,data:so(t.equation,e)}},uo=ro,po=Qn.loadFont;ao.randomText=io,ao.create=lo,ao.createMathExpr=co,ao.options=uo,ao.loadFont=po;var ho=ao;var fo=class{constructor(e={}){let{level:t=2,...n}=e;this.width=300,this.height=100;const o=[Math.floor(256*Math.random()),Math.floor(256*Math.random()),Math.floor(256*Math.random())];-1===[1,2,3,4].indexOf(t)&&(t=2);const r={};1===t?Object.assign(r,{lineWidth:5,textColor:o,textLength:4,lineOffset:0,background:[255,250,232],randomLineNum:5},n):2===t?Object.assign(r,{lineWidth:5,textColor:o,textLength:4,lineOffset:0,background:[255,250,232],randomLineNum:10},n):3===t?Object.assign(r,{lineWidth:5,textColor:o,textLength:4,lineOffset:1,background:[255,250,232],randomLineNum:15},n):4===t&&(Object.assign(r,{lineWidth:5,textColor:o,textLength:4,lineOffset:1,background:[255,250,232],randomLineNum:15},n),r.textColor=function(){return[Math.floor(256*Math.random()),Math.floor(256*Math.random()),Math.floor(256*Math.random())]}),this.config=r,this.data=Buffer.alloc(9e4)}setPixel(e,t,n){const o=3*(t*this.width+e);this.data[o]=n.b,this.data[o+1]=n.g,this.data[o+2]=n.r}getFileBuffer(e){const t=54+this.data.length,n=Buffer.alloc(t);return n.write("BM",0),n.writeUInt32LE(t,2),n.writeUInt32LE(0,6),n.writeUInt32LE(54,10),n.writeUInt32LE(40,14),n.writeInt32LE(this.width,18),n.writeInt32LE(-this.height,22),n.writeUInt16LE(1,26),n.writeUInt16LE(24,28),n.writeUInt32LE(0,30),n.writeUInt32LE(this.data.length,34),this.data.copy(n,54),n}setImageBackground(e,t,n){for(let o=0;o<this.width;o++)for(let r=0;r<this.height;r++)this.setPixel(o,r,{r:e,g:t,b:n})}setImageRandomBackground(e){for(let t=0;t<this.width;t+=e)for(let n=0;n<this.height;n+=e){const o=Math.floor(256*Math.random()),r=Math.floor(256*Math.random()),s=Math.floor(256*Math.random());for(let a=0;a<e;a++)for(let i=0;i<e;i++)t+a<this.width&&n+i<this.height&&this.setPixel(t+a,n+i,{r:o,g:r,b:s})}}draw(){const e=this.randomString(this.config.textLength);this.setImageBackground(...this.config.background);for(let t=0;t<e.length;t++){const n=e[t];this.drawText(n,t)}this.config.randomLineNum&&this.drawRandomLinesWithVaryingWidth(this.config.randomLineNum);const t=this.getFileBuffer(),n="data:image/bmp;base64,"+t.toString("base64");return{text:e,buffer:t,base64:n}}randomString(e){e=e||32;var t,n="0123456789".length,o="";for(t=0;t<e;t++)o+="0123456789".charAt(Math.floor(Math.random()*n));return o}drawText(e,t){const n=this.config.lineWidth,o=this.getTextOffset(t);"1"===e?this.drawLine(1,25,25,50+n,o):"2"===e?(this.drawLine(0,5,25,40,o),this.drawLine(1,45-n,25,25+n,o),this.drawLine(0,5,50,40,o),this.drawLine(1,5,50,25+n,o),this.drawLine(0,5,75,40,o)):"3"===e?(this.drawLine(0,5,25,40,o),this.drawLine(1,45-n,25,25+n,o),this.drawLine(0,5,50,40,o),this.drawLine(1,45-n,50,25+n,o),this.drawLine(0,5,75,40,o)):"4"===e?(this.drawLine(1,5,25,25+n,o),this.drawLine(1,45-n,25,25+n,o),this.drawLine(0,5,50,40,o),this.drawLine(1,45-n,50,25+n,o)):"5"===e?(this.drawLine(0,5,25,40,o),this.drawLine(1,5,25,25+n,o),this.drawLine(0,5,50,40,o),this.drawLine(1,45-n,50,25+n,o),this.drawLine(0,5,75,40,o)):"6"===e?(this.drawLine(0,5,25,40,o),this.drawLine(1,5,25,25+n,o),this.drawLine(0,5,50,40,o),this.drawLine(1,45-n,50,25+n,o),this.drawLine(0,5,75,40,o),this.drawLine(1,5,50,25+n,o)):"7"===e?(this.drawLine(0,5,25,40,o),this.drawLine(1,45-n,25,25+n,o),this.drawLine(1,45-n,50,25+n,o)):"8"===e?(this.drawLine(0,5,25,40,o),this.drawLine(0,5,50,40,o),this.drawLine(0,5,75,40,o),this.drawLine(1,5,25,25+n,o),this.drawLine(1,5,50,25+n,o),this.drawLine(1,45-n,25,25+n,o),this.drawLine(1,45-n,50,25+n,o)):"9"===e?(this.drawLine(0,5,25,40,o),this.drawLine(0,5,50,40,o),this.drawLine(0,5,75,40,o),this.drawLine(1,5,25,25+n,o),this.drawLine(1,45-n,25,25+n,o),this.drawLine(1,45-n,50,25+n,o)):"0"===e&&(this.drawLine(0,5,25,40,o),this.drawLine(0,5,75,40,o),this.drawLine(1,5,25,25+n,o),this.drawLine(1,5,50,25+n,o),this.drawLine(1,45-n,25,25+n,o),this.drawLine(1,45-n,50,25+n,o))}drawLine(e,t,n,o,r={x:0,y:0}){const s=this.config.lineWidth,a="function"==typeof this.config.textColor?this.config.textColor():this.config.textColor;if(t+=r.x,n+=r.y,0===e)for(let e=0;e<s;e++)for(let r=0;r<o;r++){const o=this.getRandomOffset();this.setPixel(t+r+o,n+e+o,{r:a[0],g:a[1],b:a[2]})}else if(1===e)for(let e=0;e<s;e++)for(let r=0;r<o;r++){const o=this.getRandomOffset();this.setPixel(t+e+o,n+r+o,{r:a[0],g:a[1],b:a[2]})}}getRandomOffset(){return 1===this.config.lineOffset?Math.floor(3*Math.random())-1:2===this.config.lineOffset?Math.floor(6*Math.random())-3:this.config.lineOffset>=3?Math.floor(11*Math.random())-6:0}getTextOffset(e=0){const t=this.config.textLength,n=Math.round((300-50*t)/2/t);return{x:e*(300/this.config.textLength)+n,y:0}}drawRandomLinesWithVaryingWidth(e){const t=this.width,n=this.height;for(let o=0;o<e;o++){const e=Math.floor(Math.random()*t),o=Math.floor(Math.random()*n),r=Math.floor(Math.random()*t),s=Math.floor(Math.random()*n),a=Math.floor(256*Math.random()),i=Math.floor(256*Math.random()),l=Math.floor(256*Math.random()),c=Math.floor(2*Math.random())+2;this.drawLineWithWidth(e,o,r,s,{r:a,g:i,b:l},c)}}drawLineWithWidth(e,t,n,o,r,s){const a=Math.abs(n-e),i=Math.abs(o-t),l=e<n?1:-1,c=t<o?1:-1;let u=a-i;for(;;){for(let n=0;n<s;n++)this.setPixel(e,t+n,r);if(e===n&&t===o)break;const p=2*u;p>-i&&(u-=i,e+=l),p<a&&(u+=a,t+=c)}}};function go(e){if(!e)return;if(Array.isArray(e))return e;if(!/^[0-9A-Fa-f]{6}$/.test(e))return;const t=(e=e.replace(/^#/,"")).substring(0,2),n=e.substring(2,4),o=e.substring(4,6);return[parseInt(t,16),parseInt(n,16),parseInt(o,16)]}const mo=Object.prototype.toString;function yo(e){return"[object Object]"===mo.call(e)}function vo(){"development"===process.env.NODE_ENV&&console.log(...arguments)}const bo=async function(){};function xo(e){return bo.constructor===e.constructor?async function(){const t=await e.apply(this,arguments);return yo(t)&&(t.msg&&(t.message=t.msg,t.errMsg=t.msg),0===t.code?t.errCode=t.code:t.errCode=s[t.code]||t.code),t}:function(){const t=e.apply(this,arguments);return yo(t)&&(t.msg&&(t.message=t.msg,t.errMsg=t.msg),0===t.code?t.errCode=t.code:t.errCode=s[t.code]||t.code),t}}const So=uniCloud.database(),Uo=So.collection("opendb-verify-codes");class To{async setVerifyCode({clientIP:e,deviceId:t,code:n,expiresDate:o,scene:r}){if(!t)return{code:10101,msg:"deviceId不可为空"};if(!n)return{code:10102,msg:"验证码不可为空"};o||(o=180);const s=Date.now(),a={device_uuid:t,scene:r,code:n.toLocaleLowerCase(),state:0,ip:e,created_date:s,expired_date:s+1e3*o};return vo("addRes",await Uo.add(a)),{code:0,deviceId:t}}async verifyCode({deviceId:e,code:t,scene:n}){if(!e)return{code:10101,msg:"deviceId不可为空"};if(!t)return{code:10102,msg:"验证码不可为空"};const o=Date.now(),r={device_uuid:e,scene:n,code:t.toLocaleLowerCase(),state:0},s=await Uo.where(r).orderBy("created_date","desc").limit(1).get();if(vo("verifyRecord:",s),s&&s.data&&s.data.length>0){const e=s.data[0];if(e.expired_date<o)return{code:10103,msg:"验证码已失效"};return vo("upRes",await Uo.doc(e._id).update({state:1})),{code:0,msg:"验证通过"}}return{code:10104,msg:"验证码错误"}}}let wo;try{wo=require("uni-config-center")}catch(e){throw new Error("Plugin[uni-config-center] was not found")}class Eo extends class{constructor(){this.pluginConfig={},this._initConfig(),this._initScene()}_initConfig(){const e=wo({pluginId:"uni-captcha"});if(e&&e.hasFile("config.json"))try{const t=e.config();yo(t)&&(this.pluginConfig=t)}catch(e){throw new Error(this.t("config-file-invalid")+"\n"+e.messages)}}_initScene(){if(yo(this.pluginConfig.scene)){const{scene:e,...t}=this.pluginConfig;Object.keys(e).forEach(n=>{e.scene&&delete e.scene,this.pluginConfig.scene[n]=Object.assign({},t,e[n])})}}}{constructor(){super(),this.DEVICEID2opts={}}mergeConfig(e){const t=yo(this.pluginConfig.scene)?this.pluginConfig.scene[e.scene]:e.scene;return Object.assign({},yo(t)?t:this.pluginConfig,e)}async create(e={}){if(!e.scene)throw new Error("scene验证码场景不可为空");e=this.mergeConfig(e);let{scene:t,expiresDate:n,deviceId:o,clientIP:r,...s}=e;if(o=o||__ctx__.DEVICEID,r=r||__ctx__.CLIENTIP,!o)throw new Error("deviceId不可为空");const a=new To;try{const{text:i,base64:l}=function(e={}){const{uniPlatform:t="",mode:n="svg"}=e;if("svg"===n){let n;n=e.mathExpr?ho.createMathExpr(e):ho.create(e);let o="data:image/svg+xml;utf8,"+n.data.replace(/#/g,"%23");return(!t||["mp-toutiao","h5","web","app","app-plus"].indexOf(t)>-1)&&(o=o.replace(/"/g,"'").replace(/</g,"%3C").replace(/>/g,"%3E")),{text:n.text,base64:o}}{const t=new fo(JSON.parse(JSON.stringify({...e,textLength:e.size?Number(e.size):void 0,textColor:go(e.color),background:go(e.background)}))),{text:n,base64:o}=t.draw();return{text:n,base64:o}}}(s),c=await a.setVerifyCode({clientIP:r,deviceId:o,code:i,expiresDate:n,scene:t});return c.code>0?{...c,code:10001}:(this.DEVICEID2opts[o]=e,{code:0,msg:"验证码获取成功",captchaBase64:l})}catch(e){return{code:10001,msg:"验证码生成失败:"+e.message}}}async verify({deviceId:e,captcha:t,scene:n}){if(!(e=e||__ctx__.DEVICEID))throw new Error("deviceId不可为空");if(!n)throw new Error("scene验证码场景不可为空");const o=new To;try{const r=await o.verifyCode({deviceId:e,code:t,scene:n});return r.code>0?r:{code:0,msg:"验证码通过"}}catch(e){return{code:10002,msg:"验证码校验失败:"+e.message}}}async refresh(e={}){let{scene:t,expiresDate:n,deviceId:o,...r}=e;if(o=o||__ctx__.DEVICEID,!o)throw new Error("deviceId不可为空");if(!t)throw new Error("scene验证码场景不可为空");const s=await Uo.where(So.command.or([{device_uuid:o,scene:t},{deviceId:o,scene:t}])).orderBy("created_date","desc").limit(1).get();if(s&&s.data&&s.data.length>0){const e=s.data[0];await Uo.doc(e._id).update({state:2}),Object.keys(r).length>0&&(this.DEVICEID2opts[o]=Object.assign({},this.DEVICEID2opts[o],r));let a={};try{a=await this.create(Object.assign({},this.DEVICEID2opts[o],{deviceId:o,scene:t,expiresDate:n}))}catch(e){return{code:50403,msg:e.message}}return a.code>0?{...a,code:50403}:{code:0,msg:"验证码刷新成功",captchaBase64:a.captchaBase64}}return{code:10003,msg:`验证码刷新失败:无此设备在 ${t} 场景信息,请重新获取`}}}const Oo=new To;Object.keys(Oo).forEach(e=>{Eo.prototype[e]=xo(Oo[e])});const Lo=new Eo,ko=new Proxy(Lo,{get(e,t){if(t in e)return"function"==typeof e[t]?xo(e[t]).bind(ko):e[t]}});module.exports=ko;
{
"name": "uni-captcha",
"version": "0.7.0",
"description": "uni-captcha",
"main": "index.js",
"homepage": "https://ext.dcloud.net.cn/plugin?id=4048",
"repository": {
"type": "git",
"url": "git+https://gitee.com/dcloud/uni-captcha"
},
"author": "DCloud",
"license": "Apache-2.0",
"dependencies": {
"uni-config-center": "file:../../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
}
}
\ No newline at end of file
// 开发文档: https://uniapp.dcloud.net.cn/uniCloud/cloud-obj
//导入验证码公共模块
const uniCaptcha = require('uni-captcha')
//获取数据库对象
const db = uniCloud.database();
//获取数据表opendb-verify-codes对象
const verifyCodes = db.collection('opendb-verify-codes')
module.exports = {
async getImageCaptcha({
scene,isUniAppX
}) {
//获取设备id
let {
deviceId,
platform
} = this.getClientInfo();
//根据:设备id、场景值、状态,查找记录是否存在
let res = await verifyCodes.where({
scene,
deviceId,
state: 0
}).limit(1).get()
//如果已存在则调用刷新接口,反之调用插件接口
let action = res.data.length ? 'refresh' : 'create'
//执行并返回结果
let option = {
scene, //来源客户端传递,表示:使用场景值,用于防止不同功能的验证码混用
uniPlatform: platform
}
if(isUniAppX){
option.mode = "bmp"
}
return await uniCaptcha[action](option)
}
}
\ No newline at end of file
{
"name": "uni-captcha-co",
"dependencies": {
"uni-captcha": "file:../common/uni-captcha",
"uni-config-center": "file:../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
},
"extensions": {
"uni-cloud-jql": {}
}
}
\ No newline at end of file
{
"bsonType": "object",
"properties": {
"_id": {
"description": "ID,系统自动生成"
},
"code": {
"bsonType": "string",
"description": "验证码"
},
"create_date": {
"bsonType": "timestamp",
"description": "创建时间"
},
"device_uuid": {
"bsonType": "string",
"description": "设备UUID,常用于图片验证码"
},
"email": {
"bsonType": "string",
"description": "邮箱"
},
"expired_date": {
"bsonType": "timestamp",
"description": "过期时间"
},
"ip": {
"bsonType": "string",
"description": "请求时客户端IP地址"
},
"mobile": {
"bsonType": "string",
"description": "手机号码"
},
"scene": {
"bsonType": "string",
"description": "使用验证码的场景,如:login, bind, unbind, pay"
},
"state": {
"bsonType": "int",
"description": "验证状态:0 未验证、1 已验证、2 已作废"
}
},
"required": []
}
\ No newline at end of file
## 1.0.1(2023-03-02)
- 修复 方法名错误
{
"id": "uni-cloud-s2s",
"displayName": "服务空间与服务器安全通讯模块",
"version": "1.0.1",
"description": "用于解决服务空间与服务器通讯时互相信任问题",
"keywords": [
"安全通讯",
"服务器请求云函数",
"云函数请求服务器"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"type": "unicloud-template-function",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"Vue": {
"vue2": "u",
"vue3": "u"
},
"App": {
"app-vue": "u",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "u",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}
\ No newline at end of file
# uni-cloud-s2s
文档见:[外部服务器如何与uniCloud安全通讯](https://uniapp.dcloud.net.cn/uniCloud/uni-cloud-s2s.html)
'use strict'; Object.defineProperty(exports, '__esModule', { value: !0 }); const e = require('crypto'); const t = require('path'); function s (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e } }require('fs'); const o = s(e); const n = s(t); const i = 'uni-cloud-s2s'; const r = { code: 5e4, message: 'Config error' }; const c = { code: 51e3, message: 'Access denied' }; class a extends Error {constructor (e) { super(e.message), this.errMsg = e.message || '', this.code = this.errCode = e.code, this.errSubject = e.subject, this.forceReturn = e.forceReturn || !1, this.cause = e.cause, Object.defineProperties(this, { message: { get () { return this.errMsg }, set (e) { this.errMsg = e } } }) }toJSON (e = 0) { if (!(e >= 10)) return e++, { errCode: this.errCode, errMsg: this.errMsg, errSubject: this.errSubject, cause: this.cause && this.cause.toJSON ? this.cause.toJSON(e) : this.cause } }} const d = Object.prototype.toString; const h = 50002; const u = Object.create(null); ['string', 'boolean', 'number', 'null'].forEach(e => { u[e] = function (t, s) { if ((function (e) { return d.call(e).slice(8, -1).toLowerCase() }(t)) !== e) return { code: h, message: `${s} is invalid` } } }); const f = 'Unicloud-S2s-Authorization'; class g {constructor (e) { const { config: t } = e || {}; this.config = t; const { connectCode: s } = t || {}; if (this.connectCode = s, !s || typeof s !== 'string') throw new a({ subject: i, code: r.code, message: 'Invalid connectCode in config' }) }getHeadersValue (e = {}, t, s) { const o = Object.keys(e || {}).find(e => e.toLowerCase() === t.toLowerCase()); return o ? e[o] : s }verifyHttpInfo (e) { const t = this.getHeadersValue(e.headers, f, ''); const [s = '', o = ''] = t.split(' '); if (s.toLowerCase() === 'CONNECTCODE'.toLowerCase() && o === this.config.connectCode) return !0; throw new a({ subject: i, code: c.code, message: `Invalid CONNECTCODE in headers['${f}']` }) }getSecureHeaders (e) { return { [f]: `CONNECTCODE ${this.config.connectCode}` } }} function l (e) { return function (t) { const { content: s, signKey: n } = t || {}; return o.default.createHash(e).update(s + '\n' + n).digest('hex') } } const p = { md5: l('md5'), sha1: l('sha1'), sha256: l('md5'), 'hmac-sha256': function (e) { const { content: t, signKey: s } = e || {}; return o.default.createHmac('sha256', s).update(t).digest('hex') } }; function m (e) { const { timestamp: t, data: s = {}, signKey: o, hashMethod: n = 'hmac-sha256' } = e || {}; const i = p[n]; const r = ['number', 'string', 'boolean']; const c = Object.keys(s).sort(); const a = []; for (let e = 0; e < c.length; e++) { const t = c[e]; const o = s[t]; const n = typeof o; r.includes(n) && a.push(`${t}=${o}`) } return i({ content: `${t}\n${a.join('&')}`, signKey: o }) } class w {constructor (e) { const { config: t } = e || {}; this.config = t; const { signKey: s, hashMethod: o = 'hmac-sha256', timeDiffTolerance: n = 60 } = t; if (!p[o]) throw new a({ subject: i, code: r.code, message: `Invalid hashMethod in config, expected "md5", "sha1", "sha256" or "hmac-sha256", got "${o}"` }); if (!s || typeof s !== 'string') throw new a({ subject: i, code: r.code, message: 'Invalid signKey in config' }); this.signKey = s, this.hashMethod = o, this.timeDiffTolerance = n }getHttpHeaders (e) { return e.headers || {} }getHeadersValue (e, t, s) { const o = Object.keys(e || {}).find(e => e.toLowerCase() === t.toLowerCase()); return o ? e[o] : s }getHttpData (e) { const t = e.httpMethod.toLowerCase(); const s = this.getHttpHeaders(e); const o = this.getHeadersValue(s, 'Content-Type', ''); if (t === 'get') return e.queryStringParameters; if (t !== 'post') throw new a({ subject: i, code: c.code, message: `Invalid http method, expected "POST" or "get", got "${t}"` }); if (o.indexOf('application/json') === 0) return JSON.parse(e.body); if (o.indexOf('application/x-www-form-urlencoded') === 0) return require('querystring').parse(e.body); throw new a({ subject: i, code: c.code, message: `Invalid content type of POST method, expected "application/json" or "application/x-www-form-urlencoded", got "${o}"` }) }verifyHttpInfo (e) { const t = e.headers || {}; const s = this.getHeadersValue(t, 'Unicloud-S2s-Timestamp', '0'); let [o, n] = this.getHeadersValue(t, 'Unicloud-S2s-Signature', '').split(' '); if (o = o.toLowerCase(), o !== this.hashMethod) throw new a({ subject: i, code: c.code, message: `Invalid hash method, expected "${this.hashMethod}", got "${o}"` }); const r = parseInt(s); const d = Date.now(); if (Math.abs(d - r) > 1e3 * this.timeDiffTolerance) throw new a({ subject: i, code: c.code, message: `Invalid timestamp, server timestamp is ${d}, ${r} exceed max timeDiffTolerance(${this.timeDiffTolerance} seconds)` }); return m({ timestamp: r, data: this.getHttpData(e), signKey: this.signKey, hashMethod: this.hashMethod }) === n }getSecureHeaders (e) { const { data: t } = e || {}; const s = Date.now(); const o = m({ timestamp: s, data: t, signKey: this.signKey, hashMethod: this.hashMethod }); return { 'Unicloud-S2s-Timestamp': s + '', 'Unicloud-S2s-Signature': this.hashMethod + ' ' + o } }} const y = require('uni-config-center')({ pluginId: i }); class b {constructor () { this.config = y.config(); const e = n.default.resolve(require.resolve('uni-config-center'), i, 'config.json'); if (!this.config) throw new a({ subject: i, code: r.code, message: `${i} config required, please check your config file: ${e}` }); if (this.config.type === 'connectCode') this.verifier = new g({ config: this.config }); else { if (!(function (e) { return e.type === 'sign' }(this.config))) throw new a({ subject: i, code: r.code, message: `Invalid ${i} config, expected policy is "code" or "sign", got ${this.config.policy}` }); this.verifier = new w({ config: this.config }) } }verifyHttpInfo (e) { if (!e) throw new a({ subject: i, code: c.code, message: 'Access denied, httpInfo required' }); return this.verifier.verifyHttpInfo(e) }getSecureHeaders (e) { return this.verifier.getSecureHeaders(e) }}exports.getSecureHeaders = function (e) { return (new b()).getSecureHeaders(e) }, exports.verifyHttpInfo = function (e) { const t = (new b()).verifyHttpInfo(e); if (!t) throw new a({ subject: i, code: c.code, message: c.message }); return t }
{
"name": "uni-cloud-s2s",
"version": "1.0.1",
"description": "",
"keywords": [],
"author": "DCloud",
"main": "index.js",
"dependencies": {
"uni-config-center": "file:../../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
}
}
\ No newline at end of file
## 0.0.3(2022-11-11)
- 修复 config 方法获取根节点为数组格式配置时错误的转化为了对象的Bug
## 0.0.2(2021-04-16)
- 修改插件package信息
## 0.0.1(2021-03-15)
- 初始化项目
{
"id": "uni-config-center",
"displayName": "uni-config-center",
"version": "0.0.3",
"description": "uniCloud 配置中心",
"keywords": [
"配置",
"配置中心"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "",
"type": "unicloud-template-function"
},
"directories": {
"example": "../../../scripts/dist"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "u",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "u",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "u"
}
}
}
}
}
# 为什么使用uni-config-center
实际开发中很多插件需要配置文件才可以正常运行,如果每个插件都单独进行配置的话就会产生下面这样的目录结构
```bash
cloudfunctions
└─────common 公共模块
├─plugin-a // 插件A对应的目录
│ ├─index.js
│ ├─config.json // plugin-a对应的配置文件
│ └─other-file.cert // plugin-a依赖的其他文件
└─plugin-b // plugin-b对应的目录
├─index.js
└─config.json // plugin-b对应的配置文件
```
假设插件作者要发布一个项目模板,里面使用了很多需要配置的插件,无论是作者发布还是用户使用都是一个大麻烦。
uni-config-center就是用了统一管理这些配置文件的,使用uni-config-center后的目录结构如下
```bash
cloudfunctions
└─────common 公共模块
├─plugin-a // 插件A对应的目录
│ └─index.js
├─plugin-b // plugin-b对应的目录
│ └─index.js
└─uni-config-center
├─index.js // config-center入口文件
├─plugin-a
│ ├─config.json // plugin-a对应的配置文件
│ └─other-file.cert // plugin-a依赖的其他文件
└─plugin-b
└─config.json // plugin-b对应的配置文件
```
使用uni-config-center后的优势
- 配置文件统一管理,分离插件主体和配置信息,更新插件更方便
- 支持对config.json设置schema,插件使用者在HBuilderX内编写config.json文件时会有更好的提示(后续HBuilderX会提供支持)
# 用法
在要使用uni-config-center的公共模块或云函数内引入uni-config-center依赖,请参考:[使用公共模块](https://uniapp.dcloud.net.cn/uniCloud/cf-common)
```js
const createConfig = require('uni-config-center')
const uniIdConfig = createConfig({
pluginId: 'uni-id', // 插件id
defaultConfig: { // 默认配置
tokenExpiresIn: 7200,
tokenExpiresThreshold: 600,
},
customMerge: function(defaultConfig, userConfig) { // 自定义默认配置和用户配置的合并规则,不设置的情况侠会对默认配置和用户配置进行深度合并
// defaudltConfig 默认配置
// userConfig 用户配置
return Object.assign(defaultConfig, userConfig)
}
})
// 以如下配置为例
// {
// "tokenExpiresIn": 7200,
// "passwordErrorLimit": 6,
// "bindTokenToDevice": false,
// "passwordErrorRetryTime": 3600,
// "app-plus": {
// "tokenExpiresIn": 2592000
// },
// "service": {
// "sms": {
// "codeExpiresIn": 300
// }
// }
// }
// 获取配置
uniIdConfig.config() // 获取全部配置,注意:uni-config-center内不存在对应插件目录时会返回空对象
uniIdConfig.config('tokenExpiresIn') // 指定键值获取配置,返回:7200
uniIdConfig.config('service.sms.codeExpiresIn') // 指定键值获取配置,返回:300
uniIdConfig.config('tokenExpiresThreshold', 600) // 指定键值获取配置,如果不存在则取传入的默认值,返回:600
// 获取文件绝对路径
uniIdConfig.resolve('custom-token.js') // 获取uni-config-center/uni-id/custom-token.js文件的路径
// 引用文件(require)
uniIDConfig.requireFile('custom-token.js') // 使用require方式引用uni-config-center/uni-id/custom-token.js文件。文件不存在时返回undefined,文件内有其他错误导致require失败时会抛出错误。
// 判断是否包含某文件
uniIDConfig.hasFile('custom-token.js') // 配置目录是否包含某文件,true: 文件存在,false: 文件不存在
```
\ No newline at end of file
## 1.0.14(2023-03-07)
- 修复 admin用户包含其他角色时未包含在token的Bug
## 1.0.13(2022-07-21)
- 修复 创建token时未传角色权限信息生成的token不正确的bug
## 1.0.12(2022-07-15)
- 提升与旧版本uni-id的兼容性(补充读取配置文件时回退平台app-plus、h5),但是仍推荐使用新平台名进行配置(app、web)
## 1.0.11(2022-07-14)
- 修复 部分情况下报`read property 'reduce' of undefined`的错误
## 1.0.10(2022-07-11)
- 将token存储在用户表的token字段内,与旧版本uni-id保持一致
## 1.0.9(2022-07-01)
- checkToken兼容token内未缓存角色权限的情况,此时将查库获取角色权限
## 1.0.8(2022-07-01)
- 修复clientDB默认依赖时部分情况下获取不到uni-id配置的Bug
## 1.0.7(2022-06-30)
- 修复config文件不合法时未抛出具体错误的Bug
## 1.0.6(2022-06-28)
- 移除插件内的数据表schema
## 1.0.5(2022-06-27)
- 修复使用多应用配置时报`Cannot read property 'appId' of undefined`的Bug
## 1.0.4(2022-06-27)
- 修复使用自定义token内容功能报错的Bug [详情](https://ask.dcloud.net.cn/question/147945)
## 1.0.2(2022-06-23)
- 对齐旧版本uni-id默认配置
## 1.0.1(2022-06-22)
- 补充对uni-config-center的依赖
## 1.0.0(2022-06-21)
- 提供uni-id token创建、校验、刷新接口,简化旧版uni-id公共模块
{
"id": "uni-id-common",
"displayName": "uni-id-common",
"version": "1.0.14",
"description": "包含uni-id token生成、校验、刷新功能的云函数公共模块",
"keywords": [
"uni-id-common",
"uniCloud",
"token",
"权限"
],
"repository": "https://gitcode.net/dcloud/uni-id-common",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "",
"type": "unicloud-template-function"
},
"uni_modules": {
"dependencies": ["uni-config-center"],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"Vue": {
"vue2": "u",
"vue3": "u"
},
"App": {
"app-vue": "u",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "u",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}
# uni-id-common
文档请参考:[uni-id-common](https://uniapp.dcloud.net.cn/uniCloud/uni-id-common.html)
\ No newline at end of file
"use strict";var e,t=(e=require("crypto"))&&"object"==typeof e&&"default"in e?e.default:e;const n={TOKEN_EXPIRED:"uni-id-token-expired",CHECK_TOKEN_FAILED:"uni-id-check-token-failed",PARAM_REQUIRED:"uni-id-param-required",ACCOUNT_EXISTS:"uni-id-account-exists",ACCOUNT_NOT_EXISTS:"uni-id-account-not-exists",ACCOUNT_CONFLICT:"uni-id-account-conflict",ACCOUNT_BANNED:"uni-id-account-banned",ACCOUNT_AUDITING:"uni-id-account-auditing",ACCOUNT_AUDIT_FAILED:"uni-id-account-audit-failed",ACCOUNT_CLOSED:"uni-id-account-closed"};function i(e){return!!e&&("object"==typeof e||"function"==typeof e)&&"function"==typeof e.then}function r(e){if(!e)return;const t=e.match(/^(\d+).(\d+).(\d+)/);return t?t.slice(1,4).map(e=>parseInt(e)):void 0}function o(e,t){const n=r(e),i=r(t);return n?i?function(e,t){const n=Math.max(e.length,t.length);for(let i=0;i<n;i++){const n=e[i],r=t[i];if(n>r)return 1;if(n<r)return-1}return 0}(n,i):1:i?-1:0}const s={"uni-id-token-expired":30203,"uni-id-check-token-failed":30202};function c(e){const{errCode:t,errMsgValue:n}=e;e.errMsg=this._t(t,n),t in s&&(e.code=s[t]),delete e.errMsgValue}function a(e){return"object"===(i=e,Object.prototype.toString.call(i).slice(8,-1).toLowerCase())&&e.errCode&&(t=e.errCode,Object.values(n).includes(t))&&!!e.errCode;var t,i}let u={"zh-Hans":{"uni-id-token-expired":"登录状态失效,token已过期","uni-id-check-token-failed":"token校验未通过","uni-id-param-required":"缺少参数: {param}","uni-id-account-exists":"此账号已注册","uni-id-account-not-exists":"此账号未注册","uni-id-account-conflict":"用户账号冲突","uni-id-account-banned":"从账号已封禁","uni-id-account-auditing":"此账号正在审核中","uni-id-account-audit-failed":"此账号审核失败","uni-id-account-closed":"此账号已注销"},en:{"uni-id-token-expired":"The login status is invalid, token has expired","uni-id-check-token-failed":"Check token failed","uni-id-param-required":"Parameter required: {param}","uni-id-account-exists":"Account exists","uni-id-account-not-exists":"Account does not exists","uni-id-account-conflict":"User account conflict","uni-id-account-banned":"Account has been banned","uni-id-account-auditing":"Account audit in progress","uni-id-account-audit-failed":"Account audit failed","uni-id-account-closed":"Account has been closed"}};try{const e=require.resolve("uni-config-center/uni-id/lang/index.js");u=function(e,t){const n=Object.keys(e);n.push(...Object.keys(t));const i={};for(let r=0;r<n.length;r++){const o=n[r];i[o]=Object.assign({},e[o],t[o])}return i}(u,require(e))}catch(e){}var d=u;function l(e){return e.replace(/=/g,"").replace(/\+/g,"-").replace(/\//g,"_")}function h(e){return JSON.parse((t=function(e){var t=4-(e=e.toString()).length%4;if(4!==t)for(var n=0;n<t;++n)e+="=";return e.replace(/-/g,"+").replace(/_/g,"/")}(e),Buffer.from(t,"base64").toString("utf-8")));var t}function f(e){return l((t=JSON.stringify(e),Buffer.from(t,"utf-8").toString("base64")));var t}function p(e,n){return l(t.createHmac("sha256",n).update(e).digest("base64"))}const k=function(e,t){if("string"!=typeof e)throw new Error("Invalid token");const n=e.split(".");if(3!==n.length)throw new Error("Invalid token");const[i,r,o]=n;if(p(i+"."+r,t)!==o)throw new Error("Invalid token");const s=h(i);if("HS256"!==s.alg||"JWT"!==s.typ)throw new Error("Invalid token");const c=h(r);if(1e3*c.exp<Date.now()){const e=new Error("Token expired");throw e.name="TokenExpiredError",e}return c},g=function(e,t,n={}){const{expiresIn:i}=n;if(!i)throw new Error("expiresIn is required");const r=parseInt(Date.now()/1e3),o={...e,iat:r,exp:r+n.expiresIn},s=f({alg:"HS256",typ:"JWT"})+"."+f(o);return s+"."+p(s,t)},I=uniCloud.database(),_=I.command,C=I.collection("uni-id-users"),m=I.collection("uni-id-roles");class T{constructor({uniId:e}={}){this.uid=null,this.userRecord=null,this.userPermission=null,this.oldToken=null,this.oldTokenPayload=null,this.uniId=e,this.config=this.uniId._getConfig(),this.clientInfo=this.uniId._clientInfo,this.checkConfig()}checkConfig(){const{tokenExpiresIn:e,tokenExpiresThreshold:t}=this.config;if(t>e)throw new Error("Config error, tokenExpiresThreshold should be less than tokenExpiresIn")}get customToken(){return this.uniId.interceptorMap.get("customToken")}isTokenInDb(e){return o(e,"1.0.10")>=0}async getUserRecord(){if(this.userRecord)return this.userRecord;const e=await C.doc(this.uid).get();if(this.userRecord=e.data[0],!this.userRecord)throw{errCode:n.ACCOUNT_NOT_EXISTS};switch(this.userRecord.status){case void 0:case 0:break;case 1:throw{errCode:n.ACCOUNT_BANNED};case 2:throw{errCode:n.ACCOUNT_AUDITING};case 3:throw{errCode:n.ACCOUNT_AUDIT_FAILED};case 4:throw{errCode:n.ACCOUNT_CLOSED}}if(this.oldTokenPayload){if(this.isTokenInDb(this.oldTokenPayload.uniIdVersion)){if(-1===(this.userRecord.token||[]).indexOf(this.oldToken))throw{errCode:n.CHECK_TOKEN_FAILED}}if(this.userRecord.valid_token_date&&this.userRecord.valid_token_date>1e3*this.oldTokenPayload.iat)throw{errCode:n.TOKEN_EXPIRED}}return this.userRecord}async updateUserRecord(e){await C.doc(this.uid).update(e)}async getUserPermission(){if(this.userPermission)return this.userPermission;const e=(await this.getUserRecord()).role||[];if(0===e.length)return this.userPermission={role:[],permission:[]},this.userPermission;if(e.includes("admin"))return this.userPermission={role:e,permission:[]},this.userPermission;const t=await m.where({role_id:_.in(e)}).get(),n=(i=t.data.reduce((e,t)=>(t.permission&&e.push(...t.permission),e),[]),Array.from(new Set(i)));var i;return this.userPermission={role:e,permission:n},this.userPermission}async _createToken({uid:e,role:t,permission:i}={}){if(!t||!i){const e=await this.getUserPermission();t=e.role,i=e.permission}let r={uid:e,role:t,permission:i};if(this.uniId.interceptorMap.has("customToken")){const n=this.uniId.interceptorMap.get("customToken");if("function"!=typeof n)throw new Error("Invalid custom token file");r=await n({uid:e,role:t,permission:i})}const o=Date.now(),{tokenSecret:s,tokenExpiresIn:c}=this.config,a=g({...r,uniIdVersion:"1.0.14"},s,{expiresIn:c}),u=await this.getUserRecord(),d=(u.token||[]).filter(e=>{try{const t=this._checkToken(e);if(u.valid_token_date&&u.valid_token_date>1e3*t.iat)return!1}catch(e){if(e.errCode===n.TOKEN_EXPIRED)return!1}return!0});return d.push(a),await this.updateUserRecord({last_login_ip:this.clientInfo.clientIP,last_login_date:o,token:d}),{token:a,tokenExpired:o+1e3*c}}async createToken({uid:e,role:t,permission:i}={}){if(!e)throw{errCode:n.PARAM_REQUIRED,errMsgValue:{param:"uid"}};this.uid=e;const{token:r,tokenExpired:o}=await this._createToken({uid:e,role:t,permission:i});return{errCode:0,token:r,tokenExpired:o}}async refreshToken({token:e}={}){if(!e)throw{errCode:n.PARAM_REQUIRED,errMsgValue:{param:"token"}};this.oldToken=e;const t=this._checkToken(e);this.uid=t.uid,this.oldTokenPayload=t;const{uid:i}=t,{role:r,permission:o}=await this.getUserPermission(),{token:s,tokenExpired:c}=await this._createToken({uid:i,role:r,permission:o});return{errCode:0,token:s,tokenExpired:c}}_checkToken(e){const{tokenSecret:t}=this.config;let i;try{i=k(e,t)}catch(e){if("TokenExpiredError"===e.name)throw{errCode:n.TOKEN_EXPIRED};throw{errCode:n.CHECK_TOKEN_FAILED}}return i}async checkToken(e,{autoRefresh:t=!0}={}){if(!e)throw{errCode:n.PARAM_REQUIRED,errMsgValue:{param:"token"}};this.oldToken=e;const i=this._checkToken(e);this.uid=i.uid,this.oldTokenPayload=i;const{tokenExpiresThreshold:r}=this.config,{uid:o,role:s,permission:c}=i,a={role:s,permission:c};if(!s&&!c){const{role:e,permission:t}=await this.getUserPermission();a.role=e,a.permission=t}if(!r||!t){const e={code:0,errCode:0,...i,...a};return delete e.uniIdVersion,e}const u=Date.now();let d={};1e3*i.exp-u<1e3*r&&(d=await this._createToken({uid:o}));const l={code:0,errCode:0,...i,...a,...d};return delete l.uniIdVersion,l}}var E=Object.freeze({__proto__:null,checkToken:async function(e,{autoRefresh:t=!0}={}){return new T({uniId:this}).checkToken(e,{autoRefresh:t})},createToken:async function({uid:e,role:t,permission:n}={}){return new T({uniId:this}).createToken({uid:e,role:t,permission:n})},refreshToken:async function({token:e}={}){return new T({uniId:this}).refreshToken({token:e})}});const w=require("uni-config-center")({pluginId:"uni-id"});class A{constructor({context:e,clientInfo:t,config:n}={}){this._clientInfo=e?function(e){return{appId:e.APPID,platform:e.PLATFORM,locale:e.LOCALE,clientIP:e.CLIENTIP,deviceId:e.DEVICEID}}(e):t,this.config=n||this._getOriginConfig(),this.interceptorMap=new Map,w.hasFile("custom-token.js")&&this.setInterceptor("customToken",require(w.resolve("custom-token.js"))),this._i18n=uniCloud.initI18n({locale:this._clientInfo.locale,fallbackLocale:"zh-Hans",messages:d})}setInterceptor(e,t){this.interceptorMap.set(e,t)}_t(...e){return this._i18n.t(...e)}_parseOriginConfig(e){return Array.isArray(e)?e:e[0]?Object.values(e):e}_getOriginConfig(){if(w.hasFile("config.json")){let e;try{e=w.config()}catch(e){throw new Error("Invalid uni-id config file\n"+e.message)}return this._parseOriginConfig(e)}try{return this._parseOriginConfig(require("uni-id/config.json"))}catch(e){throw new Error("Invalid uni-id config file")}}_getAppConfig(){const e=this._getOriginConfig();return Array.isArray(e)?e.find(e=>e.dcloudAppid===this._clientInfo.appId)||e.find(e=>e.isDefaultConfig):e}_getPlatformConfig(){const e=this._getAppConfig();if(!e)throw new Error(`Config for current app (${this._clientInfo.appId}) was not found, please check your config file or client appId`);let t;switch("app-plus"===this._clientInfo.platform&&(this._clientInfo.platform="app"),"h5"===this._clientInfo.platform&&(this._clientInfo.platform="web"),this._clientInfo.platform){case"web":t="h5";break;case"app":t="app-plus"}const n=[{tokenExpiresIn:7200,tokenExpiresThreshold:1200,passwordErrorLimit:6,passwordErrorRetryTime:3600},e];t&&e[t]&&n.push(e[t]),n.push(e[this._clientInfo.platform]);const i=Object.assign(...n);return["tokenSecret","tokenExpiresIn"].forEach(e=>{if(!i||!i[e])throw new Error(`Config parameter missing, ${e} is required`)}),i}_getConfig(){return this._getPlatformConfig()}}for(const e in E)A.prototype[e]=E[e];function y(e){const t=new A(e);return new Proxy(t,{get(e,t){if(t in e&&0!==t.indexOf("_")){if("function"==typeof e[t])return(n=e[t],function(){let e;try{e=n.apply(this,arguments)}catch(e){if(a(e))return c.call(this,e),e;throw e}return i(e)?e.then(e=>(a(e)&&c.call(this,e),e),e=>{if(a(e))return c.call(this,e),e;throw e}):(a(e)&&c.call(this,e),e)}).bind(e);if("context"!==t&&"config"!==t)return e[t]}var n}})}A.prototype.createInstance=y;const x={createInstance:y};module.exports=x;
{
"name": "uni-id-common",
"version": "1.0.14",
"description": "uni-id token生成、校验、刷新",
"main": "index.js",
"homepage": "https://uniapp.dcloud.io/uniCloud/uni-id-common.html",
"repository": {
"type": "git",
"url": "git+https://gitee.com/dcloud/uni-id-common.git"
},
"author": "DCloud",
"license": "Apache-2.0",
"dependencies": {
"uni-config-center": "file:../../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
}
}
\ No newline at end of file
.page {
background-color: #FFF;
width: 750rpx;
flex: 1;
}
.tip {
margin:0 15px;
font-size: 14px;
}
.form {
margin:10px 50rpx 0 50rpx;
}
.my-input {
margin-top:12px;
}
.agreements-box {
padding:8px 0;
}
.uni-btn{
width: 650rpx;
margin:8px 0;
}
\ No newline at end of file
import { state, mutations } from '@/uni_modules/uni-id-pages-x/store.uts';
import config from '@/uni_modules/uni-id-pages-x/config.uts';
export const loginSuccess = (_ : UTSJSONObject) => {
// console.log('loginSuccess', e);
// console.log("新用户uid", e["uid"]);
// state.currentUserInfo = uniCloud.getCurrentUserInfo()
mutations.updateUserInfo(null)
// state.userInfo["_id"] = e["uid"]
state.isLogin = true
uni.$emit('uni-id-pages-x-login-success', '')
// 登录后重定向设置
function loginAfterToPage() {
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
const uniIdRedirectUrl = currentPage.options.get("uniIdRedirectUrl");
if (uniIdRedirectUrl != null) {
// console.log('uniIdRedirectUrl', uniIdRedirectUrl);
uni.redirectTo({
url: uniIdRedirectUrl,
fail() {
console.error("uniIdRouter redirectTo fail");
uni.switchTab({
"url": uniIdRedirectUrl
})
},
success() {
// console.log('uniIdRouter redirectTo success');
}
})
} else {
uni.navigateBack()
}
};
const toastDuration = 1500
uni.showToast({
title: '登录成功',
duration: toastDuration,
icon: 'none'
});
// 避免 showToast 还没结束就跳转引起的崩溃
setTimeout(() => loginAfterToPage(), toastDuration);
}
export const logout = () => {
// console.log("logout");
// 1. 已经过期就不需要调用服务端的注销接口 2.即使调用注销接口失败,不能阻塞客户端
if (uniCloud.getCurrentUserInfo().tokenExpired > Date.now()) {
const uniIdCo = uniCloud.importObject("uni-id-co", { customUI: false })
uniIdCo.logout().finally(() => {
// 调完注销接口 跳转至登录页面
uni.redirectTo({
url: "/uni_modules/uni-id-pages-x/pages/login/login"
})
})
}
uni.removeStorageSync('uni_id_token');
uni.setStorageSync('uni_id_token_expired', 0)
state.userInfo = {}
state.isLogin = false
uni.$emit('uni-id-pages-x-logout', '')
}
export const checkPassword = (password : string) : UTSJSONObject => {
let res : UTSJSONObject = {
"pass": true,
"errMsg": null
}
// console.log("checkPassword", password);
// 根据配置的密码强度校验
let passwordStrength = config.getString("passwordStrength")
if (passwordStrength == null) {
return res
}
// 密码强度表达式
const passwordRules : UTSJSONObject = {
"super": {
"rule": "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[~!@#$%^&_\\-+=`|\\(){}\\[\\]:;\"'<>,.?/])[0-9a-zA-Z~!@#$%^&_\\-+=`|\\(){}\\[\\]:;\"'<>,.?/]{8,16}$",
"errMsg": "密码必须包含大小写字母、数字和特殊符号,长度范围:8-16位之间"
},
"strong": {
"rule": "^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[~!@#$%^&_\\-+=`|\\(){}\\[\\]:;\"'<>,.?/])[0-9a-zA-Z~!@#$%^&_\\-+=`|\\(){}\\[\\]:;\"'<>,.?/]{8,16}$",
"errMsg": "密密码必须包含字母、数字和特殊符号,长度范围:8-16位之间"
},
"medium": {
"rule": "^(?!.*[0-9]+$)(?!.*[a-zA-Z]+$)(?!.*[~!@#$%^&_\\-+=`|\\(){}\\[\\]:;\"'<>,.?/]+$)[0-9a-zA-Z~!@#$%^&_\\-+=`|\\(){}\\[\\]:;\"'<>,.?/]{8,16}$",
"errMsg": "密码必须为字母、数字和特殊符号任意两种的组合,长度范围:8-16位之间"
},
"weak": {
"rule": "^(?=.*[0-9])(?=.*[a-zA-Z])[0-9a-zA-Z~!@#$%^&_\\-+=`|\\(){}\\[\\]:;\"'<>,.?/]{6,16}$",
"errMsg": "密码必须包含字母和数字,长度范围:6-16位之间"
}
}
let passwordRule = passwordRules.getString(passwordStrength + '.rule');
let passwordRegExp = new RegExp(passwordRule as string);
res["pass"] = passwordRegExp.test(password);
res["errMsg"] = passwordRules.getString(passwordStrength + '.errMsg');
return res
}
\ No newline at end of file
<template>
<view @click="onClick" :style="{width,height}" style="justify-content: center;">
<image v-if="cSrc.length != 0" :style="{width,height}" :src="cSrc" :mode="mode"></image>
</view>
</template>
<script>
/**
* cloud-image
* @description 兼容普通资源和unicloud图片资源渲染的组件
* @property {String} mode 图片裁剪、缩放的模式。默认为widthFix,支持所有image组件的mode值
* @property {String} src 资源完了链接或uniCloud云存储资源的fileid
* @property {String} width 图片的宽,默认为:100rpx
* @property {String} height 图片的高,默认为:100rpx
* @event {Function} click 点击 cloud-image 触发事件
*/
export default {
name: "cloud-image",
emits: ['click'],
props: {
mode: {
type: String,
default: 'widthFix'
},
src: {
type: String,
default: ""
},
width: {
type: String,
default: '100rpx'
},
height: {
type: String,
default: '100rpx'
}
},
watch: {
src: {
handler(src : string) {
if (src.length != 0 && src.substring(0, 8) == "cloud://") {
uniCloud.getTempFileURL({
fileList: [src]
}).then((res : UniCloudGetTempFileURLResult) => {
// console.log('res=====',res);
this.cSrc = res.fileList[0].tempFileURL
})
} else {
this.cSrc = src
}
},
immediate: true
}
},
methods: {
onClick() {
this.$emit('click')
}
},
data() {
return {
cSrc: ""
};
}
}
</script>
\ No newline at end of file
<template>
<view class="agreements-root" v-if="agreements.length != 0">
<template v-if="needAgreements">
<checkbox-group @change="setAgree">
<checkbox class="checkbox" style="transform: scale(0.7);margin-right: -6px;padding-right: 3px;margin-left: 1px;"
:checked="!pendingAgreements" value="agreement">
<text class="checkbox-text">同意</text>
</checkbox>
</checkbox-group>
<view class="content">
<view class="agreements-item" v-for="(agreement,index) in agreements" :key="index">
<text class="agreement text" @click="navigateTo(index)">{{agreement.title}}</text>
<text class="text and" v-if="hasAnd(index)" space="nbsp"> 和 </text>
</view>
</view>
</template>
<!-- 弹出式 -->
<uni-popup ref="popup">
<view class="popup-content">
<view class="popup-header">
<uni-id-pages-x-icons :size="18" color="#E6A23C" type="advert" />
<text class="popup-title">请先阅读并同意</text>
</view>
<view class="content">
<view class="agreements-item" v-for="(agreement,index) in agreements" :key="index">
<text class="agreement popup-text" @click="navigateTo(index)">{{agreement.title}}</text>
<text class="popup-text and" v-if="hasAnd(index)" space="nbsp"> 和 </text>
</view>
</view>
<view class="btn-group">
<text class="btn" @click="cancel">取消</text>
<text class="btn confirm" @click="confirm">同意</text>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import config from '@/uni_modules/uni-id-pages-x/config.uts';
import {state} from '@/uni_modules/uni-id-pages-x/store.uts';
type Agreement = {url:string,title:string};
let confirmCallBack = ():void=>{} //console.log('未传入回调函数,value:')
export default {
name: "uni-id-pages-x-agreements",
computed: {
agreements():Agreement[]{
const agreements = config.getJSON('agreements')
if(agreements === null){
return []
}else{
return [
{
"url":agreements.getString('serviceUrl') as string,
"title":"用户服务协议"
},
{
"url":agreements.getString('privacyUrl') as string,
"title":"隐私政策条款"
}
] as Agreement[]
}
},
pendingAgreements():boolean{
return state.pendingAgreements
}
},
props: {
scope: {
type: String,
default: "login"
},
},
data() {
return {
needAgreements:true,
$popupComponent:null as null | UniPopupComponentPublicInstance
}
},
mounted() {
const scopeList = config.getArray<string>('agreements.scopeList')
if(scopeList == null){
this.needAgreements = false
}else{
this.needAgreements = scopeList.includes(this.scope)
}
state.pendingAgreements = this.needAgreements;
this.$popupComponent = (this.$refs['popup'] as UniPopupComponentPublicInstance)
// this.showPopupAgreements(()=>{
// console.log('showPopupAgreements after');
// })
uni.$on("uni-id-pages-x-agreements-shake",()=>{
// console.log('弹出申请同意隐私协议的框');
(this.$refs['popup-box'] as INode).style.setProperty("top",0)
})
},
methods: {
hasAnd(index:number) : boolean{
return this.agreements.length - 1 > index
},
navigateTo(index:number){
const title = this.agreements[index].title
const url = this.agreements[index].url
uni.navigateTo({
"url":"/uni_modules/uni-id-pages-x/pages/common/webview/webview?url="+url+"&title="+title,
fail:e=>{
console.error(e);
}
})
},
setAgree(event: CheckboxGroupChangeEvent){
state.pendingAgreements = event.detail.value.length == 0
},
showPopupAgreements(callback: () => void){
confirmCallBack = callback;
this.$popupComponent!.open()
},
cancel(){
state.pendingAgreements = true;
this.$popupComponent!.close()
},
confirm(){
state.pendingAgreements = false;
// console.log('state.pendingAgreements',state.pendingAgreements);
confirmCallBack();
this.$popupComponent!.close()
}
},
}
</script>
<style lang="scss" scoped>
.agreements-root {
flex-direction: row;
align-items: center;
justify-content: flex-start;
margin-left: -9px;
}
.checkbox-text {
transform: scale(1.3);
margin-left: 3px;
color: #8a8f8b;
font-size: 12px;
}
.agreements-item {
flex-direction: row;
}
.text {
line-height: 26px;
font-size: 12px;
}
.popup-text{
line-height: 30px;
font-size:14px;
}
.agreement {
color: #04498c;
/* #ifdef WEB */
cursor: pointer;
/* #endif */
}
.content {
padding: 5px 0;
flex-wrap: wrap;
flex-direction: row;
}
.popup-content {
background-color: #FFF;
padding-bottom: 0;
border-radius: 10px;
width: 600rpx;
padding: 15px;
}
.popup-header {
display: flex;
flex-direction: row;
align-items: center;
padding-top: 5px;
}
.popup-title {
font-size: 16px;
// text-align: center;
margin: 15px 0;
padding-left: 5px;
// color: #E6A23C;
}
.popup-content .btn-group {
border-top: 1px solid #eee;
flex-direction: row;
justify-content: space-around;
margin-top: 10px;
}
.popup-content .btn-group .btn {
flex: 1;
text-align: center;
color: #666;
height: 45px;
line-height: 45px;
font-size: 14px;
}
.popup-content .btn-group .btn.confirm {
color: #115ff7;
border-left: solid 1px #eee;
}
</style>
\ No newline at end of file
<template>
<view class="avatar-box-root">
<!-- #ifdef MP-WEIXIN -->
<button open-type="chooseAvatar" @chooseavatar="bindchooseavatar" @click="uploadAvatarImg" class="box"
:class="{'showBorder':border}" :style="{width,height,lineHeight:height}">
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<view open-type="chooseAvatar" @click="uploadAvatarImg" class="box" :class="{'showBorder':border}"
:style="{width,height}">
<!-- #endif -->
<!-- {{avatar_file!.getString("url")}} -->
<cloud-image v-if="avatar_file != null" :src="avatar_file!.getString('url')" :width="width" :height="height"></cloud-image>
<image v-else-if="readOnly" class="default-avatarUrl" src="@/uni_modules/uni-id-pages-x/static/default-avatar.png" mode="widthFix"></image>
<view v-else class="upload-img-icon">
<uni-id-pages-x-icons :size="35" color="#eee" type="jiahao"></uni-id-pages-x-icons>
</view>
<!-- #ifndef MP-WEIXIN -->
</view>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
</button>
<!-- #endif -->
</view>
</template>
<script>
import { state, mutations, UserInfo, AvatarFile } from '@/uni_modules/uni-id-pages-x/store.uts';
/**
* uni-id-pages-x-avatar
* @description 用户头像组件
* @property {String} width 图片的宽,默认为:50px
* @property {String} height 图片的高,默认为:50px
*/
export default {
name: "uni-id-pages-x-avatar",
data() {
return {
isPC: false
}
},
props: {
//头像图片宽
width: {
type: String,
default: "50px"
},
//头像图片高
height: {
type: String,
default: "50px"
},
border: {
type: Boolean,
default: false
},
readOnly: {
type: Boolean,
default: false
}
},
mounted() {
// #ifdef WEB
this.isPC = !['ios', 'android'].includes(uni.getSystemInfoSync().platform);
// #endif
},
computed: {
hasLogin() : boolean {
return state.isLogin as Boolean
},
userInfo() : UTSJSONObject {
return state.userInfo
},
avatar_file() : UTSJSONObject | null {
return this.userInfo.getJSON("avatar_file")
}
},
methods: {
setAvatarFile() {//avatar_file : AvatarFile
// 使用 clientDB 提交数据
// mutations.updateUserInfo({avatar_file})
},
bindchooseavatar() {
// res
// let avatarUrl = res.detail.avatarUrl
// let avatar_file = {
// extname: avatarUrl.split('.')[avatarUrl.split('.').length - 1],
// name:'',
// url:''
// }
// //上传到服务器
// let cloudPath = this.userInfo._id + '' + Date.now()
// avatar_file.name = cloudPath
// try{
// uni.showLoading({
// title: "更新中",
// mask: true
// });
// let {
// fileID
// } = await uniCloud.uploadFile({
// filePath:avatarUrl,
// cloudPath,
// fileType: "image"
// });
// avatar_file.url = fileID
// uni.hideLoading()
// }catch(e){
// console.error(e);
// }
// console.log('avatar_file',avatar_file);
// this.setAvatarFile(avatar_file)
},
uploadAvatarImg() {
if(this.readOnly){
return
}
// #ifdef MP-WEIXIN
return // 微信小程序走 bindchooseavatar方法
// #endif
// #ifndef MP-WEIXIN
// if(!this.hasLogin){
// uni.navigateTo({
// url:'/uni_modules/uni-id-pages-x/pages/login/login-withoutpwd'
// })
// return
// }
const crop : ChooseImageCropOptions = {
quality: 100,
width: 600,
height: 600,
resize: true
};
uni.chooseImage({
count: 1,
crop,
success(res) : void {
// console.log('res', res);
let tempFiles = res.tempFiles as UTSJSONObject[];
let tempFile = tempFiles[0];
let tempFileName = tempFile.getString('name')
let tempFilePath = tempFile.getString('path')
if (tempFileName == null) {
tempFileName = ""
}
if (tempFilePath == null) {
tempFilePath = ""
}
// console.log(9527,tempFileName.length);
// console.error('tempFileName',tempFileName);
// console.error('tempFilePath',tempFilePath);
let avatar_file = {
// #ifdef H5
extname: tempFileName.split(".")[tempFileName.split(".").length - 1],
// #endif
// #ifndef H5
extname: tempFilePath.split(".")[tempFilePath.split(".").length - 1],
// #endif
name: tempFileName,
url: tempFilePath
} as UTSJSONObject
console.error('avatar_file', avatar_file);
let filePath = res.tempFilePaths[0]
//非app端剪裁头像,app端用内置的原生裁剪
// #ifndef UNI-APP-X
filePath = await new Promise((callback) => {
// #ifdef H5
if (!this.isPC) {
callback(filePath)
}
// #endif
uni.navigateTo({
url: '/uni_modules/uni-id-pages-x/pages/userinfo/cropImage/cropImage?path=' +
filePath + `&options=${JSON.stringify(crop)}`,
animationType: "fade-in",
events: {
success: url => {
callback(url)
}
},
complete(e) {
// console.log(e);
}
});
})
// #endif
let _id = this.userInfo["_id"] as string
// if(_id != null){
// _id = "" as string
// }
let cloudPath = _id + '' + Date.now()
avatar_file.name = cloudPath
uni.showLoading({
title: "更新中",
mask: true
});
// console.log('filePath', filePath);
uniCloud.uploadFile({
filePath,
cloudPath,
})
.then<void>(function (res) {
// console.log('res777777777',res);
avatar_file.url = res.fileID
// console.log('avatar_file111', avatar_file);
mutations.updateUserInfo({ avatar_file } as UTSJSONObject)
})
.catch<void>(function (err : any | null) {
const error = err as UniCloudError
uni.showModal({
content: '上传失败,' + error.errMsg,
showCancel: false
});
console.error(error);
}).finally(() => {
uni.hideLoading()
})
},
fail(err) : void {
console.error('chooseImage fail: ', err)
uni.showModal({
content: '失败,' + err.errMsg,
showCancel: false
});
}
})
// #endif
}
}
}
</script>
<style>
.avatar-box-root {
background-color: #fff;
}
.box {
overflow: hidden;
padding: 0;
}
.chooseAvatar {
/* #ifndef UNI-APP-X */
display: inline-block;
box-sizing: border-box;
/* #endif */
border-radius: 100px;
text-align: center;
padding: 1px;
}
.upload-img-icon {
border: 1px dotted #c8c8c8;
width: 55px;
height: 55px;
justify-content: center;
align-items: center;
}
.showBorder {
border: solid 1px #ddd;
}
</style>
\ No newline at end of file
<template>
<view class="fab-login-box">
<template v-for="(item,index) in servicesList" :key="item.id">
<view class="item" @click="changeLoginType(index)">
<image class="logo" :src="item.logo" mode="scaleToFill"></image>
<text class="title">{{item.text}}</text>
</view>
</template>
</view>
</template>
<script>
// import config from '@/uni_modules/uni-id-pages-x/config.js'
//前一个窗口的页面地址。控制点击切换快捷登录方式是创建还是返回
// import {store,mutations} from '@/uni_modules/uni-id-pages-x/common/store.js'
// let allServicesList = []
import config from '@/uni_modules/uni-id-pages-x/config.uts';
import { state } from '@/uni_modules/uni-id-pages-x/store.uts';
type Services = {
id : string,
text : string,
logo : string
}
export default {
name: "uni-id-pages-x-fab-login",
props: {
currentLoginType: {
type: String
}
},
computed: {
agreements() {
// if (!config.agreements) {
// return []
// }
// let {
// serviceUrl,
// privacyUrl
// } = config.agreements
// return [{
// url: serviceUrl,
// title: "用户服务协议"
// },
// {
// url: privacyUrl,
// title: "隐私政策条款"
// }
// ]
},
servicesList() : Services[] {
const servicesList = [{
"id": "username",
"text": "账号登录",
"logo": "/uni_modules/uni-id-pages-x/static/login/uni-fab-login/user.png",
},
{
"id": "smsCode",
"text": "短信验证码",
"logo": "/uni_modules/uni-id-pages-x/static/login/uni-fab-login/sms.png",
},
{
"id": "weixin",
"text": "微信登录",
"logo": "/uni_modules/uni-id-pages-x/static/login/uni-fab-login/weixin.png",
},
{
"id": "weixinMobile",
"text": "微信手机号",
"logo": "/uni_modules/uni-id-pages-x/static/login/uni-fab-login/weixin.png",
},
// #ifndef MP-WEIXIN
{
"id": "apple",
"text": "苹果登录",
"logo": "/uni_modules/uni-id-pages-x/static/app-plus/uni-fab-login/apple.png",
},
{
"id": "univerify",
"text": "一键登录",
"logo": "/uni_modules/uni-id-pages-x/static/app-plus/uni-fab-login/univerify.png",
},
{
"id": "taobao",
"text": "淘宝登录", //暂未提供该登录方式的接口示例
"logo": "/uni_modules/uni-id-pages-x/static/app-plus/uni-fab-login/taobao.png",
},
{
"id": "facebook",
"text": "脸书登录", //暂未提供该登录方式的接口示例
"logo": "/uni_modules/uni-id-pages-x/static/app-plus/uni-fab-login/facebook.png",
},
{
"id": "alipay",
"text": "支付宝登录", //暂未提供该登录方式的接口示例
"logo": "/uni_modules/uni-id-pages-x/static/app-plus/uni-fab-login/alipay.png",
},
{
"id": "qq",
"text": "QQ登录", //暂未提供该登录方式的接口示例
"logo": "/uni_modules/uni-id-pages-x/static/app-plus/uni-fab-login/qq.png",
},
{
"id": "google",
"text": "谷歌登录", //暂未提供该登录方式的接口示例
"logo": "/uni_modules/uni-id-pages-x/static/app-plus/uni-fab-login/google.png",
},
{
"id": "douyin",
"text": "抖音登录", //暂未提供该登录方式的接口示例
"logo": "/uni_modules/uni-id-pages-x/static/app-plus/uni-fab-login/douyin.png",
},
{
"id": "sinaweibo",
"text": "新浪微博", //暂未提供该登录方式的接口示例
"logo": "/uni_modules/uni-id-pages-x/static/app-plus/uni-fab-login/sinaweibo.png",
}
// #endif
] as Services[]
const loginTypes = config.getArray<string>('loginTypes')
return servicesList.filter((item) : boolean => {
// #ifndef APP
//非app端去掉apple登录
if (item.id == 'apple') {
return false
}
// #endif
// #ifdef APP
//去掉非ios系统上的apple登录
if (item.id == 'apple' && uni.getSystemInfoSync().osName != 'ios') {
return false
}
// #endif
// 不能是当前已经显示的登录方式 && 必须是配置了的
return item.id != this.currentLoginType && loginTypes!.includes(item.id)
})
}
},
data() {
return {
// univerifyStyle: { //一键登录弹出窗的样式配置参数
// "fullScreen": true, // 是否全屏显示,true表示全屏模式,false表示非全屏模式,默认值为false。
// "backgroundColor": "#ffffff", // 授权页面背景颜色,默认值:#ffffff
// "buttons": { // 自定义登录按钮
// "iconWidth": "45px", // 图标宽度(高度等比例缩放) 默认值:45px
// "list": []
// },
// "privacyTerms": {
// "defaultCheckBoxState": false, // 条款勾选框初始状态 默认值: true
// "textColor": "#BBBBBB", // 文字颜色 默认值:#BBBBBB
// "termsColor": "#5496E3", // 协议文字颜色 默认值: #5496E3
// "prefix": "我已阅读并同意", // 条款前的文案 默认值:“我已阅读并同意”
// "suffix": "并使用本机号码登录", // 条款后的文案 默认值:“并使用本机号码登录”
// "privacyItems": []
// }
// }
}
},
watch: {
// TODO----
// agree(agree) {
// this.univerifyStyle.privacyTerms.defaultCheckBoxState = agree
// }
},
created() {
//处理一键登录
if (this.servicesList.map((i) : string => i.id).includes('univerify')) {
// console.log('处理一键登录');
// this.univerifyStyle.privacyTerms.privacyItems = this.agreements
// //设置一键登录功能底下的快捷登录按钮
// servicesList.forEach(({
// id,
// logo,
// }) => {
// if (id != 'univerify') {
// this.univerifyStyle.buttons.list.push({
// "iconPath": logo,
// "provider": id,
// })
// }
// })
}
},
methods: {
changeLoginType(index : number) {
const id = this.servicesList[index].id
if (!["username", "smsCode"].includes(id)) {
return uni.showToast({
title: 'uni-app x暂未支持此登录方式',
icon: 'none',
duration: 3000
});
}
this.$emit('changeLoginType', id)
if (["weixin", "apple", "univerify"].includes(id)) {
this.login_before(id)
}
},
toPage(path : string) {
// console.log(13, path);
},
login_before(type : string) {
// console.log(type);
// 提示空实现
if (["qq", "xiaomi", "sinaweibo", "taobao", "facebook", "google", "alipay", "douyin"].includes(type)) {
return uni.showToast({
title: '该登录方式暂未实现,欢迎提交pr',
icon: 'none',
duration: 3000
});
}
//检查当前环境是否支持这种登录方式
//判断是否需要弹出隐私协议授权框
const scope = config.getAny('agreements.scope') as string[]
// 配置中,当前类型是否需要,用户同意隐私协议
let needAgreements = scope.includes('register')
// 排除一键登录的情况(在一键登录的弹出层中选择) && !this.agree 未同意 && 需要同意
if (type != 'univerify' && needAgreements && !state.pendingAgreements) {
return uni.showToast({
title: '你未同意隐私政策协议',
icon: 'none'
});
// console.log('弹出申请同意隐私协议的框');
// uni.$emit("uni-id-pages-x-agreements-shake",'')
}
uni.showToast({
title: 'login_type:' + type,
icon: 'none'
});
// uni.login({
// "provider": type,
// "onlyAuthorize": true,
// // #ifdef APP
// // "univerifyStyle": this.univerifyStyle,
// // #endif
// success: async (e:any) => {
// console.log('eeeee',e);
// // if (type == 'apple') {
// // let res = await this.getUserInfo({
// // provider: "apple"
// // })
// // Object.assign(e.authResult, res.userInfo)
// // uni.hideLoading()
// // }
// // this.login(type == 'weixin' ? {
// // code: e.code
// // } : e.authResult, type)
// }
// // fail: async (err) => {
// // console.log(err);
// // uni.hideLoading()
// // }
// })
}
}
}
</script>
<style lang="scss" scoped>
.fab-login-box {
width: 750rpx;
position: fixed;
bottom: 30;
left: 0;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-around;
z-index: 10;
}
.fab-login-box .item {
flex-direction: column;
justify-content: center;
align-items: center;
/* #ifdef WEB */
cursor: pointer;
/* #endif */
}
.fab-login-box .item .logo {
width: 30px;
height: 30px;
border-radius: 100px;
border: solid 1px #F6F6F6;
}
.fab-login-box .item .title {
text-align: center;
color: #999;
font-size: 10px;
width: 70px;
height: 20px;
line-height: 20px;
}
</style>
\ No newline at end of file
<template>
<view>
<text class="uni-id-icon" :style="{color,'fontSize':size + 'px'}">{{iconCode}}</text>
</view>
</template>
<script>
export default {
props: {
type: {
type: String,
default: "",
required: true
},
color: {
type: String,
default: "#ccc"
},
size: {
type: Number,
default: 16
},
},
computed: {
iconCode() : string {
const iconCode : UTSJSONObject = {
"clear": "\ue622",
"down": "\ue600",
"right-arrow": "\ue8f1",
"jiahao": "\ue602",
"eyes": "\ue62b",
"advert": "\ue609"
};
return iconCode.getString(this.type) as string
}
},
data() {
return {}
}
}
</script>
<style>
@font-face {
font-family: "uni-id-icon";
src: url('/uni_modules/uni-id-pages-x/static/fonts/uni-id-icon.ttf');
}
.uni-id-icon {
font-family: "uni-id-icon";
font-size: 16px;
font-style: normal;
}
</style>
\ No newline at end of file
<template>
<view class="input-box" :class="{border}">
<!-- <solt></solt> -->
<text v-if="hasTitleAndContent" class="title">{{title}}</text>
<input class="input" ref="input" :placeholder="_placeholder" :class="{'move-down':hasTitleAndContent}"
:value="modelValue" @input="updateValue" :type="type" :maxlength="maxlength"
:password="password && !visiblePwd" :focus="focus" @blur="onBlur" @focus="onFocus" @confirm="confirm"
:confirm-type="confirmType" />
<view class="my-input-icon-box" v-if="modelValue">
<uni-id-pages-x-icons v-if="password" :color="visiblePwd?'#0070ff':'#ddd'"
@click="setVisiblePwd('click')" @touchstart="setVisiblePwd('touchstart')" @touchend="setVisiblePwd('touchend')" @touchcancel="setVisiblePwd('touchcancel')"
type="eyes" />
<view class="password-icon">
<uni-id-pages-x-icons @click="clearValue" type="clear" color="#ddd" />
</view>
</view>
</view>
</template>
<script>
export default {
name: 'MyInput',
emits: ["modelValue", "blur", "focus", "confirm"],
data() {
return {
visiblePwd: false
}
},
computed: {
_placeholder() : string {
if (this.placeholder.length > 0) {
return this.placeholder
} else if (this.title.length > 0) {
return "请输入" + this.title
} else {
return ""
}
},
hasTitleAndContent() : boolean {
return this.title.length > 0 && this.modelValue.length > 0
}
},
props: {
title: {
type: String,
default: ""
},
modelValue: {
type: String,
default: ""
},
type: {
type: String,
default: "text"
},
placeholder: {
type: String,
default: ""
},
maxlength: {
type: Number,
default: 140
},
password: {
type: Boolean,
default: false
},
focus: {
type: Boolean,
default: false
},
border: {
type: Boolean,
default: false
},
confirmType: {
type: String,
default: "done"
},
/**
* @param touch click
*/
visiblePwdOption:{
type: String,
default: "click"
}
},
methods: {
updateValue(event : InputEvent) {
// console.log('event.detail.value', event.detail.value);
this.$emit('update:modelValue', event.detail.value);
},
clearValue() {
this.$emit('update:modelValue', '');
this.setFocus(true)
},
onBlur() {
// this.$emit('update:focus', false);
this.$emit('blur');
},
onFocus() {
this.$emit('focus');
// this.$emit('update:focus', true);
},
setFocus(state : boolean) {
const inputEl = this.$refs['input'] as Element
if (state) {
inputEl.focus();
} else {
inputEl.blur();
}
},
confirm(event : InputConfirmEvent) {
this.$emit('confirm', event)
},
setVisiblePwd(eventName:string){
if(eventName == "click" && this.visiblePwdOption == 'click'){
this.visiblePwd = !this.visiblePwd
}else if(eventName == "touch"){
switch (eventName){
case "touchstart":
this.visiblePwd = true
break;
case "touchend":
this.visiblePwd = false
break;
case "touchcancel":
this.visiblePwd = false
break;
}
}
}
}
}
</script>
<style lang="scss" scoped>
.input-box {
position: relative;
margin: 0;
background-color: #F8F8F8;
height: 50px !important;
border-radius: 5rpx;
// border: 1px solid #000;
}
.border {
border: 1px solid #86868b;
}
.input {
border-radius: 5px;
height: 50px;
padding: 0 16rpx 0 12px;
font-size: 14px;
top: 0;
position: relative;
}
.title {
position: absolute;
top: 0;
left: 16rpx;
color: #555;
font-weight: 400;
font-size: 12px;
padding-left:5px;
height: 30px;
line-height: 30px;
}
.input.move-down {
top: 22px;
height: 22px;
}
.my-input-icon-box {
position: absolute;
right: 10px;
bottom:14px;
flex-direction: row;
}
.password-icon {
margin-left: 5px;
}
</style>
\ No newline at end of file
<template>
<view class="form">
<text class="pwd-login-title">账号密码登录</text>
<uni-id-pages-x-input class="my-input" title="账号" placeholder="请输入手机号/用户名/邮箱" v-model="username" ref="username"></uni-id-pages-x-input>
<uni-id-pages-x-input class="my-input" title="密码" v-model="password" :password="true" ref="password"></uni-id-pages-x-input>
<!-- 同意隐私政策协议 -->
<uni-id-pages-x-agreements class="agreements-box" ref="agreements" scope="login"/>
<button class="uni-btn" :type="password.length > 0 && username.length >0 ? 'primary' : '' " @click="loginByPwd">登录</button>
</view>
<view class="link-group">
<text class="link" @click="toRetrievePwd">忘记密码</text>
<text class="link" @click="toRegister">注册账号</text>
</view>
<!-- 多次登录错误时,获取登录时专用的图形验证码 (悬浮) -->
<uni-popup-captcha ref="popup-captcha" scene="login-by-pwd" v-model="loginCaptcha" title="请输入验证码"></uni-popup-captcha>
</template>
<script>
const uniIdCo = uniCloud.importObject("uni-id-co", { customUI: true })
import { state } from '@/uni_modules/uni-id-pages-x/store.uts';
import { loginSuccess } from '@/uni_modules/uni-id-pages-x/common/common.uts';
export default {
name: "uni-id-pages-x-loginByPwd",
data() {
return {
username: "",
password: "",
needCaptcha: false,
loginCaptcha: ""
}
},
mounted() {
setTimeout(() => {
(this.$refs['username'] as UniIdPagesXInputComponentPublicInstance).setFocus(true);
}, 300)
},
methods: {
showPopupCaptcha(callback : () => void) {
(this.$refs["agreements"] as UniIdPagesXAgreementsComponentPublicInstance).showPopupAgreements(callback)
},
loginByPwd() {
if (this.username.length == 0) {
(this.$refs['username'] as UniIdPagesXInputComponentPublicInstance).setFocus(true);
return uni.showToast({
title: '请输入手机号/用户名/邮箱',
icon: 'none',
duration: 3000
});
}
if (this.password.length == 0) {
(this.$refs['password'] as UniIdPagesXInputComponentPublicInstance).setFocus(true);
return uni.showToast({
title: '请输入密码',
icon: 'none',
duration: 3000
});
}
if (this.needCaptcha && this.loginCaptcha.length != 4) {
(this.$refs['popup-captcha'] as UniPopupCaptchaComponentPublicInstance).open(() => {
this.loginByPwd()
});
return uni.showToast({
title: '请输入验证码',
icon: 'none',
duration: 3000
});
}
const data : UTSJSONObject = {
"password": this.password,
"captcha": this.loginCaptcha
}
let keyName = 'username'
if (/^1\d{10}$/.test(this.username)) {
keyName = "mobile"
} else if (/@/.test(this.username)) {
keyName = "email"
}
data.set(keyName, this.username)
// console.log('data',data);
if (state.pendingAgreements) {
this.showPopupCaptcha(() => {
// console.log('state.pendingAgreements', state.pendingAgreements);
if (!state.pendingAgreements) {
this.loginByPwd()
}
})
return
}
uni.showLoading({
title: '登录中',
mask: false
});
uniIdCo.login(data)
.finally(() : void => {
uni.hideLoading()
this.loginCaptcha = ""
})
.then(e => {
// console.log('uniIdCo.login res',e);
loginSuccess(e)
})
.catch<void>((err : any | null) : void => {
const error = err as UniCloudError
console.error(error)
console.error(error.code)
if (error.code == 'uni-id-captcha-required') {
this.needCaptcha = true;
//登录失败,自动重新获取验证码
(this.$refs['popup-captcha'] as UniPopupCaptchaComponentPublicInstance).open(() => {
this.loginByPwd()
});
} else {
uni.showToast({
title: error.message,
icon: 'none',
duration: 3000
});
}
})
},
toRegister() {
uni.navigateTo({
url: "/uni_modules/uni-id-pages-x/pages/register/register"
})
},
toRetrievePwd() {
let url = "/uni_modules/uni-id-pages-x/pages/retrieve/retrieve"
if (/^1\d{10}$/.test(this.username)) {
url += "?mobile=" + this.username
} else if (/@/.test(this.username)) {
url += "?email=" + this.username
}
uni.navigateTo({ url })
},
}
}
</script>
<style lang="scss" scoped>
.pwd-login-title {
font-size: 14px;
font-weight: 700;
}
.link-group {
flex-direction: row;
width: 600rpx;
margin-left: 75rpx;
margin-top: 15px;
justify-content: space-between;
}
.link-group .link {
color: #083679;
font-size: 12px;
}
</style>
\ No newline at end of file
<template>
<view>
<text class="tip">未注册的账号验证通过后将自动注册</text>
<view class="form">
<!-- 获取验证码组件(输入手机号码+发送短信验证码所需的图形验证码,获得短信验证码) -->
<uni-id-pages-x-smsCode ref="smsCode" @input="smsCodeInput" ></uni-id-pages-x-smsCode>
<!-- 同意隐私政策协议 -->
<uni-id-pages-x-agreements class="agreements-box" ref="agreements" scope="login"/>
<button class="uni-btn" type="primary" @click="sendSmsCode">获取手机验证码</button>
</view>
<!-- 多次登录错误时,获取登录时专用的图形验证码 (悬浮) -->
<uni-popup-captcha ref="captcha" scene="login-by-sms" v-model="captcha" title="请输入验证码"></uni-popup-captcha>
</view>
</template>
<script>
import { state } from '@/uni_modules/uni-id-pages-x/store.uts';
import { loginSuccess } from '@/uni_modules/uni-id-pages-x/common/common.uts';
const uniIdCo = uniCloud.importObject("uni-id-co", { "customUI": true })
export default {
name: "uni-id-pages-x-loginBySmsCode",
data() {
return {
captcha: "",
$smsCodeRef: null as null | UniIdPagesXSmsCodeComponentPublicInstance
}
},
computed: {
},
mounted() {
this.$smsCodeRef = (this.$refs["smsCode"] as UniIdPagesXSmsCodeComponentPublicInstance)
},
methods: {
sendSmsCode() {
this.$smsCodeRef!.$callMethod('sendSmsCode');
},
smsCodeInput(param : UTSJSONObject) {
// console.log('smsCodeInput param', param);
const mobile = param.getString("mobile") as string;
const code = param.getString("code") as string;
if (mobile.length == 11 && code.length == 6) {
this.login(param)
}
},
showPopupCaptcha(callback : () => void) {
(this.$refs["agreements"] as UniIdPagesXAgreementsComponentPublicInstance).showPopupAgreements(callback)
},
login(param : UTSJSONObject) {
uni.showLoading({ "title": "登录中" })
uniIdCo.loginBySms(param)
.finally(() : void => {
uni.hideLoading()
})
.then<void>((e : UTSJSONObject) : void => {
// console.log('then');
// console.log(e);
this.$smsCodeRef!.$callMethod("hideCodeInput");
uni.showToast({
title: '登录成功',
icon: 'none'
});
loginSuccess(e)
this.$smsCodeRef!.$callMethod('clearCodeInput')
})
.catch<void>((err : any | null) : void => {
const error = err as UniCloudError
console.error(error)
console.error(error.code)
if (["uni-id-captcha-required", "uni-captcha-verify-fail"].includes(error.code as string)) {
(this.$refs["captcha"] as UniPopupCaptchaComponentPublicInstance).open(() => {
this.login(param)
});
} else {
uni.showToast({
title: error.message,
icon: 'none',
mask: false
});
this.$smsCodeRef!.$callMethod('clearCodeInput')
this.captcha = ""
}
})
}
}
}
</script>
<style>
.tip {
font-size: 12px;
padding: 0 0 5px 10px;
}
</style>
\ No newline at end of file
<template>
<uni-popup ref="popup">
<view class="dialog-box">
<text class="title">{{title}}</text>
<input class="input" ref="input" type="text" v-model="value" cursor-spacing="210" />
<view class="btn-group">
<text class="btn" @click="cancel">取消</text>
<text class="btn confirm" @click="confirm">确认</text>
</view>
</view>
</uni-popup>
</template>
<script>
let confirmCallBack = (_ : string) : void => {}// console.log('未传入回调函数,value:', value)
export default {
emits: ["confirm"],
data() {
return {
title: "默认标题" as string,
value: "" as string
}
},
methods: {
open(param : UTSJSONObject, callback : (value : string) => void) : void {
// console.log('param', param);
this.title = (param.getString("title") as string);
let value = param.getString("value")
if (value != null) {
this.value = value
}
confirmCallBack = callback;
(this.$refs['popup'] as UniPopupComponentPublicInstance).open();
this.$nextTick(() => {
(this.$refs['input'] as Element).focus()
})
},
cancel() {
(this.$refs['popup'] as UniPopupComponentPublicInstance).close();
this.value = "";
this.$emit('cancel')
},
confirm() {
if (this.value.length > 0) {
this.$emit('confirm', this.value)
confirmCallBack(this.value);
(this.$refs['popup'] as UniPopupComponentPublicInstance).close();
this.value = "";
} else {
uni.showToast({
title: '不能为空',
icon: 'none'
});
}
}
}
}
</script>
<style>
.dialog-box {
background-color: #FFF;
width: 600rpx;
border-radius: 5px;
}
.dialog-box .title {
font-weight: 700;
font-size: 16px;
text-align: center;
margin-top: 15px;
}
.dialog-box .input {
border: 1px solid #eee;
border-radius: 5px;
margin: 10px 20px;
height: 38px;
padding: 0 30rpx;
}
.dialog-box .btn-group {
border-top: 1px solid #eee;
flex-direction: row;
justify-content: space-around;
}
.dialog-box .btn-group .btn {
flex: 1;
text-align: center;
color: #666;
height: 45px;
line-height: 45px;
}
.dialog-box .btn-group .btn.confirm {
color: #115ff7;
border-left: solid 1px #eee;
}
</style>
\ No newline at end of file
<template>
<uni-popup ref="popup" :mask-click="false">
<view class="sms-code-content">
<!-- 顶部文字 -->
<text class="sms-code-title">{{title}}</text>
<text class="sms-code-tip">短信验证码已发送至{{mobile}}</text>
<view class="code-input-list">
<template v-for="(item,i) in smsCodeList" :key="i">
<input @input="setSmsCode(i,$event as InputEvent)" :value="item" type="number" ref="code-input"
class="code-input" maxlength="7" @focus="onFocus(i)" />
</template>
</view>
<view class="fab-sms-code-input-foot">
<text class="close" @click="clear();hide();">关闭</text>
<text @click="reGetSmsCode" :class="'get-sms-code-tip'+ ((reverseNumber == 0) ? '-active' : '')">{{reGetSmsCodeTip}}</text>
</view>
</view>
</uni-popup>
</template>
<script>
export default {
emits: ["modelValue", "blur", "focus", "reGetSmsCode"],
data() {
return {
smsCodeList: ["\u200b", "\u200b", "\u200b", "\u200b", "\u200b", "\u200b"],
isOpen: false
}
},
props: {
title: {
type: String,
default: "请输入验证码"
},
mobile: {
type: String,
default: ""
},
modelValue: {
type: String,
default: ""
},
reverseNumber: {
type: Number,
default: 0
},
},
computed: {
reGetSmsCodeTip() : string {
if (this.reverseNumber == 0) return "重新获取";
return "重新发送" + '(' + this.reverseNumber + 's)';
},
smsCode() : string {
return this.smsCodeList.map((item) : string => item.replace(/\u200b/g, '')).join("")
}
},
watch: {
smsCode(smsCode) {
this.$emit('update:modelValue', smsCode);
},
},
methods: {
show() {
(this.$refs['popup'] as UniPopupComponentPublicInstance).open();
this.$nextTick(() => {
(this.$refs["code-input"] as Element[])[0].focus();
})
this.isOpen = true
},
hide() {
(this.$refs['popup'] as UniPopupComponentPublicInstance).close();
this.isOpen = false
// console.log('hide success');
},
clear() {
this.smsCodeList = ["\u200b", "\u200b", "\u200b", "\u200b", "\u200b", "\u200b"];
if (this.isOpen) {
(this.$refs["code-input"] as Element[])[0].focus();
// console.log('clear success');
} else {
// console.log('已经关了,不能清空');
}
},
setSmsCode(i : number, e : InputEvent) {
const { value } = e.detail
// console.log('~~',value,value.length);
// 已满6位数就直接调登录
let $value = value.replace(/\u200b/g, '')
if ($value.length == 6) {
// (this.$refs["code-input"] as Element[])[0].focus();
// console.log('~~~~~', $value, $value.split(''));
$value.split('').forEach((item : string, index : number) => {
// console.log('index', index, item);
this.smsCodeList[index] = "\u200b" + item
})
return
}
// 限制每个空格内的文字不超过2位
if (value.length > 2) {
this.$nextTick(() => {
let newValue = value.slice(value.length - 1)
// console.log('newValue', newValue);
this.smsCodeList[i] = newValue
})
}
// 被删除完就直接后退一格
if (value.length == 0) {
this.smsCodeList[i] = ""
this.$nextTick(() => {
this.smsCodeList[i] = '\u200b'
})
if (i != 0) {
(this.$refs["code-input"] as Element[])[i - 1].focus();
this.smsCodeList[i - 1] = ""
}
} else if (value != "\u200b") {
this.smsCodeList[i] = value;
if (i != (this.smsCodeList.length - 1)) {
(this.$refs["code-input"] as Element[])[i + 1].focus();
} else {
// console.log(i, (this.smsCodeList.length - 1));
}
}
},
codeChange(i : number, e : InputEvent) {
// console.log(i, e);
},
onFocus(i : number) {
if (this.smsCodeList[i].length == 0) {
// console.log('i', i);
this.smsCodeList[i] = '\u200b'
}
},
reGetSmsCode() {
this.$emit("reGetSmsCode")
}
}
}
</script>
<style lang="scss" scoped>
.sms-code-content {
background-color: #FFF;
width: 550rpx;
justify-content: center;
align-items: center;
padding: 15px 20px;
border-radius: 5px;
}
.sms-code-title,
.sms-code-tip {
text-align: left;
padding-left:20px;
width: 550rpx;
}
.sms-code-tip {
color: #555;
font-weight: 400;
font-size: 12px;
margin-top: 5px;
}
.code-input-list {
flex-direction: row;
}
.code-input-list .code-input {
width: 30px;
height: 30px;
border: 1px solid #eee;
border-radius: 5px;
margin: 10px 5px;
text-align: center;
}
.fab-sms-code-input-foot {
margin-top: 5px;
margin-left: 300rpx;
position: relative;
left: -15px;
width: 250rpx;
flex-direction: row;
justify-content: flex-end;
}
.fab-sms-code-input-foot .close {
width: 80rpx;
font-size: 12px;
color: #888;
text-align: center;
margin-right: 26rpx;
}
.get-sms-code-tip,
.get-sms-code-tip-active {
font-size: 12px;
text-align: right;
color: #AAA;
}
.get-sms-code-tip-active {
color: #005eca;
}
</style>
\ No newline at end of file
<template>
<view class="mobile-box">
<view @click="chooseArea" class="area">
<text>+86</text>
<uni-id-pages-x-icons :size="10" color="#666" @click="chooseArea" type="down" />
</view>
<view class="input-box">
<uni-id-pages-x-input type="number" :border="false" v-model="mobile" :maxlength="11" placeholder="请输入11位手机号"
ref="mobileInput"></uni-id-pages-x-input>
</view>
</view>
<!-- 获取发送短信的验证码的图形验证码组件 -->
<view class="send-sms-captcha-box">
<uni-captcha ref="sendSmsCaptcha" scene="send-sms-code" v-model="sendSmsCaptcha" />
</view>
<!-- 悬浮的短信验证码输入框 -->
<fab-sms-code-input v-model="smsCode" :mobile="mobile" :reverseNumber="reverseNumber" ref="fab-sms-code-input"
@reGetSmsCode="reGetSmsCode" title="验证并登录" />
</template>
<script>
import fabSmsCodeInput from './fab-sms-code-input.uvue';
import { state } from '@/uni_modules/uni-id-pages-x/store.uts';
export default {
name: "uni-id-pages-x-smsCode",
components: { fabSmsCodeInput },
data() {
return {
mobile: "",
sendSmsCaptcha: "",
smsCode: "",
$fabSmsCodeInputEl: null as null | ComponentPublicInstance,
$sendSmsCaptchaCP: null as null | UniCaptchaComponentPublicInstance,
reverseNumber: 0
}
},
props: {
scene: {
type: String,
default: "login-by-sms"
},
autoSend: {
type: Boolean,
default: true
}
},
computed: {
},
watch: {
// 手机号输满11位时,自动给发送短信验证码的图形验证码“获取焦点”
mobile(mobile) {
this.emitInput()
if (mobile.length == 11) {
this.$sendSmsCaptchaCP!.setFocus(true);
// 倒计时归零,允许再次发送
this.reverseNumber = 0
}
},
// 图形验证码填好直接自动发送 短信验证码
sendSmsCaptcha() {
this.emitInput()
if (this.autoSend && this.sendSmsCaptcha.length == 4 && this.mobile.length == 11) {
this.sendSmsCode()
}
},
smsCode() {
this.emitInput()
}
},
mounted() {
this.$fabSmsCodeInputEl = this.$refs["fab-sms-code-input"] as ComponentPublicInstance;
this.$sendSmsCaptchaCP = this.$refs["sendSmsCaptcha"] as UniCaptchaComponentPublicInstance;
// 加载好,手机号码输入框就自动获取焦点
// TO 临时方案解决 this.$nextTick 无效,由setTimeout 300 代替
setTimeout(() => {
this.setMobileInputFocus(true)
}, 300)
},
methods: {
setMobileInputFocus(val:boolean){
(this.$refs['mobileInput'] as UniIdPagesXInputComponentPublicInstance).setFocus(val);
},
showCodeInput() {
if (reverseNumber == 0) {
this.reverseNumber = 6
//开始倒计时
let reverseTimer = function () { }
reverseTimer = () => {
if (this.reverseNumber != 0) {
this.reverseNumber--;
setTimeout(() => {
reverseTimer()
}, 1000)
}
}
reverseTimer()
}
this.$fabSmsCodeInputEl?.$callMethod('show', true);
},
emitInput() {
const param : UTSJSONObject = {
"mobile": this.mobile,
"code": this.smsCode,
"sendSmsCaptcha": this.sendSmsCaptcha,
}
this.$emit("input", param)
},
chooseArea() {
uni.showToast({
title: "目前只支持中国",
icon: "none"
});
},
// 重新获取短信验证码
reGetSmsCode() {
this.hideCodeInput();
this.clearCodeInput();
this.$sendSmsCaptchaCP!.getImageCaptcha(true);
},
hideCodeInput() {
this.$fabSmsCodeInputEl!.$callMethod("hide");
},
clearCodeInput() {
this.$fabSmsCodeInputEl!.$callMethod('clear')
},
reset() {
this.smsCode = ""
this.sendSmsCaptcha = ""
this.clearCodeInput()
this.hideCodeInput()
// console.log('reset');
},
sendSmsCode() {
// console.log("state.pendingAgreements", state.pendingAgreements);
if (state.pendingAgreements) {
// uni.hideKeyboard();
this.$sendSmsCaptchaCP!.setFocus(false);
(this.$parent as ComponentPublicInstance).$callMethod("showPopupCaptcha", () => {
// console.log('state.pendingAgreements', state.pendingAgreements);
if (!state.pendingAgreements) {
this.sendSmsCode()
}
});
uni.showToast({
title: '未同意隐私政策协议',
icon: 'none'
});
return
}
// console.log('sendSmsCode');
// 如果还在倒计时就显示出来输入框,阻止发送
if (this.reverseNumber != 0) {
return this.showCodeInput()
}
let reg_mobile = /^1\d{10}$/;
if (!reg_mobile.test(this.mobile)) {
(this.$refs['mobileInput'] as UniIdPagesXInputComponentPublicInstance).setFocus(true)
uni.showToast({
title: "手机号格式错误",
icon: 'none',
duration: 3000
})
return
}
if (this.sendSmsCaptcha.length != 4) {
this.$sendSmsCaptchaCP!.setFocus(true)
uni.showToast({
title: '请先输入图形验证码',
icon: 'none',
duration: 3000
});
return
}
// const param : UTSJSONObject
// console.log('sendSmsCode',{
// "mobile": this.mobile,
// "scene": "login-by-sms",
// "captcha": this.sendSmsCaptcha
// });
uni.showLoading({ "title": "发送中" })
const uniIdCo = uniCloud.importObject("uni-id-co", { "customUI": true })
uniIdCo.sendSmsCode({
"mobile": this.mobile,
"scene": this.scene,
"captcha": this.sendSmsCaptcha
})
.finally(() : void => {
uni.hideLoading()
})
.then((_ : UTSJSONObject) => {
uni.showToast({
title: "短信验证码发送成功",
icon: 'none',
duration: 3000
});
// console.log('result', result);
this.showCodeInput()
})
.catch<void>((err : any | null) : void => {
const error = err as UniCloudError
// console.error(error.message)
// console.error(error.code)
switch (error.code) {
case "uni-captcha-verify-fail":
uni.showToast({
title: error.message,
icon: 'none',
duration: 3000,
mask: false
});
this.$sendSmsCaptchaCP!.getImageCaptcha(true);
this.sendSmsCaptcha = "";
break;
case "uni-id-invalid-sms-template-id":
this.showCodeInput()
uni.showToast({
title: ' 当前为测试模式,详情【控制台信息】',
icon: 'none',
duration: 3000
});
console.warn(error.message);
break;
default:
uni.showToast({
title: error.message,
icon: 'none',
duration: 3000
});
break;
}
})
},
}
}
</script>
<style>
.mobile-box {
position: relative;
flex-direction: row;
background-color: #F8F8F8;
justify-content: center;
align-items: center;
border-radius: 5rpx;
}
.area {
width: 90rpx;
justify-content: space-around;
align-items: center;
flex-direction: row;
margin-left: 10px;
}
.mobile-box .input-box {
flex: 1;
}
.send-sms-captcha-box {
margin-top: 10px;
}
</style>
\ No newline at end of file
export default {
"debug":true,
"agreements":{
"serviceUrl": "https://uniapp.dcloud.io/", // 用户服务协议链接
"privacyUrl": "https://uniapp.dcloud.io/", // 隐私政策条款链接
/*
* 哪些场景下显示
* 1. register注册(包括登录并注册,如:短信验证码登录,一键登录)
* 2. login登录(如:用户名密码登录,短信验证码登录)
*/
"scopeList": [
"register","login"
]
},
"needLogin":[
"/uni_modules/uni-id-pages-x/pages/userinfo/*"
],
"loginTypes":[
"username",
"smsCode",
// 以下登录方式 uni-app x 暂不支持
// "qq",
// "xiaomi",
// "sinaweibo",
// "taobao",
// "facebook",
// "google",
// "alipay",
// "douyin",
// "weixin",
// #ifdef APP
// "univerify",
// "apple"
// #endif
],
/**
* 密码强度
* super(超强:密码必须包含大小写字母、数字和特殊符号,长度范围:8-16位之间)
* strong(强: 密密码必须包含字母、数字和特殊符号,长度范围:8-16位之间)
* medium (中:密码必须为字母、数字和特殊符号任意两种的组合,长度范围:8-16位之间)
* weak(弱:密码必须包含字母和数字,长度范围:6-16位之间)
* 为空或false则不验证密码强度
*/
passwordStrength: 'weak'
} as UTSJSONObject
// 导入 autoReportPushClientId 模块,将再重新登录会 token 续期后自动将客户端上报到服务端
// import reportPushClientId from '@/uni_modules/uni-id-pages-x/lib/autoReportPushClientId.uts'
// 导入配置
import config from '@/uni_modules/uni-id-pages-x/config.uts'
import {state} from '@/uni_modules/uni-id-pages-x/store.uts';
// uni-id的云对象
const uniIdCo = uniCloud.importObject('uni-id-co', {
customUI: true
})
// 用户配置的登录方式、是否打开调试模式
const loginTypes = config.getArray<string>('loginTypes');
const debug = config.getBoolean('debug') as boolean;
export default async function () {
// 有打开调试模式的情况下
if (debug) {
// 1. 检查本地uni-id-pages中配置的登录方式,服务器端是否已经配置正确。否则提醒并引导去配置
// 调用云对象,获取服务端已正确配置的登录方式
const res = await uniIdCo.getSupportedLoginType()
let supportedLoginType = res.getArray<string>('supportedLoginType')
if(supportedLoginType == null){
supportedLoginType = []
}
// 登录方式,服务端和客户端的映射关系
const data:UTSJSONObject = {
"smsCode": 'mobile-code',
"univerify": 'univerify',
"username": 'username-password',
"weixin": 'weixin',
"qq": 'qq',
"xiaomi": 'xiaomi',
"sinaweibo": 'sinaweibo',
"taobao": 'taobao',
"facebook": 'facebook',
"google": 'google',
"alipay": 'alipay',
"apple": 'apple',
"weixinMobile": 'weixin'
};
// 遍历客户端配置的登录方式,与服务端比对。并在错误时抛出错误提示
const list = loginTypes?.filter((type:string):boolean =>{
let currentType = data.getString(type);
if(currentType == null){
currentType = ""
}
return !(supportedLoginType.includes(currentType))
})
if (list?.length != 0) {
console.error(
`错误:前端启用的登录方式:${list?.join(',')};没有在服务端完成配置。配置文件路径:"/uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center/uni-id/config.json"`
)
}
}
// #ifdef UNI-APP-X
// 如果uni-id-pages配置的登录功能有一键登录,有则执行预登录(异步)
// if (loginTypes.includes('univerify')) {
// // uni.preLogin({
// // provider: 'univerify',
// // complete: e => {
// // // console.log(e);
// // }
// // })
// }
// #endif
// // 3. 绑定clientDB错误事件
// // clientDB对象
// const db = uniCloud.database()
// db.on('error', onDBError)
// // clientDB的错误提示
// function onDBError ({
// code, // 错误码详见https://uniapp.dcloud.net.cn/uniCloud/clientdb?id=returnvalue
// message
// }) {
// // console.error('onDBError', {code,message});
// }
// // 解绑clientDB错误事件
// // db.off('error', onDBError)
const checkNeedLogin = (url:string):boolean=>{
if(state.isLogin){
return false
}
const needLogin = config.getArray<string>('needLogin')
if (needLogin != null) {
return needLogin.every((item:string):boolean => {
return item == url || new RegExp(item).test(url)
})
}else{
return false
}
}
uni.addInterceptor('navigateTo',{
invoke:(e:NavigateToOptions)=>{
if (checkNeedLogin(e.url)) {
uni.showToast({
title: '请先登录',
icon: 'none'
})
e.url = "/uni_modules/uni-id-pages-x/pages/login/login?uniIdRedirectUrl=" + encodeURIComponent(e.url)
}
}
})
uni.addInterceptor('redirectTo',{
invoke:(e:RedirectToOptions)=>{
if (checkNeedLogin(e.url)) {
uni.showToast({
title: '请先登录',
icon: 'none'
})
e.url = "/uni_modules/uni-id-pages-x/pages/login/login?uniIdRedirectUrl=" + encodeURIComponent(e.url)
}
}
})
}
//同步客户端push_clientid至device表
const uniIdCo = uniCloud.importObject('uni-id-co', {
customUI: true
})
const reportPushClientId = () => {
uni.getPushClientId({
success: function (e) {
// console.log(e)
const pushClientId = e.cid
uniIdCo.setPushCid({
pushClientId
})
.then(res => {
// console.log('getPushClientId', res);
})
},
fail(e) {
// console.log(e)
}
})
}
export default reportPushClientId;
// console.log('已导入 autoReportPushClientId 模块,将再重新登录会 token 续期后自动将客户端上报到服务端');
uniCloud.onRefreshToken((_) => {
// console.log('onRefreshToken');
reportPushClientId()
})
\ No newline at end of file
{
"id": "uni-id-pages-x",
"displayName": "uni-id-pages-x",
"version": "0.0.0",
"description": "适用于 uni-app-x 的云端一体简单、统一、可扩展的用户中心页面模版",
"keywords": [
"用户管理",
"用户中心",
"短信验证码",
"login",
"登录",
"uni-id"
],
"repository": "",
"engines": {
"HBuilderX": "^3.97"
},
"dcloudext": {
"type": "uniapp-template-page",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"Vue": {
"vue2": "n",
"vue3": "y"
},
"App": {
"app-vue": "n",
"app-nvue": "n"
},
"H5-mobile": {
"Safari": "n",
"Android Browser": "n",
"微信浏览器(Android)": "n",
"QQ浏览器(Android)": "n"
},
"H5-pc": {
"Chrome": "n",
"IE": "n",
"Edge": "n",
"Firefox": "n",
"Safari": "n"
},
"小程序": {
"微信": "n",
"阿里": "n",
"百度": "n",
"字节跳动": "n",
"QQ": "n",
"钉钉": "n",
"快手": "n",
"飞书": "n",
"京东": "n"
},
"快应用": {
"华为": "n",
"联盟": "n"
}
}
}
}
}
\ No newline at end of file
<!-- 网络链接内容展示页(uni-id-pages-x中用于展示隐私政策协议内容) -->
<template>
<web-view class="web-view" v-if="url.length != 0" :src="url"></web-view>
</template>
<script>
export default {
onLoad(param : Map<string, string>) {
const url = param.get("url") as string;
let title = param.get("title") as string;
// console.log('url', url);
if (url.substring(0, 4) != 'http') {
uni.showModal({
title: "错误",
content: '不是一个有效的网站链接,' + '"' + url + '"',
showCancel: false,
confirmText: "知道了",
complete: () => {
uni.navigateBack()
}
});
title = "页面路径错误"
} else {
this.url = url;
}
if (title.length != 0) {
uni.setNavigationBarTitle({ title });
}
},
data() {
return {
url: ""
};
}
}
</script>
<style>
.web-view {
width: 750rpx;
flex: 1;
height: 100%;
}
</style>
\ No newline at end of file
<template>
<view class="page">
<!-- 顶部文字 -->
<!-- <text class="title">请选择登录方式{{loginType}}--isAgree:{{pendingAgreements}}</text> -->
<!-- 应用图标 -->
<image class="app-logo" :src="logo" mode="aspectFit"></image>
<!-- 苹果账号 微信账号 微信账号手机号 登录 -->
<view class="big-login-box" v-if="['apple','weixin', 'weixinMobile'].includes(loginType)">
<text class="tip">将根据第三方账号服务平台的授权范围获取你的信息</text>
<image v-if="loginType !== 'weixinMobile'" @click="login" :src="imgSrc" mode="widthFix" class="big-login-img">
</image>
<button v-else type="primary" open-type="getmobileNumber" @getmobilenumber="login"
class="uni-btn">微信授权手机号登录</button>
<!-- 同意隐私政策协议 -->
<uni-id-pages-x-agreements class="agreements-box" scope="login" />
</view>
<!-- 密码登录 -->
<uni-id-pages-x-loginByPwd v-if="loginType == 'username'" />
<!-- 手机号验证码登录 -->
<uni-id-pages-x-loginBySmsCode v-if="loginType == 'smsCode'" />
<!-- 固定定位的快捷登录按钮 -->
<uni-id-pages-x-fab-login :currentLoginType="loginType"
@changeLoginType="changeLoginType"></uni-id-pages-x-fab-login>
</view>
</template>
<script>
import { state } from '@/uni_modules/uni-id-pages-x/store.uts';
import config from '@/uni_modules/uni-id-pages-x/config.uts';
export default {
computed: {
//大快捷登录按钮图
imgSrc() : string {
return this.loginType == 'weixin' ? '/uni_modules/uni-id-pages-x/static/login/weixin.png' : '/uni_modules/uni-id-pages-x/static/app-plus/apple.png'
},
pendingAgreements() : boolean {
return state.pendingAgreements
}
},
data() {
return {
loginType: "username" as string,
logo: "/static/logo.png"
}
},
onLoad(param : Map<string, string>) {
const type = param.get("type")
// 如果传递了参数就按参数指定的登录方式,否则指定为 config 中配置的第一项
if (type != null) {
this.loginType = type
} else {
let loginTypes = config.getArray<string>("loginTypes");
this.loginType = loginTypes![0];
}
},
methods: {
login() {
},
changeLoginType(type : string) {
// if (["weixin", "apple", "univerify"].includes(type)) {
// }
this.loginType = type
}
}
}
</script>
<style lang="scss" scoped>
@import url("/uni_modules/uni-id-pages-x/common/common.scss");
.app-logo {
width: 150rpx;
height: 150rpx;
margin: 30px 300rpx;
}
.big-login-box .agreements-box {
justify-content: center;
}
.big-login-img {
margin: 10px 25rpx;
height: 60px;
width: 700rpx;
}
</style>
\ No newline at end of file
<template>
<view class="page">
<view class="form">
<view class="item">
<text class="required">*</text>
<uni-id-pages-x-input class="my-input" title="用户名" placeholder="请输入用户名(数字+字母的3~20位字符)" v-model="username"
ref="username"></uni-id-pages-x-input>
</view>
<view class="item">
<uni-id-pages-x-input class="my-input" title="昵称" placeholder="请输入用户名(限中文、数字、字母组成的3~20位字符)"
v-model="nickname" ref="nickname"></uni-id-pages-x-input>
</view>
<view class="item">
<text class="required">*</text>
<uni-id-pages-x-input class="my-input" title="设置密码" placeholder="请输入6位数密码" v-model="password"
:password="true" ref="password"></uni-id-pages-x-input>
</view>
<view class="item">
<text class="required">*</text>
<uni-id-pages-x-input class="my-input" title="重复密码" placeholder="请重新输入密码" v-model="password2" :password="true"
ref="password2"></uni-id-pages-x-input>
</view>
<view class="item mt">
<uni-captcha ref="captcha" scene="register" v-model="captcha" />
</view>
<!-- 隐私政策协议授权申请框 -->
<uni-id-pages-x-agreements class="agreements-box" ref="agreements" />
<button @click="registerBefore" type="primary">注册并登录</button>
</view>
</view>
</template>
<script>
import { loginSuccess, checkPassword } from '@/uni_modules/uni-id-pages-x/common/common.uts';
import { state } from '@/uni_modules/uni-id-pages-x/store.uts';
export default {
data() {
return {
username: "",
nickname: "",
password: "",
password2: "",
captcha: ""
}
},
methods: {
showPopupCaptcha(callback : () => void) {
(this.$refs["agreements"] as UniIdPagesXAgreementsComponentPublicInstance).showPopupAgreements(callback)
},
registerBefore() {
// TODO 为了自动化测试构建一个对象
let myForm:UTSJSONObject = {
username:this.username,
password:this.password,
password2: this.password2,
captcha:this.captcha
};
// 校验是否为空,并聚焦 (注意:这里使用数组格式,以确保校验按顺序执行)
[
["username", "用户名",],
["password", "密码",],
["captcha", "验证码"]
].forEach((item) => {
let key = item[0];
let errMsg = item[1] + "不能为空";
if (myForm.get(key) == "") {
// 给出错的项聚焦
(this.$refs[key] as ComponentPublicInstance).$callMethod('setFocus',true);
uni.showToast({
title: errMsg,
icon: "none",
duration: 3000
});
throw Error(errMsg)
}
});
// 校验用户名规则
const usernameRegExp = new RegExp("^(?=.*[a-z])(?=.*\\d)[a-z\\d]{3,20}$");
if (!usernameRegExp.test(this.username)) {
// 给出错的项聚焦
(this.$refs['username'] as UniIdPagesXInputComponentPublicInstance).setFocus(true);
return uni.showModal({
content: "用户名由数字和小写字母的3-20个字符组成",
showCancel: false,
confirmText: "知道了"
});
}
// 校验昵称规则
if (this.nickname.length > 0) {
const nicknameRegExp = new RegExp("^[\u4e00-\u9fa5a-z0-9]{3,20}$");
if (!nicknameRegExp.test(this.username)) {
// 给出错的项聚焦
(this.$refs['username'] as UniIdPagesXInputComponentPublicInstance).setFocus(true);
return uni.showModal({
content: "昵称由小写字母、数字、中文组成的3-20位字符串",
showCancel: false,
confirmText: "知道了"
});
}
}
let checkPasswordRes = checkPassword(this.password);
let isPass = checkPasswordRes.getBoolean("pass") as Boolean
if (!isPass) {
const errMsg = checkPasswordRes.getString("errMsg")
return uni.showModal({
content: errMsg,
showCancel: false,
confirmText: "知道了",
complete() {
(this.$refs['password'] as UniIdPagesXInputComponentPublicInstance).setFocus(true);
}
});
}
if (this.password != this.password2) {
return uni.showModal({
content: "两次输入的密码不一致",
showCancel: false,
confirmText: "知道了",
complete() {
(this.$refs["password2"] as UniIdPagesXInputComponentPublicInstance).setFocus(true);
}
});
}
if (this.captcha.length != 4) {
return uni.showModal({
content: "请输入验证码",
showCancel: false,
confirmText: "知道了",
complete() {
(this.$refs["captcha"] as UniCaptchaComponentPublicInstance).setFocus(true);
}
});
}
if (state.pendingAgreements) {
this.showPopupCaptcha(() => {
// console.log('state.pendingAgreements', state.pendingAgreements);
if (!state.pendingAgreements) {
this.registerBefore()
}
})
return
}
this.register()
},
register() {
const data: UTSJSONObject = {
"username": this.username,
"nickname": this.nickname,
"password": this.password,
"password2": this.password2,
"captcha": this.captcha
}
const uniIdCo = uniCloud.importObject("uni-id-co", { customUI: true });
uni.showLoading({ "title": "请求中" })
uniIdCo.registerUser(data)
.finally(() => uni.hideLoading())
.then(e => {
// console.log('e: ',e);
uni.showToast({
title: '注册成功',
icon: 'none'
});
loginSuccess(e)
})
.catch<void>((err : any | null) : void => {
const error = err as UniCloudError
console.error(error)
console.error(error.code)
if (error.code == 'uni-captcha-verify-fail') {
(this.$refs["captcha"] as UniCaptchaComponentPublicInstance).getImageCaptcha(true);
}
uni.showToast({
title: error.message,
icon: 'none'
});
})
}
}
}
</script>
<style lang="scss" scoped>
@import url("/uni_modules/uni-id-pages-x/common/common.scss");
.form {
padding-left: 15px;
}
.item {
margin-top: 8px;
position: relative;
padding-left: 15px;
margin-left: -15px;
}
.mt{
margin-top: 16px;
}
.required {
position: absolute;
color: red;
left: 0px;
top: 15px;
}
</style>
\ No newline at end of file
<template>
<view class="page">
<view class="form">
<!-- 获取验证码组件(输入手机号码+发送短信验证码所需的图形验证码,获得短信验证码) -->
<uni-id-pages-x-smsCode scene="reset-pwd-by-sms" ref="smsCode" @input="smsCodeInput"
:autoSend="false"></uni-id-pages-x-smsCode>
<uni-id-pages-x-input class="my-input" :border="false" v-model="password" placeholder="请输入新密码" ref="password"
:password="true"></uni-id-pages-x-input>
<uni-id-pages-x-input class="my-input" :border="false" v-model="password2" placeholder="请再次输入密码" ref="password2"
:password="true"></uni-id-pages-x-input>
<button class="uni-btn" type="primary" @click="doNext">下一步</button>
</view>
<!-- 多次重置输入错误时,需要额外输入图形验证码 (悬浮) -->
<uni-popup-captcha ref="captcha" scene="reset-pwd-by-sms" v-model="captcha" title="请输入验证码"></uni-popup-captcha>
</view>
</template>
<script>
import { checkPassword } from '@/uni_modules/uni-id-pages-x/common/common.uts';
const uniIdCo = uniCloud.importObject("uni-id-co", { "customUI": true })
export default {
data() {
return {
smsCode: "",
$smsCodeRef: null as null | UniIdPagesXSmsCodeComponentPublicInstance,
password: "",
password2: "",
captcha: ""
}
},
mounted() {
this.$smsCodeRef = (this.$refs["smsCode"] as UniIdPagesXSmsCodeComponentPublicInstance)
},
onLoad(param : Map<string, string>) {
const mobile = param.get("mobile")
// const email = param.get("email")
// console.log('mobile--', mobile);
// console.log('email--', email);
if (mobile != null) {
this.$nextTick(() => {
this.$smsCodeRef!.mobile = mobile
})
}
},
methods: {
smsCodeInput(param : UTSJSONObject) {
// console.log('smsCodeInput param', param);
const mobile = param.getString("mobile") as string;
const code = param.getString("code") as string;
const sendSmsCaptcha = param.getString("sendSmsCaptcha") as string;
if (sendSmsCaptcha.length == 4 && code.length == 0) {
(this.$refs["password"] as UniIdPagesXInputComponentPublicInstance).setFocus(true);
}
if (mobile.length == 11 && code.length == 6) {
this.resetPwdBySms(param)
}
},
doNext() {
// 根据配置的密码强度校验
let checkPasswordRes = checkPassword(this.password);
let isPass = checkPasswordRes.getBoolean("pass") as Boolean
if (!isPass) {
const errMsg = checkPasswordRes.getString("errMsg")
return uni.showModal({
title:"提示",
content: errMsg,
showCancel: false,
confirmText: "知道了",
complete() {
(this.$refs["password"] as UniIdPagesXInputComponentPublicInstance).setFocus(true);
}
});
}
if (this.password != this.password2) {
return uni.showModal({
title:"提示",
content: "两次输入的密码不一致",
showCancel: false,
confirmText: "知道了",
complete() {
(this.$refs["password2"] as UniIdPagesXInputComponentPublicInstance).setFocus(true);
}
});
}
this.$smsCodeRef!.sendSmsCode();
},
resetPwdBySms(param : UTSJSONObject) {
uni.showLoading({ "title": "请求中" })
param.set("password", this.password)
param.set("captcha", this.captcha)
uniIdCo.resetPwdBySms(param)
.finally(() : void => {
uni.hideLoading()
})
.then<void>((_ : UTSJSONObject) : void => {
// console.log('then');
// console.log(e);
this.$smsCodeRef!.hideCodeInput();
uni.showToast({
title: '重置成功',
icon: 'none'
});
uni.navigateBack()
})
.catch<void>((err : any | null) : void => {
const error = err as UniCloudError
console.error(error)
console.error(error.code)
if (["uni-id-captcha-required", "uni-captcha-verify-fail"].includes(error.code as string)) {
(this.$refs["captcha"] as UniPopupCaptchaComponentPublicInstance).open(() => {
this.resetPwdBySms(param)
});
} else {
uni.showToast({
title: error.message,
icon: 'none',
mask: false
});
this.$smsCodeRef!.clearCodeInput()
this.captcha = ""
}
})
},
}
}
</script>
<style>
@import url("/uni_modules/uni-id-pages-x/common/common.scss");
.root {
background-color: #FFF;
width: 750rpx;
flex: 1;
}
.form {
margin:50rpx 50rpx 0 50rpx;
}
</style>
\ No newline at end of file
<template>
<view class="page">
<view class="form">
<!-- 获取验证码组件(输入手机号码+发送短信验证码所需的图形验证码,获得短信验证码) -->
<uni-id-pages-x-smsCode scene="bind-mobile-by-sms" ref="smsCode"
@input="smsCodeInput"></uni-id-pages-x-smsCode>
<button class="uni-btn" type="primary" @click="sendSmsCode">获取验证码</button>
</view>
<!-- 多次登录错误时,获取登录时专用的图形验证码 (悬浮) -->
<uni-popup-captcha @cancel="popupCaptchaCancel" ref="popup-captcha" scene="bind-mobile-by-sms" v-model="captcha"
title="请输入验证码"></uni-popup-captcha>
</view>
</template>
<script>
import { state, mutations } from '@/uni_modules/uni-id-pages-x/store.uts';
export default {
data() {
return {
$smsCodeEl: null as null | UniIdPagesXSmsCodeComponentPublicInstance,
needCaptcha: false,
captcha: ""
}
},
computed: {
},
watch: {
},
mounted() {
this.$smsCodeEl = (this.$refs["smsCode"] as UniIdPagesXSmsCodeComponentPublicInstance)
},
methods: {
sendSmsCode() {
this.$smsCodeEl!.sendSmsCode();
},
smsCodeInput(param : UTSJSONObject) {
// console.log('smsCodeInput param', param);
const mobile = param.getString("mobile") as string;
const code = param.getString("code") as string;
if (mobile.length == 11 && code.length == 6) {
this.bindMobileBySms(param)
}
},
bindMobileBySms(param : UTSJSONObject) {
// 设置(添加)验证码并重新发起
function setCaptchaRetry() {
uni.showToast({
title: '请输入验证码',
icon: 'none',
duration: 3000
});
(this.$refs['popup-captcha'] as UniPopupCaptchaComponentPublicInstance).open(() => {
this.bindMobileBySms(param)
});
}
if (this.needCaptcha) {
if (this.captcha.length != 4) {
return setCaptchaRetry()
} else {
param.set("captcha", this.captcha)
}
}
uni.showLoading({ "title": "请求中" })
const uniIdCo = uniCloud.importObject("uni-id-co", { "customUI": true })
uniIdCo.bindMobileBySms(param)
.finally(() : void => {
uni.hideLoading()
})
.then<void>((e : UTSJSONObject) : void => {
// console.log('then');
// console.log(e);
uni.showToast({
title: '绑定成功',
icon: 'none',
duration: 3000,
complete() {
uni.navigateBack()
}
});
mutations.updateUserInfo({
"mobile": param.getString("mobile")
} as UTSJSONObject)
})
.catch<void>((err : any | null) : void => {
const error = err as UniCloudError
console.error(error)
console.error(error.code)
if (['uni-id-captcha-required', 'uni-captcha-verify-fail'].includes(error.code as string)) {
this.needCaptcha = true;
//登录失败,自动重新获取验证码
return setCaptchaRetry()
} else if (error.code == "uni-id-mobile-verify-code-error") {
this.$smsCodeEl!.clearCodeInput();
} else {
this.$smsCodeEl!.reset();
this.captcha = ""
}
uni.showToast({
title: error.message,
icon: 'none',
duration: 3000,
mask: false
});
})
},
popupCaptchaCancel() {
// console.log('popupCaptchaCancel');
this.$smsCodeEl!.reset();
this.captcha = ""
}
}
}
</script>
<style>
@import url("/uni_modules/uni-id-pages-x/common/common.scss");
.uni-btn {
margin-top: 10px;
}
</style>
<!-- 注销(销毁)账号 -->
<template>
<view class="uni-content">
<scroll-view class="scroll-view" :scroll-y="true">
<text class="words" :decode="true">{{words}}</text>
</scroll-view>
<view class="button-group">
<button @click="nextStep" class="btn next" type="default">下一步</button>
<button @click="cancel" class="btn" type="primary">取消</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
words: `一、注销是不可逆操作,注销后:\n
1.帐号将无法登录、无法找回。\n
2.帐号所有信息都会清除(个人身份信息、粉丝数等;发布的作品、评论、点赞等;交易信息等),你的朋友将无法通过本应用帐号联系你,请自行备份相关信息和数据。\n
二、重要提示\n
1.封禁帐号(永久封禁、社交封禁、直播权限封禁)不能申请注销。\n
2.注销后,你的身份证、三方帐号(微信、QQ、微博、支付宝)、手机号等绑定关系将解除,解除后可以绑定到其他帐号。\n
3.注销后,手机号可以注册新的帐号,新帐号不会存在之前帐号的任何信息(作品、粉丝、评论、个人信息等)。\n
4.注销本应用帐号前,需尽快处理帐号下的资金问题。\n
5.视具体帐号情况而定,注销最多需要7天。`
}
},
onLoad() { },
methods: {
cancel() {
uni.navigateBack()
},
nextStep() {
uni.showModal({
content: '已经仔细阅读注销提示,知晓可能带来的后果,并确认要注销',
cancelText: "返回",
confirmText: "确定",
success: (e) => {
// console.log('confirm', e.confirm);
if (e.confirm) {
const uniIdco = uniCloud.importObject("uni-id-co");
uniIdco.closeAccount().then(() => {
uni.showToast({
title: '注销成功',
icon: 'none',
duration: 3000
});
uni.removeStorageSync('uni_id_token');
uni.setStorageSync('uni_id_token_expired', 0)
uni.navigateTo({
url: "/uni_modules/uni-id-pages-x/pages/login/login-withoutpwd"
})
})
} else {
uni.navigateBack()
}
}
});
}
}
}
</script>
<style>
.uni-content,
.scroll-view {
flex-direction: column;
flex: 1;
}
.words {
color: #222;
padding: 0 26rpx;
line-height: 26px;
font-size: 14px;
}
.button-group {
flex-direction: row;
width: 750rpx;
height: 60px;
justify-content: space-around;
align-items: center;
border-top: solid 1px #e4e6ec;
background-color: #FFFFFF;
}
.button-group .btn {
border-radius: 100px;
border: solid 1px #0070ff;
width: 300rpx;
height: 42px;
line-height: 42px;
font-size: 32rpx;
}
.button-group .btn.next {
color: #0070ff;
}
</style>
\ No newline at end of file
<template>
<view class="page">
<uni-navbar-lite title="设置昵称" class="uni-navbar-lite">
<template v-slot:right>
<text class="submit" :class="{disabled:!hasChange || nickname == ''}" @click="setNickname">完成</text>
</template>
</uni-navbar-lite>
<uni-id-pages-x-input class="my-input" v-model="nickname" @confirm="setNickname" placeholder="请输入昵称" :focus="true"
:maxlength="25"></uni-id-pages-x-input>
</view>
</template>
<script>
import { state, mutations } from '@/uni_modules/uni-id-pages-x/store.uts';
export default {
data() {
return {
nickname: ""
};
},
computed: {
hasChange() : boolean {
return this.currentNickname != this.nickname
},
currentNickname() : string {
return (state.userInfo.get("nickname") as string)
}
},
onLoad() {
this.nickname = this.currentNickname
},
methods: {
setNickname() {
if (this.nickname.length == 0) {
uni.showToast({
title: '昵称不能为空',
icon: 'none'
});
this.nickname = this.currentNickname
return
}
if (!this.hasChange) {
// console.log('没有变化');
return
}
uni.showLoading({
title: '加载中',
mask: false
});
const db = uniCloud.databaseForJQL()
const uid = uniCloud.getCurrentUserInfo().uid
if (uid != null) {
db.collection('uni-id-users')
.doc(uid)
.update({
"nickname": this.nickname
})
.then<void>(() => {
mutations.updateUserInfo({ "nickname": this.nickname } as UTSJSONObject)
uni.navigateBack()
})
.catch<void>((err : any | null) => {
const error = err as UniCloudError
console.error('error', error);
})
.finally(() => {
// console.log('finally');
uni.hideLoading()
})
}
}
}
}
</script>
<style lang="scss">
@import url("/uni_modules/uni-id-pages-x/common/common.scss");
.page {
background-color: #f8f8f8;
}
.my-input {
background-color: #FFF;
}
.uni-navbar-lite .right-content {
width: 65px;
}
.submit {
// height: 28px;
line-height: 28px;
width: 50px;
text-align: center;
font-size: 14px;
border-radius: 5px;
margin-right: 15px;
background-color: #0070ff;
color: #FFF;
}
.submit.disabled {
background-color: #EFEFEF;
color: #AAA;
}
</style>
\ No newline at end of file
<template>
<view class="uni-content">
<view class="list">
<view class="item">
<text class="title">头像</text>
<uni-id-pages-x-avatar ref="avatar" width="60px" height="60px" :border="false"></uni-id-pages-x-avatar>
</view>
<view class="item">
<text class="title">昵称</text>
<view @click="setNickname" class="content">
<text class="unset" v-if="!isLogin">未登录</text>
<text class="value"
v-else-if="userInfo['nickname'] != null && userInfo['nickname'] != ''">{{userInfo['nickname']}}</text>
<text class="unset" v-else>未设置</text>
<uni-id-pages-x-icons v-if="isLogin" :size="16" type="right-arrow"
class="link-icon"></uni-id-pages-x-icons>
</view>
</view>
<view class="item">
<text class="title">手机号码</text>
<view @click="bindmobile" class="content">
<text class="value" v-if="userInfo['mobile'] != null">{{userInfo['mobile']}}</text>
<text class="unset" v-else>未绑定</text>
<uni-id-pages-x-icons :size="16" type="right-arrow" class="link-icon"></uni-id-pages-x-icons>
</view>
</view>
</view>
<view class="list">
<view @click="deactivate" class="item">
<text class="title">注销账号</text>
<uni-id-pages-x-icons :size="16" type="right-arrow" class="link-icon"></uni-id-pages-x-icons>
</view>
<view class="item" v-if="isLogin && showLoginManage" @click="logout">
<text class="title">退出登录</text>
<uni-id-pages-x-icons :size="16" type="right-arrow" class="link-icon"></uni-id-pages-x-icons>
</view>
<view class="mask" v-if="!isLogin" @click="login"></view>
</view>
<view class="btn-list" v-if="showLoginManage">
<!-- <button v-if="isLogin" @click="logout" class="logout">退出登录</button> -->
<button v-if="!isLogin" @click="login" type="primary" class="login">去登录</button>
</view>
<!-- 弹出对话框组件 -->
<uni-id-pages-x-popup-dialog ref="popup-dialog" />
</view>
</template>
<script>
import { state, mutations } from '@/uni_modules/uni-id-pages-x/store.uts';
import { logout } from '@/uni_modules/uni-id-pages-x/common/common.uts';
export default {
data() {
return {
}
},
computed: {
userInfo() : UTSJSONObject {
return state.userInfo
},
isLogin() : boolean {
return state.isLogin
}
},
onShow() {
},
props: {
showLoginManage: {
type: Boolean,
default: true
}
},
methods: {
setNickname() {
uni.navigateTo({
"url": "/uni_modules/uni-id-pages-x/pages/userinfo/setNickname/setNickname"
})
},
bindmobile() {
uni.navigateTo({
url: "/uni_modules/uni-id-pages-x/pages/userinfo/bindMobile/bindMobile",
complete(_) {
// console.log('e', e);
}
})
},
deactivate() {
uni.navigateTo({
url: "/uni_modules/uni-id-pages-x/pages/userinfo/deactivate/deactivate",
complete(_) {
// console.log('e', e);
}
})
},
logout() {
logout()
},
login() {
uni.navigateTo({
url: "/uni_modules/uni-id-pages-x/pages/login/login",
// complete(e){
// console.log('e',e);
// }
})
}
}
}
</script>
<style>
.uni-content {
width: 750rpx;
background-color: #f5f5f5;
flex: 1;
}
.avatar {
border: 1px solid #000;
align-items: center;
}
.list {
position: relative;
background-color: #fff;
margin-top: 15px;
}
.mask {
position: absolute;
width: 750rpx;
height: 100%;
}
.list .item {
border-bottom: 1px solid #f5f5f5;
flex-direction: row;
align-items: center;
padding: 15px 15px;
}
.list .item.deactivate {
margin-top: 20px;
border-top: 0.1px solid #EEE;
}
.list .item .title {
font-size: 14px;
flex: 1;
color: #333;
}
.list .item .content {
flex-direction: row;
justify-content: center;
align-items: center;
}
.list .item .content .value {
color: #999;
font-size: 12px;
}
.list .item .content .unset {
color: #aaa;
font-size: 12px;
}
.link-icon {
margin-left: 5px;
width: 13px;
}
.btn-list {
margin-top: 30px;
padding: 30px 150rpx;
}
.logout {
color: #888;
border-style: none;
}
</style>
\ No newline at end of file
文档详情查看:[]()
\ No newline at end of file
export type State = {
pendingAgreements : boolean,
isLogin : boolean,
userInfo : UTSJSONObject
}
// 实例化为state
export const state = reactive({
pendingAgreements: false,
userInfo: {
"_id": null,
"avatar_file": null,
"nickname": null,
"mobile": null,
} as UTSJSONObject,
isLogin: false
} as State)
function initState() {
try {
let userInfo = uni.getStorageSync('uni-id-pages-x-userInfo')
if (userInfo instanceof UTSJSONObject) {
state.userInfo = userInfo
// console.log('init userInfo',userInfo);
}
} catch (e) {
console.error('init userInfo error', e);
}
state.isLogin = uniCloud.getCurrentUserInfo().tokenExpired > Date.now()
};
initState()
type Mutations = {
updateUserInfo(param : null | UTSJSONObject) : void
}
export const mutations = {
updateUserInfo(param : null | UTSJSONObject) {
// console.log('updateUserInfo', param); // param为 null 时从云端获取数据更新,为UTSJSONObject时直接根据传入的值来更新
function afterUpdateUserInfo() {
// console.log('afterUpdateUserInfo', state.userInfo);
uni.setStorageSync('uni-id-pages-x-userInfo', state.userInfo)
}
if (param == null) {
const db = uniCloud.databaseForJQL()
const user_id = uniCloud.getCurrentUserInfo().uid
// console.log('user_id', user_id);
if (user_id != null) {
db.collection('uni-id-users')
.doc(user_id)
.field('_id,username,nickname,avatar_file,mobile')
.get()
.then<void>(res => {
// console.log("get cloud userinfo", res);
state.userInfo = res.data[0]
afterUpdateUserInfo()
})
.catch<void>((err : any | null) => {
const error = err as UniCloudError
console.error(error.errMsg, '错误')
})
}
} else {
param.toMap().forEach((value, key) => {
// console.log("updateUserInfo.", key, value)
state.userInfo.set(key, value)
})
afterUpdateUserInfo()
}
}
} as Mutations;
\ No newline at end of file
const db = uniCloud.database()
const dbCmd = db.command
const userCollectionName = 'uni-id-users'
const userCollection = db.collection(userCollectionName)
const verifyCollectionName = 'opendb-verify-codes'
const verifyCollection = db.collection(verifyCollectionName)
const deviceCollectionName = 'uni-id-device'
const deviceCollection = db.collection(deviceCollectionName)
const openDataCollectionName = 'opendb-open-data'
const openDataCollection = db.collection(openDataCollectionName)
const frvLogsCollectionName = 'opendb-frv-logs'
const frvLogsCollection = db.collection(frvLogsCollectionName)
const USER_IDENTIFIER = {
_id: 'uid',
username: 'username',
mobile: 'mobile',
email: 'email',
wx_unionid: 'wechat-account',
'wx_openid.app': 'wechat-account',
'wx_openid.mp': 'wechat-account',
'wx_openid.h5': 'wechat-account',
'wx_openid.web': 'wechat-account',
qq_unionid: 'qq-account',
'qq_openid.app': 'qq-account',
'qq_openid.mp': 'qq-account',
ali_openid: 'alipay-account',
apple_openid: 'alipay-account',
identities: 'idp'
}
const USER_STATUS = {
NORMAL: 0,
BANNED: 1,
AUDITING: 2,
AUDIT_FAILED: 3,
CLOSED: 4
}
const CAPTCHA_SCENE = {
REGISTER: 'register',
LOGIN_BY_PWD: 'login-by-pwd',
LOGIN_BY_SMS: 'login-by-sms',
RESET_PWD_BY_SMS: 'reset-pwd-by-sms',
RESET_PWD_BY_EMAIL: 'reset-pwd-by-email',
SEND_SMS_CODE: 'send-sms-code',
SEND_EMAIL_CODE: 'send-email-code',
BIND_MOBILE_BY_SMS: 'bind-mobile-by-sms',
SET_PWD_BY_SMS: 'set-pwd-by-sms'
}
const LOG_TYPE = {
LOGOUT: 'logout',
LOGIN: 'login',
REGISTER: 'register',
RESET_PWD_BY_SMS: 'reset-pwd',
RESET_PWD_BY_EMAIL: 'reset-pwd',
BIND_MOBILE: 'bind-mobile',
BIND_WEIXIN: 'bind-weixin',
BIND_QQ: 'bind-qq',
BIND_APPLE: 'bind-apple',
BIND_ALIPAY: 'bind-alipay',
UNBIND_WEIXIN: 'unbind-weixin',
UNBIND_QQ: 'unbind-qq',
UNBIND_ALIPAY: 'unbind-alipay',
UNBIND_APPLE: 'unbind-apple'
}
const SMS_SCENE = {
LOGIN_BY_SMS: 'login-by-sms',
RESET_PWD_BY_SMS: 'reset-pwd-by-sms',
BIND_MOBILE_BY_SMS: 'bind-mobile-by-sms',
SET_PWD_BY_SMS: 'set-pwd-by-sms'
}
const EMAIL_SCENE = {
REGISTER: 'register',
LOGIN_BY_EMAIL: 'login-by-email',
RESET_PWD_BY_EMAIL: 'reset-pwd-by-email',
BIND_EMAIL: 'bind-email'
}
const REAL_NAME_STATUS = {
NOT_CERTIFIED: 0,
WAITING_CERTIFIED: 1,
CERTIFIED: 2,
CERTIFY_FAILED: 3
}
const EXTERNAL_DIRECT_CONNECT_PROVIDER = 'externalDirectConnect'
module.exports = {
db,
dbCmd,
userCollection,
verifyCollection,
deviceCollection,
openDataCollection,
frvLogsCollection,
USER_IDENTIFIER,
USER_STATUS,
CAPTCHA_SCENE,
LOG_TYPE,
SMS_SCENE,
EMAIL_SCENE,
REAL_NAME_STATUS,
EXTERNAL_DIRECT_CONNECT_PROVIDER
}
const ERROR = {
ACCOUNT_EXISTS: 'uni-id-account-exists',
ACCOUNT_NOT_EXISTS: 'uni-id-account-not-exists',
ACCOUNT_NOT_EXISTS_IN_CURRENT_APP: 'uni-id-account-not-exists-in-current-app',
ACCOUNT_CONFLICT: 'uni-id-account-conflict',
ACCOUNT_BANNED: 'uni-id-account-banned',
ACCOUNT_AUDITING: 'uni-id-account-auditing',
ACCOUNT_AUDIT_FAILED: 'uni-id-account-audit-failed',
ACCOUNT_CLOSED: 'uni-id-account-closed',
CAPTCHA_REQUIRED: 'uni-id-captcha-required',
PASSWORD_ERROR: 'uni-id-password-error',
PASSWORD_ERROR_EXCEED_LIMIT: 'uni-id-password-error-exceed-limit',
INVALID_USERNAME: 'uni-id-invalid-username',
INVALID_PASSWORD: 'uni-id-invalid-password',
INVALID_PASSWORD_SUPER: 'uni-id-invalid-password-super',
INVALID_PASSWORD_STRONG: 'uni-id-invalid-password-strong',
INVALID_PASSWORD_MEDIUM: 'uni-id-invalid-password-medium',
INVALID_PASSWORD_WEAK: 'uni-id-invalid-password-weak',
INVALID_MOBILE: 'uni-id-invalid-mobile',
INVALID_EMAIL: 'uni-id-invalid-email',
INVALID_NICKNAME: 'uni-id-invalid-nickname',
INVALID_PARAM: 'uni-id-invalid-param',
PARAM_REQUIRED: 'uni-id-param-required',
GET_THIRD_PARTY_ACCOUNT_FAILED: 'uni-id-get-third-party-account-failed',
GET_THIRD_PARTY_USER_INFO_FAILED: 'uni-id-get-third-party-user-info-failed',
MOBILE_VERIFY_CODE_ERROR: 'uni-id-mobile-verify-code-error',
EMAIL_VERIFY_CODE_ERROR: 'uni-id-email-verify-code-error',
ADMIN_EXISTS: 'uni-id-admin-exists',
PERMISSION_ERROR: 'uni-id-permission-error',
SYSTEM_ERROR: 'uni-id-system-error',
SET_INVITE_CODE_FAILED: 'uni-id-set-invite-code-failed',
INVALID_INVITE_CODE: 'uni-id-invalid-invite-code',
CHANGE_INVITER_FORBIDDEN: 'uni-id-change-inviter-forbidden',
BIND_CONFLICT: 'uni-id-bind-conflict',
UNBIND_FAIL: 'uni-id-unbind-failed',
UNBIND_NOT_SUPPORTED: 'uni-id-unbind-not-supported',
UNBIND_UNIQUE_LOGIN: 'uni-id-unbind-unique-login',
UNBIND_PASSWORD_NOT_EXISTS: 'uni-id-unbind-password-not-exists',
UNBIND_MOBILE_NOT_EXISTS: 'uni-id-unbind-mobile-not-exists',
UNSUPPORTED_REQUEST: 'uni-id-unsupported-request',
ILLEGAL_REQUEST: 'uni-id-illegal-request',
CONFIG_FIELD_REQUIRED: 'uni-id-config-field-required',
CONFIG_FIELD_INVALID: 'uni-id-config-field-invalid',
FRV_FAIL: 'uni-id-frv-fail',
FRV_PROCESSING: 'uni-id-frv-processing',
REAL_NAME_VERIFIED: 'uni-id-realname-verified',
ID_CARD_EXISTS: 'uni-id-idcard-exists',
INVALID_ID_CARD: 'uni-id-invalid-idcard',
INVALID_REAL_NAME: 'uni-id-invalid-realname',
UNKNOWN_ERROR: 'uni-id-unknown-error',
REAL_NAME_VERIFY_UPPER_LIMIT: 'uni-id-realname-verify-upper-limit'
}
function isUniIdError (errCode) {
return Object.values(ERROR).includes(errCode)
}
class UniCloudError extends Error {
constructor (options) {
super(options.message)
this.errMsg = options.message || ''
this.errCode = options.code
}
}
module.exports = {
ERROR,
isUniIdError,
UniCloudError
}
const crypto = require('crypto')
const { ERROR } = require('./error')
function checkSecret (secret) {
if (!secret) {
throw {
errCode: ERROR.CONFIG_FIELD_REQUIRED,
errMsgValue: {
field: 'sensitiveInfoEncryptSecret'
}
}
}
if (secret.length !== 32) {
throw {
errCode: ERROR.CONFIG_FIELD_INVALID,
errMsgValue: {
field: 'sensitiveInfoEncryptSecret'
}
}
}
}
function encryptData (text = '') {
if (!text) return text
const encryptSecret = this.config.sensitiveInfoEncryptSecret
checkSecret(encryptSecret)
const iv = encryptSecret.slice(-16)
const cipher = crypto.createCipheriv('aes-256-cbc', encryptSecret, iv)
const encrypted = Buffer.concat([
cipher.update(Buffer.from(text, 'utf-8')),
cipher.final()
])
return encrypted.toString('base64')
}
function decryptData (text = '') {
if (!text) return text
const encryptSecret = this.config.sensitiveInfoEncryptSecret
checkSecret(encryptSecret)
const iv = encryptSecret.slice(-16)
const cipher = crypto.createDecipheriv('aes-256-cbc', encryptSecret, iv)
const decrypted = Buffer.concat([
cipher.update(Buffer.from(text, 'base64')),
cipher.final()
])
return decrypted.toString('utf-8')
}
module.exports = {
encryptData,
decryptData
}
const { ERROR } = require('./error')
function getHttpClientInfo () {
const requestId = this.getUniCloudRequestId()
const { clientIP, userAgent, source, secretType = 'none' } = this.getClientInfo()
const { clientInfo = {} } = JSON.parse(this.getHttpInfo().body)
return {
...clientInfo,
clientIP,
userAgent,
source,
secretType,
requestId
}
}
function getHttpUniIdToken () {
const { uniIdToken = '' } = JSON.parse(this.getHttpInfo().body)
return uniIdToken
}
function verifyHttpMethod () {
const { headers, httpMethod } = this.getHttpInfo()
if (!/^application\/json/.test(headers['content-type']) || httpMethod.toUpperCase() !== 'POST') {
throw {
errCode: ERROR.UNSUPPORTED_REQUEST,
errMsg: 'unsupported request'
}
}
}
function universal () {
if (this.getClientInfo().source === 'http') {
verifyHttpMethod.call(this)
this.getParams()[0] = JSON.parse(this.getHttpInfo().body).params
this.getUniversalClientInfo = getHttpClientInfo.bind(this)
this.getUniversalUniIdToken = getHttpUniIdToken.bind(this)
} else {
this.getUniversalClientInfo = this.getClientInfo
this.getUniversalUniIdToken = this.getUniIdToken
}
}
module.exports = universal
function batchFindObjctValue (obj = {}, keys = []) {
const values = {}
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const keyPath = key.split('.')
let currentKey = keyPath.shift()
let result = obj
while (currentKey) {
if (!result) {
break
}
result = result[currentKey]
currentKey = keyPath.shift()
}
values[key] = result
}
return values
}
function getType (val) {
return Object.prototype.toString.call(val).slice(8, -1).toLowerCase()
}
function hasOwn (obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key)
}
function isValidString (val) {
return val && getType(val) === 'string'
}
function isPlainObject (obj) {
return getType(obj) === 'object'
}
function isFn (fn) {
// 务必注意AsyncFunction
return typeof fn === 'function'
}
// 获取文件后缀,只添加几种图片类型供客服消息接口使用
const mime2ext = {
'image/png': 'png',
'image/jpeg': 'jpg',
'image/gif': 'gif',
'image/svg+xml': 'svg',
'image/bmp': 'bmp',
'image/webp': 'webp'
}
function getExtension (contentType) {
return mime2ext[contentType]
}
const isSnakeCase = /_(\w)/g
const isCamelCase = /[A-Z]/g
function snake2camel (value) {
return value.replace(isSnakeCase, (_, c) => (c ? c.toUpperCase() : ''))
}
function camel2snake (value) {
return value.replace(isCamelCase, str => '_' + str.toLowerCase())
}
function parseObjectKeys (obj, type) {
let parserReg, parser
switch (type) {
case 'snake2camel':
parser = snake2camel
parserReg = isSnakeCase
break
case 'camel2snake':
parser = camel2snake
parserReg = isCamelCase
break
}
for (const key in obj) {
if (hasOwn(obj, key)) {
if (parserReg.test(key)) {
const keyCopy = parser(key)
obj[keyCopy] = obj[key]
delete obj[key]
if (isPlainObject(obj[keyCopy])) {
obj[keyCopy] = parseObjectKeys(obj[keyCopy], type)
} else if (Array.isArray(obj[keyCopy])) {
obj[keyCopy] = obj[keyCopy].map((item) => {
return parseObjectKeys(item, type)
})
}
}
}
}
return obj
}
function snake2camelJson (obj) {
return parseObjectKeys(obj, 'snake2camel')
}
function camel2snakeJson (obj) {
return parseObjectKeys(obj, 'camel2snake')
}
function getOffsetDate (offset) {
return new Date(
Date.now() + (new Date().getTimezoneOffset() + (offset || 0) * 60) * 60000
)
}
function getDateStr (date, separator = '-') {
date = date || new Date()
const dateArr = []
dateArr.push(date.getFullYear())
dateArr.push(('00' + (date.getMonth() + 1)).substr(-2))
dateArr.push(('00' + date.getDate()).substr(-2))
return dateArr.join(separator)
}
function getTimeStr (date, separator = ':') {
date = date || new Date()
const timeArr = []
timeArr.push(('00' + date.getHours()).substr(-2))
timeArr.push(('00' + date.getMinutes()).substr(-2))
timeArr.push(('00' + date.getSeconds()).substr(-2))
return timeArr.join(separator)
}
function getFullTimeStr (date) {
date = date || new Date()
return getDateStr(date) + ' ' + getTimeStr(date)
}
function getDistinctArray (arr) {
return Array.from(new Set(arr))
}
/**
* 拼接url
* @param {string} base 基础路径
* @param {string} path 在基础路径上拼接的路径
* @returns
*/
function resolveUrl (base, path) {
if (/^https?:/.test(path)) {
return path
}
return base + path
}
function getVerifyCode (len = 6) {
let code = ''
for (let i = 0; i < len; i++) {
code += Math.floor(Math.random() * 10)
}
return code
}
function coverMobile (mobile) {
if (typeof mobile !== 'string') {
return mobile
}
return mobile.slice(0, 3) + '****' + mobile.slice(7)
}
function getNonceStr (length = 16) {
let str = ''
while (str.length < length) {
str += Math.random().toString(32).substring(2)
}
return str.substring(0, length)
}
try {
require('lodash.merge')
} catch (error) {
console.error('uni-id-co缺少依赖,请在uniCloud/cloudfunctions/uni-id-co目录执行 npm install 安装依赖')
throw error
}
function isMatchUserApp (userAppList, matchAppList) {
if (userAppList === undefined || userAppList === null) {
return true
}
if (getType(userAppList) !== 'array') {
return false
}
if (userAppList.includes('*')) {
return true
}
if (getType(matchAppList) === 'string') {
matchAppList = [matchAppList]
}
return userAppList.some(item => matchAppList.includes(item))
}
function checkIdCard (idCardNumber) {
if (!idCardNumber || typeof idCardNumber !== 'string' || idCardNumber.length !== 18) return false
const coefficient = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
const checkCode = [1, 0, 'x', 9, 8, 7, 6, 5, 4, 3, 2]
const code = idCardNumber.substring(17)
let sum = 0
for (let i = 0; i < 17; i++) {
sum += Number(idCardNumber.charAt(i)) * coefficient[i]
}
return checkCode[sum % 11].toString() === code.toLowerCase()
}
function catchAwait (fn, finallyFn) {
if (!fn) return [new Error('no function')]
if (Promise.prototype.finally === undefined) {
// eslint-disable-next-line no-extend-native
Promise.prototype.finally = function (finallyFn) {
return this.then(
res => Promise.resolve(finallyFn()).then(() => res),
error => Promise.resolve(finallyFn()).then(() => { throw error })
)
}
}
return fn
.then((data) => [undefined, data])
.catch((error) => [error])
.finally(() => typeof finallyFn === 'function' && finallyFn())
}
function dataDesensitization (value = '', options = {}) {
const { onlyLast = false } = options
const [firstIndex, middleIndex, lastIndex] = onlyLast ? [0, 0, -1] : [0, 1, -1]
if (!value) return value
const first = value.slice(firstIndex, middleIndex)
const middle = value.slice(middleIndex, lastIndex)
const last = value.slice(lastIndex)
const star = Array.from(new Array(middle.length), (v) => '*').join('')
return first + star + last
}
function getCurrentDateTimestamp (date = Date.now(), targetTimezone = 8) {
const oneHour = 60 * 60 * 1000
return parseInt((date + targetTimezone * oneHour) / (24 * oneHour)) * (24 * oneHour) - targetTimezone * oneHour
}
module.exports = {
getType,
isValidString,
batchFindObjctValue,
isPlainObject,
isFn,
getDistinctArray,
getFullTimeStr,
resolveUrl,
getOffsetDate,
camel2snakeJson,
snake2camelJson,
getExtension,
getVerifyCode,
coverMobile,
getNonceStr,
isMatchUserApp,
checkIdCard,
catchAwait,
dataDesensitization,
getCurrentDateTimestamp
}
const {
isValidString,
getType
} = require('./utils.js')
const {
ERROR
} = require('./error')
const baseValidator = Object.create(null)
baseValidator.username = function (username) {
const errCode = ERROR.INVALID_USERNAME
if (!isValidString(username)) {
return {
errCode
}
}
if (/^\d+$/.test(username)) {
// 用户名不能为纯数字
return {
errCode
}
};
if (!/^[a-zA-Z0-9_-]+$/.test(username)) {
// 用户名仅能使用数字、字母、“_”及“-”
return {
errCode
}
}
}
baseValidator.password = function (password) {
const errCode = ERROR.INVALID_PASSWORD
if (!isValidString(password)) {
return {
errCode
}
}
if (password.length < 6) {
// 密码长度不能小于6
return {
errCode
}
}
}
baseValidator.mobile = function (mobile) {
const errCode = ERROR.INVALID_MOBILE
if (getType(mobile) !== 'string') {
return {
errCode
}
}
if (mobile && !/^1\d{10}$/.test(mobile)) {
return {
errCode
}
}
}
baseValidator.email = function (email) {
const errCode = ERROR.INVALID_EMAIL
if (getType(email) !== 'string') {
return {
errCode
}
}
if (email && !/@/.test(email)) {
return {
errCode
}
}
}
baseValidator.nickname = function (nickname) {
const errCode = ERROR.INVALID_NICKNAME
if (nickname.indexOf('@') !== -1) {
// 昵称不允许含@
return {
errCode
}
};
if (/^\d+$/.test(nickname)) {
// 昵称不能为纯数字
return {
errCode
}
};
if (nickname.length > 100) {
// 昵称不可超过100字符
return {
errCode
}
}
}
const baseType = ['string', 'boolean', 'number', 'null'] // undefined不会由客户端提交上来
baseType.forEach((type) => {
baseValidator[type] = function (val) {
if (getType(val) === type) {
return
}
return {
errCode: ERROR.INVALID_PARAM
}
}
})
function tokenize(name) {
let i = 0
const result = []
let token = ''
while (i < name.length) {
const char = name[i]
switch (char) {
case '|':
case '<':
case '>':
token && result.push(token)
result.push(char)
token = ''
break
default:
token += char
break
}
i++
if (i === name.length && token) {
result.push(token)
}
}
return result
}
/**
* 处理validator名
* @param {string} name
*/
function parseValidatorName(name) {
const tokenList = tokenize(name)
let i = 0
let currentToken = tokenList[i]
const result = {
type: 'root',
children: [],
parent: null
}
let lastRealm = result
while (currentToken) {
switch (currentToken) {
case 'array': {
const currentRealm = {
type: 'array',
children: [],
parent: lastRealm
}
lastRealm.children.push(currentRealm)
lastRealm = currentRealm
break
}
case '<':
if (lastRealm.type !== 'array') {
throw new Error('Invalid validator token "<"')
}
break
case '>':
if (lastRealm.type !== 'array') {
throw new Error('Invalid validator token ">"')
}
lastRealm = lastRealm.parent
break
case '|':
if (lastRealm.type !== 'array' && lastRealm.type !== 'root') {
throw new Error('Invalid validator token "|"')
}
break
default:
lastRealm.children.push({
type: currentToken
})
break
}
i++
currentToken = tokenList[i]
}
return result
}
function getRuleCategory(rule) {
switch (rule.type) {
case 'array':
return 'array'
case 'root':
return 'root'
default:
return 'base'
}
}
function isMatchUnionType(val, rule) {
if (!rule.children || rule.children.length === 0) {
return true
}
const children = rule.children
for (let i = 0; i < children.length; i++) {
const child = children[i]
const category = getRuleCategory(child)
let pass = false
switch (category) {
case 'base':
pass = isMatchBaseType(val, child)
break
case 'array':
pass = isMatchArrayType(val, child)
break
default:
break
}
if (pass) {
return true
}
}
return false
}
function isMatchBaseType(val, rule) {
if (typeof baseValidator[rule.type] !== 'function') {
throw new Error(`invalid schema type: ${rule.type}`)
}
const validateRes = baseValidator[rule.type](val)
if (validateRes && validateRes.errCode) {
return false
}
return true
}
function isMatchArrayType(arr, rule) {
if (getType(arr) !== 'array') {
return false
}
if (rule.children && rule.children.length && arr.some(item => !isMatchUnionType(item, rule))) {
return false
}
return true
}
// 特殊符号 https://www.ibm.com/support/pages/password-strength-rules ~!@#$%^&*_-+=`|\(){}[]:;"'<>,.?/
// const specialChar = '~!@#$%^&*_-+=`|\(){}[]:;"\'<>,.?/'
// const specialCharRegExp = /^[~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]$/
// for (let i = 0, arr = specialChar.split(''); i < arr.length; i++) {
// const char = arr[i]
// if (!specialCharRegExp.test(char)) {
// throw new Error('check special character error: ' + char)
// }
// }
// 密码强度表达式
const passwordRules = {
// 密码必须包含大小写字母、数字和特殊符号
super: /^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/])[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{8,16}$/,
// 密码必须包含字母、数字和特殊符号
strong: /^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/])[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{8,16}$/,
// 密码必须为字母、数字和特殊符号任意两种的组合
medium: /^(?![0-9]+$)(?![a-zA-Z]+$)(?![~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]+$)[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{8,16}$/,
// 密码必须包含字母和数字
weak: /^(?=.*[0-9])(?=.*[a-zA-Z])[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{6,16}$/,
}
function createPasswordVerifier({
passwordStrength = ''
} = {}) {
return function (password) {
const passwordRegExp = passwordRules[passwordStrength]
if (!passwordRegExp) {
throw new Error('Invalid password strength config: ' + passwordStrength)
}
const errCode = ERROR.INVALID_PASSWORD
if (!isValidString(password)) {
return {
errCode
}
}
if (!passwordRegExp.test(password)) {
return {
errCode: errCode + '-' + passwordStrength
}
}
}
}
function isEmpty(value) {
return value === undefined ||
value === null ||
(typeof value === 'string' && value.trim() === '')
}
class Validator {
constructor({
passwordStrength = ''
} = {}) {
this.baseValidator = baseValidator
this.customValidator = Object.create(null)
if (passwordStrength) {
this.mixin(
'password',
createPasswordVerifier({
passwordStrength
})
)
}
}
mixin(type, handler) {
this.customValidator[type] = handler
}
getRealBaseValidator(type) {
return this.customValidator[type] || this.baseValidator[type]
}
get validator() {
return new Proxy({}, {
get: (_, prop) => {
if (typeof prop !== 'string') {
return
}
const realBaseValidator = this.getRealBaseValidator(prop)
if (realBaseValidator) {
return realBaseValidator
}
const rule = parseValidatorName(prop)
return function (val) {
if (!isMatchUnionType(val, rule)) {
return {
errCode: ERROR.INVALID_PARAM
}
}
}
}
})
}
validate(value = {}, schema = {}) {
for (const schemaKey in schema) {
let schemaValue = schema[schemaKey]
if (getType(schemaValue) === 'string') {
schemaValue = {
required: true,
type: schemaValue
}
}
const {
required,
type
} = schemaValue
// value内未传入了schemaKey或对应值为undefined
if (isEmpty(value[schemaKey])) {
if (required) {
return {
errCode: ERROR.PARAM_REQUIRED,
errMsgValue: {
param: schemaKey
},
schemaKey
}
} else {
//delete value[schemaKey]
continue
}
}
const validateMethod = this.validator[type]
if (!validateMethod) {
throw new Error(`invalid schema type: ${type}`)
}
const validateRes = validateMethod(value[schemaKey])
if (validateRes) {
validateRes.schemaKey = schemaKey
return validateRes
}
}
}
}
function checkClientInfo(clientInfo) {
const stringNotRequired = {
required: false,
type: 'string'
}
const numberNotRequired = {
required: false,
type: 'number'
}
const numberOrStringNotRequired = {
required: false,
type: 'number|string'
}
const schema = {
uniPlatform: 'string',
appId: 'string',
deviceId: stringNotRequired,
osName: stringNotRequired,
locale: stringNotRequired,
clientIP: stringNotRequired,
appName: stringNotRequired,
appVersion: stringNotRequired,
appVersionCode: numberOrStringNotRequired,
channel: numberOrStringNotRequired,
userAgent: stringNotRequired,
uniIdToken: stringNotRequired,
deviceBrand: stringNotRequired,
deviceModel: stringNotRequired,
osVersion: stringNotRequired,
osLanguage: stringNotRequired,
osTheme: stringNotRequired,
romName: stringNotRequired,
romVersion: stringNotRequired,
devicePixelRatio: numberNotRequired,
windowWidth: numberNotRequired,
windowHeight: numberNotRequired,
screenWidth: numberNotRequired,
screenHeight: numberNotRequired
}
const validateRes = new Validator().validate(clientInfo, schema)
if (validateRes) {
if (validateRes.errCode === ERROR.PARAM_REQUIRED) {
console.warn('- 如果使用HBuilderX运行本地云函数/云对象功能时出现此提示,请改为使用客户端调用本地云函数方式调试,或更新HBuilderX到3.4.12及以上版本。\n- 如果是缺少clientInfo.appId,请检查项目manifest.json内是否配置了DCloud AppId')
throw new Error(`"clientInfo.${validateRes.schemaKey}" is required.`)
} else {
throw new Error(`Invalid client info: clienInfo.${validateRes.schemaKey}`)
}
}
}
module.exports = {
Validator,
checkClientInfo
}
// 各接口权限配置,未配置接口表示允许任何用户访问(包括未登录用户)
module.exports = {
// 管理接口
addUser: {
// auth: true // 已登录用户方可操作,配置角色或权限时此项可不写
role: ['admin'] // 允许进行此操作的角色,包含任一角色均可操作。
// permission: [] // 允许进行此操作的权限,包含任一权限均可操作。
// 权限角色均配置时,用户拥有任一权限或任一角色均可操作
},
updateUser: {
role: ['admin']
},
authorizeAppLogin: {
role: ['admin']
},
removeAuthorizedApp: {
role: ['admin']
},
setAuthorizedApp: {
role: ['admin']
},
// 用户接口
closeAccount: {
auth: true
},
updatePwd: {
auth: true
},
logout: {
auth: true
},
bindMobileBySms: {
auth: true
},
bindMobileByUniverify: {
auth: true
},
bindMobileByMpWeixin: {
auth: true
},
bindAlipay: {
auth: true
},
bindApple: {
auth: true
},
bindQQ: {
auth: true
},
bindWeixin: {
auth: true
},
acceptInvite: {
auth: true
},
getInvitedUser: {
auth: true
},
setPushCid: {
auth: true
},
getAccountInfo: {
auth: true
},
unbindWeixin: {
auth: true
},
unbindAlipay: {
auth: true
},
unbindQQ: {
auth: true
},
unbindApple: {
auth: true
},
setPwd: {
auth: true
},
getFrvCertifyId: {
auth: true
},
getFrvAuthResult: {
auth: true
},
getRealNameInfo: {
auth: true
}
}
const uniIdCommon = require('uni-id-common')
const uniCaptcha = require('uni-captcha')
const {
getType,
checkIdCard
} = require('./common/utils')
const {
checkClientInfo,
Validator
} = require('./common/validator')
const ConfigUtils = require('./lib/utils/config')
const {
isUniIdError,
ERROR
} = require('./common/error')
const middleware = require('./middleware/index')
const universal = require('./common/universal')
const {
registerAdmin,
registerUser,
registerUserByEmail
} = require('./module/register/index')
const {
addUser,
updateUser
} = require('./module/admin/index')
const {
login,
loginBySms,
loginByUniverify,
loginByWeixin,
loginByAlipay,
loginByQQ,
loginByApple,
loginByWeixinMobile
} = require('./module/login/index')
const {
logout
} = require('./module/logout/index')
const {
bindMobileBySms,
bindMobileByUniverify,
bindMobileByMpWeixin,
bindAlipay,
bindApple,
bindQQ,
bindWeixin,
unbindWeixin,
unbindAlipay,
unbindQQ,
unbindApple
} = require('./module/relate/index')
const {
setPwd,
updatePwd,
resetPwdBySms,
resetPwdByEmail,
closeAccount,
getAccountInfo,
getRealNameInfo
} = require('./module/account/index')
const {
createCaptcha,
refreshCaptcha,
sendSmsCode,
sendEmailCode
} = require('./module/verify/index')
const {
refreshToken,
setPushCid,
secureNetworkHandshakeByWeixin
} = require('./module/utils/index')
const {
getInvitedUser,
acceptInvite
} = require('./module/fission')
const {
authorizeAppLogin,
removeAuthorizedApp,
setAuthorizedApp
} = require('./module/multi-end')
const {
getSupportedLoginType
} = require('./module/dev/index')
const {
externalRegister,
externalLogin,
updateUserInfoByExternal
} = require('./module/external')
const {
getFrvCertifyId,
getFrvAuthResult
} = require('./module/facial-recognition-verify')
module.exports = {
async _before () {
// 支持 callFunction 与 URL化
universal.call(this)
const clientInfo = this.getUniversalClientInfo()
/**
* 检查clientInfo,无appId和uniPlatform时本云对象无法正常运行
* 此外需要保证用到的clientInfo字段均经过类型检查
* clientInfo由客户端上传并非完全可信,clientInfo内除clientIP、userAgent、source外均为客户端上传参数
* 否则可能会出现一些意料外的情况
*/
checkClientInfo(clientInfo)
let clientPlatform = clientInfo.uniPlatform
// 统一platform名称
switch (clientPlatform) {
case 'app':
case 'app-plus':
clientPlatform = 'app'
break
case 'web':
case 'h5':
clientPlatform = 'web'
break
default:
break
}
this.clientPlatform = clientPlatform
// 挂载uni-id实例到this上,方便后续调用
this.uniIdCommon = uniIdCommon.createInstance({
clientInfo
})
// 包含uni-id配置合并等功能的工具集
this.configUtils = new ConfigUtils({
context: this
})
this.config = this.configUtils.getPlatformConfig()
this.hooks = this.configUtils.getHooks()
this.validator = new Validator({
passwordStrength: this.config.passwordStrength
})
// 扩展 validator 增加 验证身份证号码合法性
this.validator.mixin('idCard', function (idCard) {
if (!checkIdCard(idCard)) {
return {
errCode: ERROR.INVALID_ID_CARD
}
}
})
this.validator.mixin('realName', function (realName) {
if (
typeof realName !== 'string' ||
realName.length < 2 ||
!/^[\u4e00-\u9fa5]{1,10}(·?[\u4e00-\u9fa5]{1,10}){0,5}$/.test(realName)
) {
return {
errCode: ERROR.INVALID_REAL_NAME
}
}
})
/**
* 示例:覆盖密码验证规则
*/
// this.validator.mixin('password', function (password) {
// if (typeof password !== 'string' || password.length < 10) {
// // 调整为密码长度不能小于10
// return {
// errCode: ERROR.INVALID_PASSWORD
// }
// }
// })
/**
* 示例:新增验证规则
*/
// this.validator.mixin('timestamp', function (timestamp) {
// if (typeof timestamp !== 'number' || timestamp > Date.now()) {
// return {
// errCode: ERROR.INVALID_PARAM
// }
// }
// })
// // 新增规则同样可以在数组验证规则中使用
// this.validator.valdate({
// timestamp: 123456789
// }, {
// timestamp: 'timestamp'
// })
// this.validator.valdate({
// timestampList: [123456789, 123123123123]
// }, {
// timestampList: 'array<timestamp>'
// })
// // 甚至更复杂的写法
// this.validator.valdate({
// timestamp: [123456789, 123123123123]
// }, {
// timestamp: 'timestamp|array<timestamp>'
// })
// 挂载uni-captcha到this上,方便后续调用
this.uniCaptcha = uniCaptcha
Object.defineProperty(this, 'uniOpenBridge', {
get () {
return require('uni-open-bridge-common')
}
})
// 挂载中间件
this.middleware = {}
for (const mwName in middleware) {
this.middleware[mwName] = middleware[mwName].bind(this)
}
// 国际化
const messages = require('./lang/index')
const fallbackLocale = 'zh-Hans'
const i18n = uniCloud.initI18n({
locale: clientInfo.locale || 'zh-Hans',
fallbackLocale,
messages: JSON.parse(JSON.stringify(messages))
})
// console.log('i18n.locale',i18n.locale);
if (!messages[i18n.locale]) {
i18n.setLocale(fallbackLocale)
}
this.t = i18n.t.bind(i18n)
this.response = {}
// 请求鉴权验证
await this.middleware.verifyRequestSign()
// 通用权限校验模块
await this.middleware.accessControl()
},
_after (error, result) {
if (error) {
// 处理中间件内抛出的标准响应对象
if (error.errCode && getType(error) === 'object') {
const errCode = error.errCode
if (!isUniIdError(errCode)) {
return error
}
return {
errCode,
errMsg: error.errMsg || this.t(errCode, error.errMsgValue)
}
}
throw error
}
return Object.assign(this.response, result)
},
/**
* 注册管理员
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#register-admin
* @param {Object} params
* @param {String} params.username 用户名
* @param {String} params.password 密码
* @param {String} params.nickname 昵称
* @returns
*/
registerAdmin,
/**
* 新增用户
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#add-user
* @param {Object} params
* @param {String} params.username 用户名
* @param {String} params.password 密码
* @param {String} params.nickname 昵称
* @param {Array} params.authorizedApp 允许登录的AppID列表
* @param {Array} params.role 用户角色列表
* @param {String} params.mobile 手机号
* @param {String} params.email 邮箱
* @param {Array} params.tags 用户标签
* @param {Number} params.status 用户状态
* @returns
*/
addUser,
/**
* 修改用户
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#update-user
* @param {Object} params
* @param {String} params.id 要更新的用户id
* @param {String} params.username 用户名
* @param {String} params.password 密码
* @param {String} params.nickname 昵称
* @param {Array} params.authorizedApp 允许登录的AppID列表
* @param {Array} params.role 用户角色列表
* @param {String} params.mobile 手机号
* @param {String} params.email 邮箱
* @param {Array} params.tags 用户标签
* @param {Number} params.status 用户状态
* @returns
*/
updateUser,
/**
* 授权用户登录应用
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#authorize-app-login
* @param {Object} params
* @param {String} params.uid 用户id
* @param {String} params.appId 授权的应用的AppId
* @returns
*/
authorizeAppLogin,
/**
* 移除用户登录授权
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#remove-authorized-app
* @param {Object} params
* @param {String} params.uid 用户id
* @param {String} params.appId 取消授权的应用的AppId
* @returns
*/
removeAuthorizedApp,
/**
* 设置用户允许登录的应用列表
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#set-authorized-app
* @param {Object} params
* @param {String} params.uid 用户id
* @param {Array} params.appIdList 允许登录的应用AppId列表
* @returns
*/
setAuthorizedApp,
/**
* 注册普通用户
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#register-user
* @param {Object} params
* @param {String} params.username 用户名
* @param {String} params.password 密码
* @param {String} params.captcha 图形验证码
* @param {String} params.nickname 昵称
* @param {String} params.inviteCode 邀请码
* @returns
*/
registerUser,
/**
* 通过邮箱+验证码注册用户
* @param {Object} params
* @param {String} params.email 邮箱
* @param {String} params.password 密码
* @param {String} params.nickname 昵称
* @param {String} params.code 邮箱验证码
* @param {String} params.inviteCode 邀请码
* @returns
*/
registerUserByEmail,
/**
* 用户名密码登录
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#login
* @param {Object} params
* @param {String} params.username 用户名
* @param {String} params.mobile 手机号
* @param {String} params.email 邮箱
* @param {String} params.password 密码
* @param {String} params.captcha 图形验证码
* @returns
*/
login,
/**
* 短信验证码登录
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#login-by-sms
* @param {Object} params
* @param {String} params.mobile 手机号
* @param {String} params.code 短信验证码
* @param {String} params.captcha 图形验证码
* @param {String} params.inviteCode 邀请码
* @returns
*/
loginBySms,
/**
* App端一键登录
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#login-by-univerify
* @param {Object} params
* @param {String} params.access_token APP端一键登录返回的access_token
* @param {String} params.openid APP端一键登录返回的openid
* @param {String} params.inviteCode 邀请码
* @returns
*/
loginByUniverify,
/**
* 微信登录
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#login-by-weixin
* @param {Object} params
* @param {String} params.code 微信登录返回的code
* @param {String} params.inviteCode 邀请码
* @returns
*/
loginByWeixin,
/**
* 支付宝登录
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#login-by-alipay
* @param {Object} params
* @param {String} params.code 支付宝小程序客户端登录返回的code
* @param {String} params.inviteCode 邀请码
* @returns
*/
loginByAlipay,
/**
* QQ登录
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#login-by-qq
* @param {Object} params
* @param {String} params.code QQ小程序登录返回的code参数
* @param {String} params.accessToken App端QQ登录返回的accessToken参数
* @param {String} params.accessTokenExpired accessToken过期时间,由App端QQ登录返回的expires_in参数计算而来,单位:毫秒
* @param {String} params.inviteCode 邀请码
* @returns
*/
loginByQQ,
/**
* 苹果登录
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#login-by-apple
* @param {Object} params
* @param {String} params.identityToken 苹果登录返回的identityToken
* @param {String} params.nickname 用户昵称
* @param {String} params.inviteCode 邀请码
* @returns
*/
loginByApple,
loginByWeixinMobile,
/**
* 用户退出登录
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#logout
* @returns
*/
logout,
/**
* 通过短信验证码绑定手机号
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#bind-mobile-by-sms
* @param {Object} params
* @param {String} params.mobile 手机号
* @param {String} params.code 短信验证码
* @param {String} params.captcha 图形验证码
* @returns
*/
bindMobileBySms,
/**
* 通过一键登录绑定手机号
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#bind-mobile-by-univerify
* @param {Object} params
* @param {String} params.openid APP端一键登录返回的openid
* @param {String} params.access_token APP端一键登录返回的access_token
* @returns
*/
bindMobileByUniverify,
/**
* 通过微信绑定手机号
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#bind-mobile-by-mp-weixin
* @param {Object} params
* @param {String} params.encryptedData 微信获取手机号返回的加密信息
* @param {String} params.iv 微信获取手机号返回的初始向量
* @returns
*/
bindMobileByMpWeixin,
/**
* 绑定微信
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#bind-weixin
* @param {Object} params
* @param {String} params.code 微信登录返回的code
* @returns
*/
bindWeixin,
/**
* 绑定QQ
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#bind-qq
* @param {Object} params
* @param {String} params.code 小程序端QQ登录返回的code
* @param {String} params.accessToken APP端QQ登录返回的accessToken
* @param {String} params.accessTokenExpired accessToken过期时间,由App端QQ登录返回的expires_in参数计算而来,单位:毫秒
* @returns
*/
bindQQ,
/**
* 绑定支付宝账号
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#bind-alipay
* @param {Object} params
* @param {String} params.code 支付宝小程序登录返回的code参数
* @returns
*/
bindAlipay,
/**
* 绑定苹果账号
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#bind-apple
* @param {Object} params
* @param {String} params.identityToken 苹果登录返回identityToken
* @returns
*/
bindApple,
/**
* 更新密码
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#update-pwd
* @param {object} params
* @param {string} params.oldPassword 旧密码
* @param {string} params.newPassword 新密码
* @returns {object}
*/
updatePwd,
/**
* 通过短信验证码重置密码
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#reset-pwd-by-sms
* @param {object} params
* @param {string} params.mobile 手机号
* @param {string} params.mobile 短信验证码
* @param {string} params.password 密码
* @param {string} params.captcha 图形验证码
* @returns {object}
*/
resetPwdBySms,
/**
* 通过邮箱验证码重置密码
* @param {object} params
* @param {string} params.email 邮箱
* @param {string} params.code 邮箱验证码
* @param {string} params.password 密码
* @param {string} params.captcha 图形验证码
* @returns {object}
*/
resetPwdByEmail,
/**
* 注销账户
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#close-account
* @returns
*/
closeAccount,
/**
* 获取账户账户简略信息
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#get-account-info
*/
getAccountInfo,
/**
* 创建图形验证码
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#create-captcha
* @param {Object} params
* @param {String} params.scene 图形验证码使用场景
* @returns
*/
createCaptcha,
/**
* 刷新图形验证码
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#refresh-captcha
* @param {Object} params
* @param {String} params.scene 图形验证码使用场景
* @returns
*/
refreshCaptcha,
/**
* 发送短信验证码
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#send-sms-code
* @param {Object} params
* @param {String} params.mobile 手机号
* @param {String} params.captcha 图形验证码
* @param {String} params.scene 短信验证码使用场景
* @returns
*/
sendSmsCode,
/**
* 发送邮箱验证码
* @tutorial 需自行实现功能
* @param {Object} params
* @param {String} params.email 邮箱
* @param {String} params.captcha 图形验证码
* @param {String} params.scene 短信验证码使用场景
* @returns
*/
sendEmailCode,
/**
* 刷新token
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#refresh-token
*/
refreshToken,
/**
* 接受邀请
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#accept-invite
* @param {Object} params
* @param {String} params.inviteCode 邀请码
* @returns
*/
acceptInvite,
/**
* 获取受邀用户
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#get-invited-user
* @param {Object} params
* @param {Number} params.level 获取受邀用户的级数,1表示直接邀请的用户
* @param {Number} params.limit 返回数据大小
* @param {Number} params.offset 返回数据偏移
* @param {Boolean} params.needTotal 是否需要返回总数
* @returns
*/
getInvitedUser,
/**
* 更新device表的push_clien_id
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#set-push-cid
* @param {object} params
* @param {string} params.pushClientId 客户端pushClientId
* @returns
*/
setPushCid,
/**
* 获取支持的登录方式
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#get-supported-login-type
* @returns
*/
getSupportedLoginType,
/**
* 解绑微信
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#unbind-weixin
* @returns
*/
unbindWeixin,
/**
* 解绑支付宝
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#unbind-alipay
* @returns
*/
unbindAlipay,
/**
* 解绑QQ
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#unbind-qq
* @returns
*/
unbindQQ,
/**
* 解绑Apple
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#unbind-apple
* @returns
*/
unbindApple,
/**
* 安全网络握手,目前仅处理微信小程序安全网络握手
*/
secureNetworkHandshakeByWeixin,
/**
* 设置密码
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#set-pwd
* @returns
*/
setPwd,
/**
* 外部注册用户
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#external-register
* @param {object} params
* @param {string} params.externalUid 业务系统的用户id
* @param {string} params.nickname 昵称
* @param {string} params.gender 性别
* @param {string} params.avatar 头像
* @returns {object}
*/
externalRegister,
/**
* 外部用户登录
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#external-login
* @param {object} params
* @param {string} params.userId uni-id体系用户id
* @param {string} params.externalUid 业务系统的用户id
* @returns {object}
*/
externalLogin,
/**
* 使用 userId 或 externalUid 获取用户信息
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#external-update-userinfo
* @param {object} params
* @param {string} params.userId uni-id体系的用户id
* @param {string} params.externalUid 业务系统的用户id
* @param {string} params.nickname 昵称
* @param {string} params.gender 性别
* @param {string} params.avatar 头像
* @returns {object}
*/
updateUserInfoByExternal,
/**
* 获取认证ID
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#get-frv-certify-id
* @param {Object} params
* @param {String} params.realName 真实姓名
* @param {String} params.idCard 身份证号码
* @returns
*/
getFrvCertifyId,
/**
* 查询认证结果
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#get-frv-auth-result
* @param {Object} params
* @param {String} params.certifyId 认证ID
* @param {String} params.needAlivePhoto 是否获取认证照片,Y_O (原始图片)、Y_M(虚化,背景马赛克)、N(不返图)
* @returns
*/
getFrvAuthResult,
/**
* 获取实名信息
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#get-realname-info
* @param {Object} params
* @param {Boolean} params.decryptData 是否解密数据
* @returns
*/
getRealNameInfo
}
const word = {
login: 'login',
'verify-mobile': 'verify mobile number'
}
const sentence = {
'uni-id-account-exists': 'Account exists',
'uni-id-account-not-exists': 'Account does not exists',
'uni-id-account-not-exists-in-current-app': 'Account does not exists in current app',
'uni-id-account-conflict': 'User account conflict',
'uni-id-account-banned': 'Account has been banned',
'uni-id-account-auditing': 'Account audit in progress',
'uni-id-account-audit-failed': 'Account audit failed',
'uni-id-account-closed': 'Account has been closed',
'uni-id-captcha-required': 'Captcha required',
'uni-id-password-error': 'Password error',
'uni-id-password-error-exceed-limit': 'The number of password errors is excessive',
'uni-id-invalid-username': 'Invalid username',
'uni-id-invalid-password': 'invalid password',
'uni-id-invalid-password-super': 'Passwords must have 8-16 characters and contain uppercase letters, lowercase letters, numbers, and symbols.',
'uni-id-invalid-password-strong': 'Passwords must have 8-16 characters and contain letters, numbers and symbols.',
'uni-id-invalid-password-medium': 'Passwords must have 8-16 characters and contain at least two of the following: letters, numbers, and symbols.',
'uni-id-invalid-password-weak': 'Passwords must have 6-16 characters and contain letters and numbers.',
'uni-id-invalid-mobile': 'Invalid mobile mobile number',
'uni-id-invalid-email': 'Invalid email address',
'uni-id-invalid-nickname': 'Invalid nickname',
'uni-id-invalid-param': 'Invalid parameter',
'uni-id-param-required': 'Parameter required: {param}',
'uni-id-get-third-party-account-failed': 'Get third party account failed',
'uni-id-get-third-party-user-info-failed': 'Get third party user info failed',
'uni-id-mobile-verify-code-error': 'Verify code error or expired',
'uni-id-email-verify-code-error': 'Verify code error or expired',
'uni-id-admin-exists': 'Administrator exists',
'uni-id-permission-error': 'Permission denied',
'uni-id-system-error': 'System error',
'uni-id-set-invite-code-failed': 'Set invite code failed',
'uni-id-invalid-invite-code': 'Invalid invite code',
'uni-id-change-inviter-forbidden': 'Change inviter is not allowed',
'uni-id-bind-conflict': 'This account has been bound',
'uni-id-admin-exist-in-other-apps': 'Administrator is registered in other consoles',
'uni-id-unbind-failed': 'Please bind first and then unbind',
'uni-id-unbind-not-supported': 'Unbinding is not supported',
'uni-id-unbind-mobile-not-exists': 'This is the only way to login at the moment, please bind your mobile number and then try to unbind',
'uni-id-unbind-password-not-exists': 'Please set a password first',
'uni-id-unsupported-request': 'Unsupported request',
'uni-id-illegal-request': 'Illegal request',
'uni-id-config-field-required': 'Config field required: {field}',
'uni-id-config-field-invalid': 'Config field: {field} is invalid',
'uni-id-frv-fail': 'Real name certify failed',
'uni-id-frv-processing': 'Waiting for face recognition',
'uni-id-realname-verified': 'This account has been verified',
'uni-id-idcard-exists': 'The ID number has been bound to the account',
'uni-id-invalid-idcard': 'ID number is invalid',
'uni-id-invalid-realname': 'The name can only be Chinese characters',
'uni-id-unknown-error': 'unknown error',
'uni-id-realname-verify-upper-limit': 'The number of real-name certify on the day has reached the upper limit'
}
module.exports = {
...word,
...sentence
}
let lang = {
'zh-Hans': require('./zh-hans'),
en: require('./en')
}
function mergeLanguage (lang1, lang2) {
const localeList = Object.keys(lang1)
localeList.push(...Object.keys(lang2))
const result = {}
for (let i = 0; i < localeList.length; i++) {
const locale = localeList[i]
result[locale] = Object.assign({}, lang1[locale], lang2[locale])
}
return result
}
try {
const langPath = require.resolve('uni-config-center/uni-id/lang/index.js')
lang = mergeLanguage(lang, require(langPath))
} catch (error) { }
module.exports = lang
const word = {
login: '登录',
'verify-mobile': '验证手机号'
}
const sentence = {
'uni-id-token-expired': '登录状态失效,token已过期',
'uni-id-check-token-failed': 'token校验未通过',
'uni-id-account-exists': '此账号已注册',
'uni-id-account-not-exists': '此账号未注册',
'uni-id-account-not-exists-in-current-app': '此账号未在该应用注册',
'uni-id-account-conflict': '用户账号冲突',
'uni-id-account-banned': '此账号已封禁',
'uni-id-account-auditing': '此账号正在审核中',
'uni-id-account-audit-failed': '此账号审核失败',
'uni-id-account-closed': '此账号已注销',
'uni-id-captcha-required': '请输入图形验证码',
'uni-id-password-error': '密码错误',
'uni-id-password-error-exceed-limit': '密码错误次数过多,请稍后再试',
'uni-id-invalid-username': '用户名不合法',
'uni-id-invalid-password': '密码不合法',
'uni-id-invalid-password-super': '密码必须包含大小写字母、数字和特殊符号,长度8-16位',
'uni-id-invalid-password-strong': '密码必须包含字母、数字和特殊符号,长度8-16位不合法',
'uni-id-invalid-password-medium': '密码必须为字母、数字和特殊符号任意两种的组合,长度8-16位',
'uni-id-invalid-password-weak': '密码必须包含字母和数字,长度6-16位',
'uni-id-invalid-mobile': '手机号码不合法',
'uni-id-invalid-email': '邮箱不合法',
'uni-id-invalid-nickname': '昵称不合法',
'uni-id-invalid-param': '参数错误',
'uni-id-param-required': '缺少参数: {param}',
'uni-id-get-third-party-account-failed': '获取第三方账号失败',
'uni-id-get-third-party-user-info-failed': '获取用户信息失败',
'uni-id-mobile-verify-code-error': '手机验证码错误或已过期',
'uni-id-email-verify-code-error': '邮箱验证码错误或已过期',
'uni-id-admin-exists': '超级管理员已存在',
'uni-id-permission-error': '权限错误',
'uni-id-system-error': '系统错误',
'uni-id-set-invite-code-failed': '设置邀请码失败',
'uni-id-invalid-invite-code': '邀请码不可用',
'uni-id-change-inviter-forbidden': '禁止修改邀请人',
'uni-id-bind-conflict': '此账号已被绑定',
'uni-id-admin-exist-in-other-apps': '超级管理员已在其他控制台注册',
'uni-id-unbind-failed': '请先绑定后再解绑',
'uni-id-unbind-not-supported': '不支持解绑',
'uni-id-unbind-mobile-not-exists': '这是当前唯一登录方式,请绑定手机号后再尝试解绑',
'uni-id-unbind-password-not-exists': '请先设置密码在尝试解绑',
'uni-id-unsupported-request': '不支持的请求方式',
'uni-id-illegal-request': '非法请求',
'uni-id-frv-fail': '实名认证失败',
'uni-id-frv-processing': '等待人脸识别',
'uni-id-realname-verified': '该账号已实名认证',
'uni-id-idcard-exists': '该证件号码已绑定账号',
'uni-id-invalid-idcard': '身份证号码不合法',
'uni-id-invalid-realname': '姓名只能是汉字',
'uni-id-unknown-error': '未知错误',
'uni-id-realname-verify-upper-limit': '当日实名认证次数已达上限',
'uni-id-config-field-required': '缺少配置项: {field}',
'uni-id-config-field-invalid': '配置项: {field}无效'
}
module.exports = {
...word,
...sentence
}
# 说明
此目录内为uni-id-co基础能力,不建议直接修改。如果你发现有些逻辑加入会更好,或者此部分代码有Bug可以向我们提交PR,仓库地址:[]()。如果有特殊的需求也可以在[论坛](https://ask.dcloud.net.cn/)提出,我们可以讨论下如何实现。
\ No newline at end of file
const AlipayBase = require('../alipayBase')
const protocols = require('./protocols')
module.exports = class Auth extends AlipayBase {
constructor (options) {
super(options)
this._protocols = protocols
}
async code2Session (code) {
const result = await this._exec('alipay.system.oauth.token', {
grantType: 'authorization_code',
code
})
return result
}
}
module.exports = {
code2Session: {
// args (fromArgs) {
// return fromArgs
// },
returnValue: {
openid: 'userId'
}
}
}
const {
camel2snakeJson,
snake2camelJson,
getOffsetDate,
getFullTimeStr
} = require('../../../common/utils')
const crypto = require('crypto')
const ALIPAY_ALGORITHM_MAPPING = {
RSA: 'RSA-SHA1',
RSA2: 'RSA-SHA256'
}
module.exports = class AlipayBase {
constructor (options = {}) {
if (!options.appId) throw new Error('appId required')
if (!options.privateKey) throw new Error('privateKey required')
const defaultOptions = {
gateway: 'https://openapi.alipay.com/gateway.do',
timeout: 5000,
charset: 'utf-8',
version: '1.0',
signType: 'RSA2',
timeOffset: -new Date().getTimezoneOffset() / 60,
keyType: 'PKCS8'
}
if (options.sandbox) {
options.gateway = 'https://openapi.alipaydev.com/gateway.do'
}
this.options = Object.assign({}, defaultOptions, options)
const privateKeyType =
this.options.keyType === 'PKCS8' ? 'PRIVATE KEY' : 'RSA PRIVATE KEY'
this.options.privateKey = this._formatKey(
this.options.privateKey,
privateKeyType
)
if (this.options.alipayPublicKey) {
this.options.alipayPublicKey = this._formatKey(
this.options.alipayPublicKey,
'PUBLIC KEY'
)
}
}
_formatKey (key, type) {
return `-----BEGIN ${type}-----\n${key}\n-----END ${type}-----`
}
_formatUrl (url, params) {
let requestUrl = url
// 需要放在 url 中的参数列表
const urlArgs = [
'app_id',
'method',
'format',
'charset',
'sign_type',
'sign',
'timestamp',
'version',
'notify_url',
'return_url',
'auth_token',
'app_auth_token'
]
for (const key in params) {
if (urlArgs.indexOf(key) > -1) {
const val = encodeURIComponent(params[key])
requestUrl = `${requestUrl}${requestUrl.includes('?') ? '&' : '?'
}${key}=${val}`
// 删除 postData 中对应的数据
delete params[key]
}
}
return { execParams: params, url: requestUrl }
}
_getSign (method, params) {
const bizContent = params.bizContent || null
delete params.bizContent
const signParams = Object.assign({
method,
appId: this.options.appId,
charset: this.options.charset,
version: this.options.version,
signType: this.options.signType,
timestamp: getFullTimeStr(getOffsetDate(this.options.timeOffset))
}, params)
if (bizContent) {
signParams.bizContent = JSON.stringify(camel2snakeJson(bizContent))
}
// params key 驼峰转下划线
const decamelizeParams = camel2snakeJson(signParams)
// 排序
const signStr = Object.keys(decamelizeParams)
.sort()
.map((key) => {
let data = decamelizeParams[key]
if (Array.prototype.toString.call(data) !== '[object String]') {
data = JSON.stringify(data)
}
return `${key}=${data}`
})
.join('&')
// 计算签名
const sign = crypto
.createSign(ALIPAY_ALGORITHM_MAPPING[this.options.signType])
.update(signStr, 'utf8')
.sign(this.options.privateKey, 'base64')
return Object.assign(decamelizeParams, { sign })
}
async _exec (method, params = {}, option = {}) {
// 计算签名
const signData = this._getSign(method, params)
const { url, execParams } = this._formatUrl(this.options.gateway, signData)
const { status, data } = await uniCloud.httpclient.request(url, {
method: 'POST',
data: execParams,
// 按 text 返回(为了验签)
dataType: 'text',
timeout: this.options.timeout
})
if (status !== 200) throw new Error('request fail')
/**
* 示例响应格式
* {"alipay_trade_precreate_response":
* {"code": "10000","msg": "Success","out_trade_no": "111111","qr_code": "https:\/\/"},
* "sign": "abcde="
* }
* 或者
* {"error_response":
* {"code":"40002","msg":"Invalid Arguments","sub_code":"isv.code-invalid","sub_msg":"授权码code无效"},
* }
*/
const result = JSON.parse(data)
const responseKey = `${method.replace(/\./g, '_')}_response`
const response = result[responseKey]
const errorResponse = result.error_response
if (response) {
// 按字符串验签
const validateSuccess = option.validateSign ? this._checkResponseSign(data, responseKey) : true
if (validateSuccess) {
if (!response.code || response.code === '10000') {
const errCode = 0
const errMsg = response.msg || ''
return {
errCode,
errMsg,
...snake2camelJson(response)
}
}
const msg = response.sub_code ? `${response.sub_code} ${response.sub_msg}` : `${response.msg || 'unkonwn error'}`
throw new Error(msg)
} else {
throw new Error('check sign error')
}
} else if (errorResponse) {
throw new Error(errorResponse.sub_msg || errorResponse.msg || 'request fail')
}
throw new Error('request fail')
}
_checkResponseSign (signStr, responseKey) {
if (!this.options.alipayPublicKey || this.options.alipayPublicKey === '') {
console.warn('options.alipayPublicKey is empty')
// 支付宝公钥不存在时不做验签
return true
}
// 带验签的参数不存在时返回失败
if (!signStr) { return false }
// 根据服务端返回的结果截取需要验签的目标字符串
const validateStr = this._getSignStr(signStr, responseKey)
// 服务端返回的签名
const serverSign = JSON.parse(signStr).sign
// 参数存在,并且是正常的结果(不包含 sub_code)时才验签
const verifier = crypto.createVerify(ALIPAY_ALGORITHM_MAPPING[this.options.signType])
verifier.update(validateStr, 'utf8')
return verifier.verify(this.options.alipayPublicKey, serverSign, 'base64')
}
_getSignStr (originStr, responseKey) {
// 待签名的字符串
let validateStr = originStr.trim()
// 找到 xxx_response 开始的位置
const startIndex = originStr.indexOf(`${responseKey}"`)
// 找到最后一个 “"sign"” 字符串的位置(避免)
const lastIndex = originStr.lastIndexOf('"sign"')
/**
* 删除 xxx_response 及之前的字符串
* 假设原始字符串为
* {"xxx_response":{"code":"10000"},"sign":"jumSvxTKwn24G5sAIN"}
* 删除后变为
* :{"code":"10000"},"sign":"jumSvxTKwn24G5sAIN"}
*/
validateStr = validateStr.substr(startIndex + responseKey.length + 1)
/**
* 删除最后一个 "sign" 及之后的字符串
* 删除后变为
* :{"code":"10000"},
* {} 之间就是待验签的字符串
*/
validateStr = validateStr.substr(0, lastIndex)
// 删除第一个 { 之前的任何字符
validateStr = validateStr.replace(/^[^{]*{/g, '{')
// 删除最后一个 } 之后的任何字符
validateStr = validateStr.replace(/\}([^}]*)$/g, '}')
return validateStr
}
}
const rsaPublicKeyPem = require('../rsa-public-key-pem')
let authKeysCache = null
module.exports = class Auth {
constructor (options) {
this.options = Object.assign({
baseUrl: 'https://appleid.apple.com',
timeout: 10000
}, options)
}
async _fetch (url, options) {
const { baseUrl } = this.options
return uniCloud.httpclient.request(baseUrl + url, options)
}
async verifyIdentityToken (identityToken) {
// 解密出kid,拿取key
const jwtHeader = identityToken.split('.')[0]
const { kid } = JSON.parse(Buffer.from(jwtHeader, 'base64').toString())
let authKeys
if (authKeysCache) {
authKeys = authKeysCache
} else {
authKeys = await this.getAuthKeys()
authKeysCache = authKeys
}
const usedKey = authKeys.find(item => item.kid === kid)
/**
* identityToken 格式
*
* {
* iss: 'https://appleid.apple.com',
* aud: 'io.dcloud.hellouniapp',
* exp: 1610626724,
* iat: 1610540324,
* sub: '000628.30119d332d9b45a3be4a297f9391fd5c.0403',
* c_hash: 'oFfgewoG36cJX00KUbj45A',
* email: 'x2awmap99s@privaterelay.appleid.com',
* email_verified: 'true',
* is_private_email: 'true',
* auth_time: 1610540324,
* nonce_supported: true
* }
*/
const payload = require('jsonwebtoken').verify(
identityToken,
rsaPublicKeyPem(usedKey.n, usedKey.e),
{
algorithms: usedKey.alg
}
)
if (payload.iss !== 'https://appleid.apple.com' || payload.aud !== this.options.bundleId) {
throw new Error('Invalid identity token')
}
return {
openid: payload.sub,
email: payload.email,
emailVerified: payload.email_verified === 'true',
isPrivateEmail: payload.is_private_email === 'true'
}
}
async getAuthKeys () {
const { status, data } = await this._fetch('/auth/keys', {
method: 'GET',
dataType: 'json',
timeout: this.options.timeout
})
if (status !== 200) throw new Error('request https://appleid.apple.com/auth/keys fail')
return data.keys
}
}
// http://stackoverflow.com/questions/18835132/xml-to-pem-in-node-js
/* eslint-disable camelcase */
function rsaPublicKeyPem (modulus_b64, exponent_b64) {
const modulus = Buffer.from(modulus_b64, 'base64')
const exponent = Buffer.from(exponent_b64, 'base64')
let modulus_hex = modulus.toString('hex')
let exponent_hex = exponent.toString('hex')
modulus_hex = prepadSigned(modulus_hex)
exponent_hex = prepadSigned(exponent_hex)
const modlen = modulus_hex.length / 2
const explen = exponent_hex.length / 2
const encoded_modlen = encodeLengthHex(modlen)
const encoded_explen = encodeLengthHex(explen)
const encoded_pubkey = '30' +
encodeLengthHex(
modlen +
explen +
encoded_modlen.length / 2 +
encoded_explen.length / 2 + 2
) +
'02' + encoded_modlen + modulus_hex +
'02' + encoded_explen + exponent_hex
const der_b64 = Buffer.from(encoded_pubkey, 'hex').toString('base64')
const pem = '-----BEGIN RSA PUBLIC KEY-----\n' +
der_b64.match(/.{1,64}/g).join('\n') +
'\n-----END RSA PUBLIC KEY-----\n'
return pem
}
function prepadSigned (hexStr) {
const msb = hexStr[0]
if (msb < '0' || msb > '7') {
return '00' + hexStr
} else {
return hexStr
}
}
function toHex (number) {
const nstr = number.toString(16)
if (nstr.length % 2) return '0' + nstr
return nstr
}
// encode ASN.1 DER length field
// if <=127, short form
// if >=128, long form
function encodeLengthHex (n) {
if (n <= 127) return toHex(n)
else {
const n_hex = toHex(n)
const length_of_length_byte = 128 + n_hex.length / 2 // 0x80+numbytes
return toHex(length_of_length_byte) + n_hex
}
}
module.exports = rsaPublicKeyPem
const WxAccount = require('./weixin/account/index')
const QQAccount = require('./qq/account/index')
const AliAccount = require('./alipay/account/index')
const AppleAccount = require('./apple/account/index')
const createApi = require('./share/create-api')
module.exports = {
initWeixin: function () {
const oauthConfig = this.configUtils.getOauthConfig({ provider: 'weixin' })
return createApi(WxAccount, {
appId: oauthConfig.appid,
secret: oauthConfig.appsecret
})
},
initQQ: function () {
const oauthConfig = this.configUtils.getOauthConfig({ provider: 'qq' })
return createApi(QQAccount, {
appId: oauthConfig.appid,
secret: oauthConfig.appsecret
})
},
initAlipay: function () {
const oauthConfig = this.configUtils.getOauthConfig({ provider: 'alipay' })
return createApi(AliAccount, {
appId: oauthConfig.appid,
privateKey: oauthConfig.privateKey
})
},
initApple: function () {
const oauthConfig = this.configUtils.getOauthConfig({ provider: 'apple' })
return createApi(AppleAccount, {
bundleId: oauthConfig.bundleId
})
}
}
const {
UniCloudError
} = require('../../../../common/error')
const {
resolveUrl
} = require('../../../../common/utils')
const {
callQQOpenApi
} = require('../normalize')
module.exports = class Auth {
constructor (options) {
this.options = Object.assign({
baseUrl: 'https://graph.qq.com',
timeout: 5000
}, options)
}
async _requestQQOpenapi ({ name, url, data, options }) {
const defaultOptions = {
method: 'GET',
dataType: 'json',
dataAsQueryString: true,
timeout: this.options.timeout
}
const result = await callQQOpenApi({
name: `auth.${name}`,
url: resolveUrl(this.options.baseUrl, url),
data,
options,
defaultOptions
})
return result
}
async getUserInfo ({
accessToken,
openid
} = {}) {
const url = '/user/get_user_info'
const result = await this._requestQQOpenapi({
name: 'getUserInfo',
url,
data: {
oauthConsumerKey: this.options.appId,
accessToken,
openid
}
})
return {
nickname: result.nickname,
avatar: result.figureurl_qq_1
}
}
async getOpenidByToken ({
accessToken
} = {}) {
const url = '/oauth2.0/me'
const result = await this._requestQQOpenapi({
name: 'getOpenidByToken',
url,
data: {
accessToken,
unionid: 1,
fmt: 'json'
}
})
if (result.clientId !== this.options.appId) {
throw new UniCloudError({
code: 'APPID_NOT_MATCH',
message: 'appid not match'
})
}
return {
openid: result.openid,
unionid: result.unionid
}
}
async code2Session ({
code
} = {}) {
const url = 'https://api.q.qq.com/sns/jscode2session'
const result = await this._requestQQOpenapi({
name: 'getOpenidByToken',
url,
data: {
grant_type: 'authorization_code',
appid: this.options.appId,
secret: this.options.secret,
js_code: code
}
})
return result
}
}
const {
UniCloudError
} = require('../../../common/error')
const {
camel2snakeJson,
snake2camelJson
} = require('../../../common/utils')
function generateApiResult (apiName, data) {
if (data.ret || data.error) {
// 这三种都是qq的错误码规范
const code = data.ret || data.error || data.errcode || -2
const message = data.msg || data.error_description || data.errmsg || `${apiName} fail`
throw new UniCloudError({
code,
message
})
} else {
delete data.ret
delete data.msg
delete data.error
delete data.error_description
delete data.errcode
delete data.errmsg
return {
...data,
errMsg: `${apiName} ok`,
errCode: 0
}
}
}
function nomalizeError (apiName, error) {
throw new UniCloudError({
code: error.code || -2,
message: error.message || `${apiName} fail`
})
}
async function callQQOpenApi ({
name,
url,
data,
options,
defaultOptions
}) {
options = Object.assign({}, defaultOptions, options, { data: camel2snakeJson(Object.assign({}, data)) })
let result
try {
result = await uniCloud.httpclient.request(url, options)
} catch (e) {
return nomalizeError(name, e)
}
let resData = result.data
const contentType = result.headers['content-type']
if (
Buffer.isBuffer(resData) &&
(contentType.indexOf('text/plain') === 0 ||
contentType.indexOf('application/json') === 0)
) {
try {
resData = JSON.parse(resData.toString())
} catch (e) {
resData = resData.toString()
}
} else if (Buffer.isBuffer(resData)) {
resData = {
buffer: resData,
contentType
}
}
return snake2camelJson(
generateApiResult(
name,
resData || {
errCode: -2,
errMsg: 'Request failed'
}
)
)
}
module.exports = {
callQQOpenApi
}
const {
isFn,
isPlainObject
} = require('../../../common/utils')
// 注意:不进行递归处理
function parseParams (params = {}, rule) {
if (!rule || !params) {
return params
}
const internalKeys = ['_pre', '_purify', '_post']
// 转换之前的处理
if (rule._pre) {
params = rule._pre(params)
}
// 净化参数
let purify = { shouldDelete: new Set([]) }
if (rule._purify) {
const _purify = rule._purify
for (const purifyKey in _purify) {
_purify[purifyKey] = new Set(_purify[purifyKey])
}
purify = Object.assign(purify, _purify)
}
if (isPlainObject(rule)) {
for (const key in rule) {
const parser = rule[key]
if (isFn(parser) && internalKeys.indexOf(key) === -1) {
params[key] = parser(params)
} else if (typeof parser === 'string' && internalKeys.indexOf(key) === -1) {
// 直接转换属性名称的删除旧属性名
params[key] = params[parser]
purify.shouldDelete.add(parser)
}
}
} else if (isFn(rule)) {
params = rule(params)
}
if (purify.shouldDelete) {
for (const item of purify.shouldDelete) {
delete params[item]
}
}
// 转换之后的处理
if (rule._post) {
params = rule._post(params)
}
return params
}
function createApi (ApiClass, options) {
const apiInstance = new ApiClass(options)
return new Proxy(apiInstance, {
get: function (obj, prop) {
if (typeof obj[prop] === 'function' && prop.indexOf('_') !== 0 && obj._protocols && obj._protocols[prop]) {
const protocol = obj._protocols[prop]
return async function (params) {
params = parseParams(params, protocol.args)
let result = await obj[prop](params)
result = parseParams(result, protocol.returnValue)
return result
}
} else {
return obj[prop]
}
}
})
}
module.exports = createApi
const {
callWxOpenApi,
buildUrl
} = require('../normalize')
module.exports = class Auth {
constructor (options) {
this.options = Object.assign({
baseUrl: 'https://api.weixin.qq.com',
timeout: 5000
}, options)
}
async _requestWxOpenapi ({ name, url, data, options }) {
const defaultOptions = {
method: 'GET',
dataType: 'json',
dataAsQueryString: true,
timeout: this.options.timeout
}
const result = await callWxOpenApi({
name: `auth.${name}`,
url: `${this.options.baseUrl}${buildUrl(url, data)}`,
data,
options,
defaultOptions
})
return result
}
async code2Session (code) {
const url = '/sns/jscode2session'
const result = await this._requestWxOpenapi({
name: 'code2Session',
url,
data: {
grant_type: 'authorization_code',
appid: this.options.appId,
secret: this.options.secret,
js_code: code
}
})
return result
}
async getOauthAccessToken (code) {
const url = '/sns/oauth2/access_token'
const result = await this._requestWxOpenapi({
name: 'getOauthAccessToken',
url,
data: {
grant_type: 'authorization_code',
appid: this.options.appId,
secret: this.options.secret,
code
}
})
if (result.expiresIn) {
result.expired = Date.now() + result.expiresIn * 1000
// delete result.expiresIn
}
return result
}
async getUserInfo ({
accessToken,
openid
} = {}) {
const url = '/sns/userinfo'
const {
nickname,
headimgurl: avatar
} = await this._requestWxOpenapi({
name: 'getUserInfo',
url,
data: {
accessToken,
openid,
appid: this.options.appId,
secret: this.options.secret,
scope: 'snsapi_userinfo'
}
})
return {
nickname,
avatar
}
}
async getmobileNumber (accessToken, code) {
const url = `/wxa/business/getusermobilenumber?access_token=${accessToken}`
const { mobileInfo } = await this._requestWxOpenapi({
name: 'getmobileNumber',
url,
data: {
code
},
options: {
method: 'POST',
dataAsQueryString: false,
headers: {
'content-type': 'application/json'
}
}
})
return {
puremobileNumber: mobileInfo.puremobileNumber
}
}
}
const {
UniCloudError
} = require('../../../common/error')
const {
camel2snakeJson, snake2camelJson
} = require('../../../common/utils')
function generateApiResult (apiName, data) {
if (data.errcode) {
throw new UniCloudError({
code: data.errcode || -2,
message: data.errmsg || `${apiName} fail`
})
} else {
delete data.errcode
delete data.errmsg
return {
...data,
errMsg: `${apiName} ok`,
errCode: 0
}
}
}
function nomalizeError (apiName, error) {
throw new UniCloudError({
code: error.code || -2,
message: error.message || `${apiName} fail`
})
}
// 微信openapi接口接收蛇形(snake case)参数返回蛇形参数,这里进行转化,如果是formdata里面的参数需要在对应api实现时就转为蛇形
async function callWxOpenApi ({
name,
url,
data,
options,
defaultOptions
}) {
let result = {}
// 获取二维码的接口wxacode.get和wxacode.getUnlimited不可以传入access_token(可能有其他接口也不可以),否则会返回data format error
const dataCopy = camel2snakeJson(Object.assign({}, data))
if (dataCopy && dataCopy.access_token) {
delete dataCopy.access_token
}
try {
options = Object.assign({}, defaultOptions, options, { data: dataCopy })
result = await uniCloud.httpclient.request(url, options)
} catch (e) {
return nomalizeError(name, e)
}
// 有几个接口成功返回buffer失败返回json,对这些接口统一成返回buffer,然后分别解析
let resData = result.data
const contentType = result.headers['content-type']
if (
Buffer.isBuffer(resData) &&
(contentType.indexOf('text/plain') === 0 ||
contentType.indexOf('application/json') === 0)
) {
try {
resData = JSON.parse(resData.toString())
} catch (e) {
resData = resData.toString()
}
} else if (Buffer.isBuffer(resData)) {
resData = {
buffer: resData,
contentType
}
}
return snake2camelJson(
generateApiResult(
name,
resData || {
errCode: -2,
errMsg: 'Request failed'
}
)
)
}
function buildUrl (url, data) {
let query = ''
if (data && data.accessToken) {
const divider = url.indexOf('?') > -1 ? '&' : '?'
query = `${divider}access_token=${data.accessToken}`
}
return `${url}${query}`
}
module.exports = {
callWxOpenApi,
buildUrl
}
const crypto = require('crypto')
const {
isPlainObject
} = require('../../../common/utils')
// 退款通知解密key=md5(key)
function decryptData (encryptedData, key, iv = '') {
// 解密
const decipher = crypto.createDecipheriv('aes-128-cbc', key, iv)
// 设置自动 padding 为 true,删除填充补位
decipher.setAutoPadding(true)
let decoded = decipher.update(encryptedData, 'base64', 'utf8')
decoded += decipher.final('utf8')
return decoded
}
function md5 (str, encoding = 'utf8') {
return crypto
.createHash('md5')
.update(str, encoding)
.digest('hex')
}
function sha256 (str, key, encoding = 'utf8') {
return crypto
.createHmac('sha256', key)
.update(str, encoding)
.digest('hex')
}
function getSignStr (obj) {
return Object.keys(obj)
.filter(key => key !== 'sign' && obj[key] !== undefined && obj[key] !== '')
.sort()
.map(key => key + '=' + obj[key])
.join('&')
}
function getNonceStr (length = 16) {
let str = ''
while (str.length < length) {
str += Math.random().toString(32).substring(2)
}
return str.substring(0, length)
}
// 简易版Object转XML,只可在微信支付时使用,不支持嵌套
function buildXML (obj, rootName = 'xml') {
const content = Object.keys(obj).map(item => {
if (isPlainObject(obj[item])) {
return `<${item}><![CDATA[${JSON.stringify(obj[item])}]]></${item}>`
} else {
return `<${item}><![CDATA[${obj[item]}]]></${item}>`
}
})
return `<${rootName}>${content.join('')}</${rootName}>`
}
function isXML (str) {
const reg = /^(<\?xml.*\?>)?(\r?\n)*<xml>(.|\r?\n)*<\/xml>$/i
return reg.test(str.trim())
};
// 简易版XML转Object,只可在微信支付时使用,不支持嵌套
function parseXML (xml) {
const xmlReg = /<(?:xml|root).*?>([\s|\S]*)<\/(?:xml|root)>/
const str = xmlReg.exec(xml)[1]
const obj = {}
const nodeReg = /<(.*?)>(?:<!\[CDATA\[){0,1}(.*?)(?:\]\]>){0,1}<\/.*?>/g
let matches = null
// eslint-disable-next-line no-cond-assign
while ((matches = nodeReg.exec(str))) {
obj[matches[1]] = matches[2]
}
return obj
}
module.exports = {
decryptData,
md5,
sha256,
getSignStr,
getNonceStr,
buildXML,
parseXML,
isXML
}
const {
dbCmd,
userCollection
} = require('../../common/constants')
const {
USER_IDENTIFIER
} = require('../../common/constants')
const {
batchFindObjctValue,
getType,
isMatchUserApp
} = require('../../common/utils')
/**
* 查询满足条件的用户
* @param {Object} params
* @param {Object} params.userQuery 用户唯一标识组成的查询条件
* @param {Object} params.authorizedApp 用户允许登录的应用
* @returns userMatched 满足条件的用户列表
*/
async function findUser (params = {}) {
const {
userQuery,
authorizedApp = []
} = params
const condition = getUserQueryCondition(userQuery)
if (condition.length === 0) {
throw new Error('Invalid user query')
}
const authorizedAppType = getType(authorizedApp)
if (authorizedAppType !== 'string' && authorizedAppType !== 'array') {
throw new Error('Invalid authorized app')
}
let finalQuery
if (condition.length === 1) {
finalQuery = condition[0]
} else {
finalQuery = dbCmd.or(condition)
}
const userQueryRes = await userCollection.where(finalQuery).get()
return {
total: userQueryRes.data.length,
userMatched: userQueryRes.data.filter(item => {
return isMatchUserApp(item.dcloud_appid, authorizedApp)
})
}
}
function getUserIdentifier (userRecord = {}) {
const keys = Object.keys(USER_IDENTIFIER)
return batchFindObjctValue(userRecord, keys)
}
function getUserQueryCondition (userRecord = {}) {
const userIdentifier = getUserIdentifier(userRecord)
const condition = []
for (const key in userIdentifier) {
const value = userIdentifier[key]
if (!value) {
// 过滤所有value为假值的条件,在查询用户时没有意义
continue
}
const queryItem = {
[key]: value
}
// 为兼容用户老数据用户名及邮箱需要同时查小写及原始大小写数据
if (key === 'mobile') {
queryItem.mobile_confirmed = 1
} else if (key === 'email') {
queryItem.email_confirmed = 1
const email = userIdentifier.email
if (email.toLowerCase() !== email) {
condition.push({
email: email.toLowerCase(),
email_confirmed: 1
})
}
} else if (key === 'username') {
const username = userIdentifier.username
if (username.toLowerCase() !== username) {
condition.push({
username: username.toLowerCase()
})
}
} else if (key === 'identities') {
queryItem.identities = dbCmd.elemMatch(value)
}
condition.push(queryItem)
}
return condition
}
module.exports = {
findUser,
getUserIdentifier
}
const {
ERROR
} = require('../../common/error')
async function getNeedCaptcha ({
uid,
username,
mobile,
email,
type = 'login',
limitDuration = 7200000, // 两小时
limitTimes = 3 // 记录次数
} = {}) {
const db = uniCloud.database()
const dbCmd = db.command
// 当用户最近“2小时内(limitDuration)”登录失败达到3次(limitTimes)时。要求用户提交验证码
const now = Date.now()
const uniIdLogCollection = db.collection('uni-id-log')
const userIdentifier = {
user_id: uid,
username,
mobile,
email
}
let totalKey = 0; let deleteKey = 0
for (const key in userIdentifier) {
totalKey++
if (!userIdentifier[key] || typeof userIdentifier[key] !== 'string') {
deleteKey++
delete userIdentifier[key]
}
}
if (deleteKey === totalKey) {
throw new Error('System error') // 正常情况下不会进入此条件,但是考虑到后续会有其他开发者修改此云对象,在此处做一个判断
}
const {
data: recentRecord
} = await uniIdLogCollection.where({
ip: this.getUniversalClientInfo().clientIP,
...userIdentifier,
type,
create_date: dbCmd.gt(now - limitDuration)
})
.orderBy('create_date', 'desc')
.limit(limitTimes)
.get()
return recentRecord.length === limitTimes && recentRecord.every(item => item.state === 0)
}
async function verifyCaptcha (params = {}) {
const {
captcha,
scene
} = params
if (!captcha) {
throw {
errCode: ERROR.CAPTCHA_REQUIRED
}
}
const payload = await this.uniCaptcha.verify({
deviceId: this.getUniversalClientInfo().deviceId,
captcha,
scene
})
if (payload.errCode) {
throw payload
}
}
module.exports = {
getNeedCaptcha,
verifyCaptcha
}
const {
getWeixinPlatform
} = require('./weixin')
const createConfig = require('uni-config-center')
const requiredConfig = {
'web.weixin-h5': ['appid', 'appsecret'],
'web.weixin-web': ['appid', 'appsecret'],
'app.weixin': ['appid', 'appsecret'],
'mp-weixin.weixin': ['appid', 'appsecret'],
'app.qq': ['appid', 'appsecret'],
'mp-alipay.alipay': ['appid', 'privateKey'],
'app.apple': ['bundleId']
}
const uniIdConfig = createConfig({
pluginId: 'uni-id'
})
class ConfigUtils {
constructor ({
context
} = {}) {
this.context = context
this.clientInfo = context.getUniversalClientInfo()
const {
appId,
uniPlatform
} = this.clientInfo
this.appId = appId
switch (uniPlatform) {
case 'app':
case 'app-plus':
this.platform = 'app'
break
case 'web':
case 'h5':
this.platform = 'web'
break
default:
this.platform = uniPlatform
break
}
}
getConfigArray () {
let configContent
try {
configContent = require('uni-config-center/uni-id/config.json')
} catch (error) {
throw new Error('Invalid config file\n' + error.message)
}
if (configContent[0]) {
return Object.values(configContent)
}
configContent.isDefaultConfig = true
return [configContent]
}
getAppConfig () {
const configArray = this.getConfigArray()
return configArray.find(item => item.dcloudAppid === this.appId) || configArray.find(item => item.isDefaultConfig)
}
getPlatformConfig () {
const appConfig = this.getAppConfig()
if (!appConfig) {
throw new Error(
`Config for current app (${this.appId}) was not found, please check your config file or client appId`)
}
const platform = this.platform
if (
(this.platform === 'app' && appConfig['app-plus']) ||
(this.platform === 'web' && appConfig.h5)
) {
throw new Error(
`Client platform is ${this.platform}, but ${this.platform === 'web' ? 'h5' : 'app-plus'} was found in config. Please refer to: https://uniapp.dcloud.net.cn/uniCloud/uni-id-summary?id=m-to-co`
)
}
const defaultConfig = {
tokenExpiresIn: 7200,
tokenExpiresThreshold: 1200,
passwordErrorLimit: 6,
passwordErrorRetryTime: 3600
}
return Object.assign(defaultConfig, appConfig, appConfig[platform])
}
getOauthProvider ({
provider
} = {}) {
const clientPlatform = this.platform
let oatuhProivder = provider
if (provider === 'weixin' && clientPlatform === 'web') {
const weixinPlatform = getWeixinPlatform.call(this.context)
if (weixinPlatform === 'h5' || weixinPlatform === 'web') {
oatuhProivder = 'weixin-' + weixinPlatform // weixin-h5 公众号,weixin-web pc端
}
}
return oatuhProivder
}
getOauthConfig ({
provider
} = {}) {
const config = this.getPlatformConfig()
const clientPlatform = this.platform
const oatuhProivder = this.getOauthProvider({
provider
})
const requireConfigKey = requiredConfig[`${clientPlatform}.${oatuhProivder}`] || []
if (!config.oauth || !config.oauth[oatuhProivder]) {
throw new Error(`Config param required: ${clientPlatform}.oauth.${oatuhProivder}`)
}
const oauthConfig = config.oauth[oatuhProivder]
requireConfigKey.forEach((item) => {
if (!oauthConfig[item]) {
throw new Error(`Config param required: ${clientPlatform}.oauth.${oatuhProivder}.${item}`)
}
})
return oauthConfig
}
getHooks () {
if (uniIdConfig.hasFile('hooks/index.js')) {
return require(
uniIdConfig.resolve('hooks/index.js')
)
}
return {}
}
}
module.exports = ConfigUtils
const {
dbCmd,
userCollection
} = require('../../common/constants')
const {
ERROR
} = require('../../common/error')
/**
* 获取随机邀请码,邀请码由大写字母加数字组成,由于存在手动输入邀请码的场景,从可选字符中去除 0、1、I、O
* @param {number} len 邀请码长度,默认6位
* @returns {string} 随机邀请码
*/
function getRandomInviteCode (len = 6) {
const charArr = ['2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
let code = ''
for (let i = 0; i < len; i++) {
code += charArr[Math.floor(Math.random() * charArr.length)]
}
return code
}
/**
* 获取可用的邀请码,至多尝试十次以获取可用邀请码。从10亿可选值中随机,碰撞概率较低
* 也有其他方案可以尝试,比如在数据库内设置一个从0开始计数的数字,每次调用此方法时使用updateAndReturn使数字加1并返回加1后的值,根据这个值生成对应的邀请码,比如(22222A + 1 == 22222B),此方式性能理论更好,但是不适用于旧项目
* @param {object} param
* @param {string} param.inviteCode 初始随机邀请码
*/
async function getValidInviteCode () {
let retry = 10
let code
let codeValid = false
while (retry > 0 && !codeValid) {
retry--
code = getRandomInviteCode()
const getUserRes = await userCollection.where({
my_invite_code: code
}).limit(1).get()
if (getUserRes.data.length === 0) {
codeValid = true
break
}
}
if (!codeValid) {
throw {
errCode: ERROR.SET_INVITE_CODE_FAILED
}
}
return code
}
/**
* 根据邀请码查询邀请人
* @param {object} param
* @param {string} param.inviteCode 邀请码
* @param {string} param.queryUid 受邀人id,非空时校验不可被下家或自己邀请
* @returns
*/
async function findUserByInviteCode ({
inviteCode,
queryUid
} = {}) {
if (typeof inviteCode !== 'string') {
throw {
errCode: ERROR.SYSTEM_ERROR
}
}
// 根据邀请码查询邀请人
let getInviterRes
if (queryUid) {
getInviterRes = await userCollection.where({
_id: dbCmd.neq(queryUid),
inviter_uid: dbCmd.not(dbCmd.all([queryUid])),
my_invite_code: inviteCode
}).get()
} else {
getInviterRes = await userCollection.where({
my_invite_code: inviteCode
}).get()
}
if (getInviterRes.data.length > 1) {
// 正常情况下不可能进入此条件,以防用户自行修改数据库出错,在此做出判断
throw {
errCode: ERROR.SYSTEM_ERROR
}
}
const inviterRecord = getInviterRes.data[0]
if (!inviterRecord) {
throw {
errCode: ERROR.INVALID_INVITE_CODE
}
}
return inviterRecord
}
/**
* 根据邀请码生成邀请信息
* @param {object} param
* @param {string} param.inviteCode 邀请码
* @param {string} param.queryUid 受邀人id,非空时校验不可被下家或自己邀请
* @returns
*/
async function generateInviteInfo ({
inviteCode,
queryUid
} = {}) {
const inviterRecord = await findUserByInviteCode({
inviteCode,
queryUid
})
// 倒叙拼接当前用户邀请链
const inviterUid = inviterRecord.inviter_uid || []
inviterUid.unshift(inviterRecord._id)
return {
inviterUid,
inviteTime: Date.now()
}
}
/**
* 检查当前用户是否可以接受邀请,如果可以返回用户记录
* @param {string} uid
*/
async function checkInviteInfo (uid) {
// 检查当前用户是否已有邀请人
const getUserRes = await userCollection.doc(uid).field({
my_invite_code: true,
inviter_uid: true
}).get()
const userRecord = getUserRes.data[0]
if (!userRecord) {
throw {
errCode: ERROR.ACCOUNT_NOT_EXISTS
}
}
if (userRecord.inviter_uid && userRecord.inviter_uid.length > 0) {
throw {
errCode: ERROR.CHANGE_INVITER_FORBIDDEN
}
}
return userRecord
}
/**
* 指定用户接受邀请码邀请
* @param {object} param
* @param {string} param.uid 用户uid
* @param {string} param.inviteCode 邀请人的邀请码
* @returns
*/
async function acceptInvite ({
uid,
inviteCode
} = {}) {
await checkInviteInfo(uid)
const {
inviterUid,
inviteTime
} = await generateInviteInfo({
inviteCode,
queryUid: uid
})
if (inviterUid === uid) {
throw {
errCode: ERROR.INVALID_INVITE_CODE
}
}
// 更新当前用户的邀请人信息
await userCollection.doc(uid).update({
inviter_uid: inviterUid,
invite_time: inviteTime
})
// 更新当前用户邀请的用户的邀请人信息,这步可能较为耗时
await userCollection.where({
inviter_uid: uid
}).update({
inviter_uid: dbCmd.push(inviterUid)
})
return {
errCode: 0,
errMsg: ''
}
}
module.exports = {
acceptInvite,
generateInviteInfo,
getValidInviteCode
}
const {
findUser
} = require('./account')
const {
userCollection,
LOG_TYPE
} = require('../../common/constants')
const {
ERROR
} = require('../../common/error')
const {
logout
} = require('./logout')
const PasswordUtils = require('./password')
async function realPreLogin (params = {}) {
const {
user
} = params
const appId = this.getUniversalClientInfo().appId
const {
total,
userMatched
} = await findUser({
userQuery: user,
authorizedApp: appId
})
if (userMatched.length === 0) {
if (total > 0) {
throw {
errCode: ERROR.ACCOUNT_NOT_EXISTS_IN_CURRENT_APP
}
}
throw {
errCode: ERROR.ACCOUNT_NOT_EXISTS
}
} else if (userMatched.length > 1) {
throw {
errCode: ERROR.ACCOUNT_CONFLICT
}
}
const userRecord = userMatched[0]
checkLoginUserRecord(userRecord)
return userRecord
}
async function preLogin (params = {}) {
const {
user
} = params
try {
const user = await realPreLogin.call(this, params)
return user
} catch (error) {
await this.middleware.uniIdLog({
success: false,
data: user,
type: LOG_TYPE.LOGIN
})
throw error
}
}
async function preLoginWithPassword (params = {}) {
const {
user,
password
} = params
try {
const userRecord = await realPreLogin.call(this, params)
const {
passwordErrorLimit,
passwordErrorRetryTime
} = this.config
const {
clientIP
} = this.getUniversalClientInfo()
// 根据ip地址,密码错误次数过多,锁定登录
let loginIPLimit = userRecord.login_ip_limit || []
// 清理无用记录
loginIPLimit = loginIPLimit.filter(item => item.last_error_time > Date.now() - passwordErrorRetryTime * 1000)
let currentIPLimit = loginIPLimit.find(item => item.ip === clientIP)
if (currentIPLimit && currentIPLimit.error_times >= passwordErrorLimit) {
throw {
errCode: ERROR.PASSWORD_ERROR_EXCEED_LIMIT
}
}
const passwordUtils = new PasswordUtils({
userRecord,
clientInfo: this.getUniversalClientInfo(),
passwordSecret: this.config.passwordSecret
})
const {
success: checkPasswordSuccess,
refreshPasswordInfo
} = passwordUtils.checkUserPassword({
password
})
if (!checkPasswordSuccess) {
// 更新用户ip对应的密码错误记录
if (!currentIPLimit) {
currentIPLimit = {
ip: clientIP,
error_times: 1,
last_error_time: Date.now()
}
loginIPLimit.push(currentIPLimit)
} else {
currentIPLimit.error_times++
currentIPLimit.last_error_time = Date.now()
}
await userCollection.doc(userRecord._id).update({
login_ip_limit: loginIPLimit
})
throw {
errCode: ERROR.PASSWORD_ERROR
}
}
const extraData = {}
if (refreshPasswordInfo) {
extraData.password = refreshPasswordInfo.passwordHash
extraData.password_secret_version = refreshPasswordInfo.version
}
const currentIPLimitIndex = loginIPLimit.indexOf(currentIPLimit)
if (currentIPLimitIndex > -1) {
loginIPLimit.splice(currentIPLimitIndex, 1)
}
extraData.login_ip_limit = loginIPLimit
return {
user: userRecord,
extraData
}
} catch (error) {
await this.middleware.uniIdLog({
success: false,
data: user,
type: LOG_TYPE.LOGIN
})
throw error
}
}
function checkLoginUserRecord (user) {
switch (user.status) {
case undefined:
case 0:
break
case 1:
throw {
errCode: ERROR.ACCOUNT_BANNED
}
case 2:
throw {
errCode: ERROR.ACCOUNT_AUDITING
}
case 3:
throw {
errCode: ERROR.ACCOUNT_AUDIT_FAILED
}
case 4:
throw {
errCode: ERROR.ACCOUNT_CLOSED
}
default:
break
}
}
async function thirdPartyLogin (params = {}) {
const {
user
} = params
return {
mobileConfirmed: !!user.mobile_confirmed,
emailConfirmed: !!user.email_confirmed
}
}
async function postLogin (params = {}) {
const {
user,
extraData,
isThirdParty = false
} = params
const {
clientIP
} = this.getUniversalClientInfo()
const uniIdToken = this.getUniversalUniIdToken()
const uid = user._id
const updateData = {
last_login_date: Date.now(),
last_login_ip: clientIP,
...extraData
}
const createTokenRes = await this.uniIdCommon.createToken({
uid
})
const {
errCode,
token,
tokenExpired
} = createTokenRes
if (errCode) {
throw createTokenRes
}
if (uniIdToken) {
try {
await logout.call(this)
} catch (error) {}
}
await userCollection.doc(uid).update(updateData)
await this.middleware.uniIdLog({
data: {
user_id: uid
},
type: LOG_TYPE.LOGIN
})
return {
errCode: 0,
newToken: {
token,
tokenExpired
},
uid,
...(
isThirdParty
? thirdPartyLogin({
user
})
: {}
),
passwordConfirmed: !!user.password
}
}
module.exports = {
preLogin,
postLogin,
checkLoginUserRecord,
preLoginWithPassword
}
const {
dbCmd,
LOG_TYPE,
deviceCollection,
userCollection
} = require('../../common/constants')
async function logout () {
const {
deviceId
} = this.getUniversalClientInfo()
const uniIdToken = this.getUniversalUniIdToken()
const payload = await this.uniIdCommon.checkToken(
uniIdToken,
{
autoRefresh: false
}
)
if (payload.errCode) {
throw payload
}
const uid = payload.uid
// 删除token
await userCollection.doc(uid).update({
token: dbCmd.pull(uniIdToken)
})
// 仅当device表的device_id和user_id均对应时才进行更新
await deviceCollection.where({
device_id: deviceId,
user_id: uid
}).update({
token_expired: 0
})
await this.middleware.uniIdLog({
data: {
user_id: uid
},
type: LOG_TYPE.LOGOUT
})
return {
errCode: 0
}
}
module.exports = {
logout
}
const {
getType
} = require('../../common/utils')
const crypto = require('crypto')
const createConfig = require('uni-config-center')
const shareConfig = createConfig({
pluginId: 'uni-id'
})
let customPassword = {}
if (shareConfig.hasFile('custom-password.js')) {
customPassword = shareConfig.requireFile('custom-password.js') || {}
}
const passwordAlgorithmMap = {
UNI_ID_HMAC_SHA1: 'hmac-sha1',
UNI_ID_HMAC_SHA256: 'hmac-sha256',
UNI_ID_CUSTOM: 'custom'
}
const passwordAlgorithmKeyMap = Object.keys(passwordAlgorithmMap).reduce((res, item) => {
res[passwordAlgorithmMap[item]] = item
return res
}, {})
const passwordExtMethod = {
[passwordAlgorithmMap.UNI_ID_HMAC_SHA1]: {
verify ({ password }) {
const { password_secret_version: passwordSecretVersion } = this.userRecord
const passwordSecret = this._getSecretByVersion({
version: passwordSecretVersion
})
const { passwordHash } = this.encrypt({
password,
passwordSecret
})
return passwordHash === this.userRecord.password
},
encrypt ({ password, passwordSecret }) {
const { value: secret, version } = passwordSecret
const hmac = crypto.createHmac('sha1', secret.toString('ascii'))
hmac.update(password)
return {
passwordHash: hmac.digest('hex'),
version
}
}
},
[passwordAlgorithmMap.UNI_ID_HMAC_SHA256]: {
verify ({ password }) {
const parse = this._parsePassword()
const passwordHash = crypto.createHmac(parse.algorithm, parse.salt).update(password).digest('hex')
return passwordHash === parse.hash
},
encrypt ({ password, passwordSecret }) {
const { version } = passwordSecret
// 默认使用 sha256 加密算法
const salt = crypto.randomBytes(10).toString('hex')
const sha256Hash = crypto.createHmac(passwordAlgorithmMap.UNI_ID_HMAC_SHA256.substring(5), salt).update(password).digest('hex')
const algorithm = passwordAlgorithmKeyMap[passwordAlgorithmMap.UNI_ID_HMAC_SHA256]
// B 为固定值,对应 PasswordMethodMaps 中的 sha256算法
// hash 格式 $[PasswordMethodFlagMapsKey]$[salt size]$[salt][Hash]
const passwordHash = `$${algorithm}$${salt.length}$${salt}${sha256Hash}`
return {
passwordHash,
version
}
}
},
[passwordAlgorithmMap.UNI_ID_CUSTOM]: {
verify ({ password, passwordSecret }) {
if (!customPassword.verifyPassword) throw new Error('verifyPassword method not found in custom password file')
// return true or false
return customPassword.verifyPassword({
password,
passwordSecret,
userRecord: this.userRecord,
clientInfo: this.clientInfo
})
},
encrypt ({ password, passwordSecret }) {
if (!customPassword.encryptPassword) throw new Error('encryptPassword method not found in custom password file')
// return object<{passwordHash: string, version: number}>
return customPassword.encryptPassword({
password,
passwordSecret,
clientInfo: this.clientInfo
})
}
}
}
class PasswordUtils {
constructor ({
userRecord = {},
clientInfo,
passwordSecret
} = {}) {
if (!clientInfo) throw new Error('Invalid clientInfo')
if (!passwordSecret) throw new Error('Invalid password secret')
this.clientInfo = clientInfo
this.userRecord = userRecord
this.passwordSecret = this.prePasswordSecret(passwordSecret)
}
/**
* passwordSecret 预处理
* @param passwordSecret
* @return {*[]}
*/
prePasswordSecret (passwordSecret) {
const newPasswordSecret = []
if (getType(passwordSecret) === 'string') {
newPasswordSecret.push({
value: passwordSecret,
type: passwordAlgorithmMap.UNI_ID_HMAC_SHA1
})
} else if (getType(passwordSecret) === 'array') {
for (const secret of passwordSecret.sort((a, b) => a.version - b.version)) {
newPasswordSecret.push({
...secret,
// 没有 type 设置默认 type hmac-sha1
type: secret.type || passwordAlgorithmMap.UNI_ID_HMAC_SHA1
})
}
} else {
throw new Error('Invalid password secret')
}
return newPasswordSecret
}
/**
* 获取最新加密密钥
* @return {*}
* @private
*/
_getLastestSecret () {
return this.passwordSecret[this.passwordSecret.length - 1]
}
_getOldestSecret () {
return this.passwordSecret[0]
}
_getSecretByVersion ({ version } = {}) {
if (!version && version !== 0) {
return this._getOldestSecret()
}
if (this.passwordSecret.length === 1) {
return this.passwordSecret[0]
}
return this.passwordSecret.find(item => item.version === version)
}
/**
* 获取密码的验证/加密方法
* @param passwordSecret
* @return {*[]}
* @private
*/
_getPasswordExt (passwordSecret) {
const ext = passwordExtMethod[passwordSecret.type]
if (!ext) {
throw new Error(`暂不支持 ${passwordSecret.type} 类型的加密算法`)
}
const passwordExt = Object.create(null)
for (const key in ext) {
passwordExt[key] = ext[key].bind(Object.assign(this, Object.keys(ext).reduce((res, item) => {
if (item !== key) {
res[item] = ext[item].bind(this)
}
return res
}, {})))
}
return passwordExt
}
_parsePassword () {
const [algorithmKey = '', cost = 0, hashStr = ''] = this.userRecord.password.split('$').filter(key => key)
const algorithm = passwordAlgorithmMap[algorithmKey] ? passwordAlgorithmMap[algorithmKey].substring(5) : null
const salt = hashStr.substring(0, Number(cost))
const hash = hashStr.substring(Number(cost))
return {
algorithm,
salt,
hash
}
}
/**
* 生成加密后的密码
* @param {String} password 密码
*/
generatePasswordHash ({ password }) {
if (!password) throw new Error('Invalid password')
const passwordSecret = this._getLastestSecret()
const ext = this._getPasswordExt(passwordSecret)
const { passwordHash, version } = ext.encrypt({
password,
passwordSecret
})
return {
passwordHash,
version
}
}
/**
* 密码校验
* @param {String} password
* @param {Boolean} autoRefresh
* @return {{refreshPasswordInfo: {version: *, passwordHash: *}, success: boolean}|{success: boolean}}
*/
checkUserPassword ({ password, autoRefresh = true }) {
if (!password) throw new Error('Invalid password')
const { password_secret_version: passwordSecretVersion } = this.userRecord
const passwordSecret = this._getSecretByVersion({
version: passwordSecretVersion
})
const ext = this._getPasswordExt(passwordSecret)
const success = ext.verify({ password, passwordSecret })
if (!success) {
return {
success: false
}
}
let refreshPasswordInfo
if (autoRefresh && passwordSecretVersion !== this._getLastestSecret().version) {
refreshPasswordInfo = this.generatePasswordHash({ password })
}
return {
success: true,
refreshPasswordInfo
}
}
}
module.exports = PasswordUtils
const {
userCollection
} = require('../../common/constants')
const {
ERROR
} = require('../../common/error')
function getQQPlatform () {
const platform = this.clientPlatform
switch (platform) {
case 'app':
case 'app-plus':
return 'app'
case 'mp-qq':
return 'mp'
default:
throw new Error('Unsupported qq platform')
}
}
async function saveQQUserKey ({
openid,
sessionKey, // QQ小程序用户sessionKey
accessToken, // App端QQ用户accessToken
accessTokenExpired // App端QQ用户accessToken过期时间
} = {}) {
// 微信公众平台、开放平台refreshToken有效期均为30天(微信没有在网络请求里面返回30天这个值,务必注意未来可能出现调整,需及时更新此处逻辑)。
// 此前QQ开放平台有调整过accessToken的过期时间:[access_token有效期由90天缩短至30天](https://wiki.connect.qq.com/%E3%80%90qq%E4%BA%92%E8%81%94%E3%80%91access_token%E6%9C%89%E6%95%88%E6%9C%9F%E8%B0%83%E6%95%B4)
const appId = this.getUniversalClientInfo().appId
const qqPlatform = getQQPlatform.call(this)
const keyObj = {
dcloudAppid: appId,
openid,
platform: 'qq-' + qqPlatform
}
switch (qqPlatform) {
case 'mp':
await this.uniOpenBridge.setSessionKey(keyObj, {
session_key: sessionKey
}, 30 * 24 * 60 * 60)
break
case 'app':
case 'h5':
case 'web':
await this.uniOpenBridge.setUserAccessToken(keyObj, {
access_token: accessToken,
access_token_expired: accessTokenExpired
}, accessTokenExpired
? Math.floor((accessTokenExpired - Date.now()) / 1000)
: 30 * 24 * 60 * 60
)
break
default:
break
}
}
function generateQQCache ({
sessionKey, // QQ小程序用户sessionKey
accessToken, // App端QQ用户accessToken
accessTokenExpired // App端QQ用户accessToken过期时间
} = {}) {
const platform = getQQPlatform.call(this)
let cache
switch (platform) {
case 'app':
cache = {
access_token: accessToken,
access_token_expired: accessTokenExpired
}
break
case 'mp':
cache = {
session_key: sessionKey
}
break
default:
throw new Error('Unsupported qq platform')
}
return {
third_party: {
[`${platform}_qq`]: cache
}
}
}
function getQQOpenid ({
userRecord
} = {}) {
const qqPlatform = getQQPlatform.call(this)
const appId = this.getUniversalClientInfo().appId
const qqOpenidObj = userRecord.qq_openid
if (!qqOpenidObj) {
return
}
return qqOpenidObj[`${qqPlatform}_${appId}`] || qqOpenidObj[qqPlatform]
}
async function getQQCacheFallback ({
userRecord,
key
} = {}) {
const platform = getQQPlatform.call(this)
const thirdParty = userRecord && userRecord.third_party
if (!thirdParty) {
return
}
const qqCache = thirdParty[`${platform}_qq`]
return qqCache && qqCache[key]
}
async function getQQCache ({
uid,
userRecord,
key
} = {}) {
const qqPlatform = getQQPlatform.call(this)
const appId = this.getUniversalClientInfo().appId
if (!userRecord) {
const getUserRes = await userCollection.doc(uid).get()
userRecord = getUserRes.data[0]
}
if (!userRecord) {
throw {
errCode: ERROR.ACCOUNT_NOT_EXISTS
}
}
const openid = getQQOpenid.call(this, {
userRecord
})
const getCacheMethod = qqPlatform === 'mp' ? 'getSessionKey' : 'getUserAccessToken'
const userKey = await this.uniOpenBridge[getCacheMethod]({
dcloudAppid: appId,
platform: 'qq-' + qqPlatform,
openid
})
if (userKey) {
return userKey[key]
}
return getQQCacheFallback({
userRecord,
key
})
}
module.exports = {
getQQPlatform,
generateQQCache,
getQQCache,
saveQQUserKey
}
const {
userCollection,
LOG_TYPE
} = require('../../common/constants')
const {
ERROR
} = require('../../common/error')
const {
findUser
} = require('./account')
const {
getValidInviteCode,
generateInviteInfo
} = require('./fission')
const {
logout
} = require('./logout')
const PasswordUtils = require('./password')
const merge = require('lodash.merge')
async function realPreRegister (params = {}) {
const {
user
} = params
const {
userMatched
} = await findUser({
userQuery: user,
authorizedApp: this.getUniversalClientInfo().appId
})
if (userMatched.length > 0) {
throw {
errCode: ERROR.ACCOUNT_EXISTS
}
}
}
async function preRegister (params = {}) {
try {
await realPreRegister.call(this, params)
} catch (error) {
await this.middleware.uniIdLog({
success: false,
type: LOG_TYPE.REGISTER
})
throw error
}
}
async function preRegisterWithPassword (params = {}) {
const {
user,
password
} = params
await preRegister.call(this, {
user
})
const passwordUtils = new PasswordUtils({
clientInfo: this.getUniversalClientInfo(),
passwordSecret: this.config.passwordSecret
})
const {
passwordHash,
version
} = passwordUtils.generatePasswordHash({
password
})
const extraData = {
password: passwordHash,
password_secret_version: version
}
return {
user,
extraData
}
}
async function thirdPartyRegister ({
user = {}
} = {}) {
return {
mobileConfirmed: !!(user.mobile && user.mobile_confirmed) || false,
emailConfirmed: !!(user.email && user.email_confirmed) || false
}
}
async function postRegister (params = {}) {
const {
user,
extraData = {},
isThirdParty = false,
inviteCode
} = params
const {
appId,
appName,
appVersion,
appVersionCode,
channel,
scene,
clientIP,
osName
} = this.getUniversalClientInfo()
const uniIdToken = this.getUniversalUniIdToken()
merge(user, extraData)
const registerChannel = channel || scene
user.register_env = {
appid: appId || '',
uni_platform: this.clientPlatform || '',
os_name: osName || '',
app_name: appName || '',
app_version: appVersion || '',
app_version_code: appVersionCode || '',
channel: registerChannel ? registerChannel + '' : '', // channel可能为数字,统一存为字符串
client_ip: clientIP || ''
}
user.register_date = Date.now()
user.dcloud_appid = [appId]
if (user.username) {
user.username = user.username.toLowerCase()
}
if (user.email) {
user.email = user.email.toLowerCase()
}
const {
autoSetInviteCode, // 注册时自动设置邀请码
forceInviteCode, // 必须有邀请码才允许注册,注意此逻辑不可对admin生效
userRegisterDefaultRole // 用户注册时配置的默认角色
} = this.config
if (autoSetInviteCode) {
user.my_invite_code = await getValidInviteCode()
}
// 如果用户注册默认角色配置存在且不为空数组
if (userRegisterDefaultRole && userRegisterDefaultRole.length) {
// 将用户已有的角色和配置的默认角色合并成一个数组,并去重
user.role = Array.from(new Set([...(user.role || []), ...userRegisterDefaultRole]))
}
const isAdmin = user.role && user.role.includes('admin')
if (forceInviteCode && !isAdmin && !inviteCode) {
throw {
errCode: ERROR.INVALID_INVITE_CODE
}
}
if (inviteCode) {
const {
inviterUid,
inviteTime
} = await generateInviteInfo({
inviteCode
})
user.inviter_uid = inviterUid
user.invite_time = inviteTime
}
if (uniIdToken) {
try {
await logout.call(this)
} catch (error) { }
}
const beforeRegister = this.hooks.beforeRegister
let userRecord = user
if (beforeRegister) {
userRecord = await beforeRegister({
userRecord,
clientInfo: this.getUniversalClientInfo()
})
}
const {
id: uid
} = await userCollection.add(userRecord)
const createTokenRes = await this.uniIdCommon.createToken({
uid
})
const {
errCode,
token,
tokenExpired
} = createTokenRes
if (errCode) {
throw createTokenRes
}
await this.middleware.uniIdLog({
data: {
user_id: uid
},
type: LOG_TYPE.REGISTER
})
return {
errCode: 0,
uid,
newToken: {
token,
tokenExpired
},
...(
isThirdParty
? thirdPartyRegister({
user: {
...userRecord,
_id: uid
}
})
: {}
),
passwordConfirmed: !!userRecord.password
}
}
module.exports = {
preRegister,
preRegisterWithPassword,
postRegister
}
const {
findUser
} = require('./account')
const {
ERROR
} = require('../../common/error')
const {
userCollection, dbCmd, USER_IDENTIFIER
} = require('../../common/constants')
const {
getUserIdentifier
} = require('../../lib/utils/account')
const {
batchFindObjctValue
} = require('../../common/utils')
const merge = require('lodash.merge')
/**
*
* @param {object} param
* @param {string} param.uid 用户id
* @param {string} param.bindAccount 要绑定的三方账户、手机号或邮箱
*/
async function preBind ({
uid,
bindAccount,
logType
} = {}) {
const {
userMatched
} = await findUser({
userQuery: bindAccount,
authorizedApp: this.getUniversalClientInfo().appId
})
if (userMatched.length > 0) {
await this.middleware.uniIdLog({
data: {
user_id: uid
},
type: logType,
success: false
})
throw {
errCode: ERROR.BIND_CONFLICT
}
}
}
async function postBind ({
uid,
extraData = {},
bindAccount,
logType
} = {}) {
await userCollection.doc(uid).update(merge(bindAccount, extraData))
await this.middleware.uniIdLog({
data: {
user_id: uid
},
type: logType
})
return {
errCode: 0
}
}
async function preUnBind ({
uid,
unBindAccount,
logType
}) {
const notUnBind = ['username', 'mobile', 'email']
const userIdentifier = getUserIdentifier(unBindAccount)
const condition = Object.keys(userIdentifier).reduce((res, key) => {
if (userIdentifier[key]) {
if (notUnBind.includes(key)) {
throw {
errCode: ERROR.UNBIND_NOT_SUPPORTED
}
}
res.push({
[key]: userIdentifier[key]
})
}
return res
}, [])
const currentUnBindAccount = Object.keys(userIdentifier).reduce((res, key) => {
if (userIdentifier[key]) {
res.push(key)
}
return res
}, [])
const { data: users } = await userCollection.where(dbCmd.and(
{ _id: uid },
dbCmd.or(condition)
)).get()
if (users.length <= 0) {
await this.middleware.uniIdLog({
data: {
user_id: uid
},
type: logType,
success: false
})
throw {
errCode: ERROR.UNBIND_FAIL
}
}
const [user] = users
const otherAccounts = batchFindObjctValue(user, Object.keys(USER_IDENTIFIER).filter(key => !notUnBind.includes(key) && !currentUnBindAccount.includes(key)))
let hasOtherAccountBind = false
for (const key in otherAccounts) {
if (otherAccounts[key]) {
hasOtherAccountBind = true
break
}
}
// 如果没有其他第三方登录方式
if (!hasOtherAccountBind) {
// 存在用户名或者邮箱但是没有设置过没密码就提示设置密码
if ((user.username || user.email) && !user.password) {
throw {
errCode: ERROR.UNBIND_PASSWORD_NOT_EXISTS
}
}
// 账号任何登录方式都没有就优先绑定手机号
if (!user.mobile) {
throw {
errCode: ERROR.UNBIND_MOBILE_NOT_EXISTS
}
}
}
}
async function postUnBind ({
uid,
unBindAccount,
logType
}) {
await userCollection.doc(uid).update(unBindAccount)
await this.middleware.uniIdLog({
data: {
user_id: uid
},
type: logType
})
return {
errCode: 0
}
}
module.exports = {
preBind,
postBind,
preUnBind,
postUnBind
}
const {
setMobileVerifyCode
} = require('./verify-code')
const {
getVerifyCode
} = require('../../common/utils')
/**
* 发送短信
* @param {object} param
* @param {string} param.mobile 手机号
* @param {object} param.code 可选,验证码
* @param {object} param.scene 短信场景
* @param {object} param.templateId 可选,短信模板id
* @returns
*/
async function sendSmsCode ({
mobile,
code,
scene,
templateId
} = {}) {
const requiredParams = [
'name',
'smsKey',
'smsSecret',
'codeExpiresIn'
]
const smsConfig = (this.config.service && this.config.service.sms) || {}
for (let i = 0; i < requiredParams.length; i++) {
const key = requiredParams[i]
if (!smsConfig[key]) {
throw new Error(`Missing config param: service.sms.${key}`)
}
}
if (!code) {
code = getVerifyCode()
}
let action
switch (scene) {
case 'login-by-sms':
action = this.t('login')
break
default:
action = this.t('verify-mobile')
break
}
const sceneConfig = (smsConfig.scene || {})[scene] || {}
if (!templateId) {
templateId = sceneConfig.templateId
}
if (!templateId) {
throw new Error('"templateId" is required')
}
const codeExpiresIn = sceneConfig.codeExpiresIn || smsConfig.codeExpiresIn
await setMobileVerifyCode.call(this, {
mobile,
code,
expiresIn: codeExpiresIn,
scene
})
await uniCloud.sendSms({
smsKey: smsConfig.smsKey,
smsSecret: smsConfig.smsSecret,
mobile: mobile,
templateId,
data: {
name: smsConfig.name,
code,
action,
expMinute: '' + Math.round(codeExpiresIn / 60)
}
})
return {
errCode: 0
}
}
module.exports = {
sendSmsCode
}
const {
checkLoginUserRecord,
postLogin
} = require('./login')
const {
postRegister
} = require('./register')
const {
findUser
} = require('./account')
const {
ERROR
} = require('../../common/error')
async function realPreUnifiedLogin (params = {}) {
const {
user,
type
} = params
const appId = this.getUniversalClientInfo().appId
const {
total,
userMatched
} = await findUser({
userQuery: user,
authorizedApp: appId
})
if (userMatched.length === 0) {
if (type === 'login') {
if (total > 0) {
throw {
errCode: ERROR.ACCOUNT_NOT_EXISTS_IN_CURRENT_APP
}
}
throw {
errCode: ERROR.ACCOUNT_NOT_EXISTS
}
}
return {
type: 'register',
user
}
} if (userMatched.length === 1) {
if (type === 'register') {
throw {
errCode: ERROR.ACCOUNT_EXISTS
}
}
const userRecord = userMatched[0]
checkLoginUserRecord(userRecord)
return {
type: 'login',
user: userRecord
}
} else if (userMatched.length > 1) {
throw {
errCode: ERROR.ACCOUNT_CONFLICT
}
}
}
async function preUnifiedLogin (params = {}) {
try {
const result = await realPreUnifiedLogin.call(this, params)
return result
} catch (error) {
await this.middleware.uniIdLog({
success: false
})
throw error
}
}
async function postUnifiedLogin (params = {}) {
const {
user,
extraData = {},
isThirdParty = false,
type,
inviteCode
} = params
let result
if (type === 'login') {
result = await postLogin.call(this, {
user,
extraData,
isThirdParty
})
} else if (type === 'register') {
result = await postRegister.call(this, {
user,
extraData,
isThirdParty,
inviteCode
})
}
return {
...result,
type
}
}
module.exports = {
preUnifiedLogin,
postUnifiedLogin
}
async function getmobileNumber ({
// eslint-disable-next-line camelcase
access_token,
openid
} = {}) {
const requiredParams = ['apiKey', 'apiSecret']
const univerifyConfig = (this.config.service && this.config.service.univerify) || {}
for (let i = 0; i < requiredParams.length; i++) {
const key = requiredParams[i]
if (!univerifyConfig[key]) {
throw new Error(`Missing config param: service.univerify.${key}`)
}
}
return uniCloud.getmobileNumber({
provider: 'univerify',
appid: this.getUniversalClientInfo().appId,
apiKey: univerifyConfig.apiKey,
apiSecret: univerifyConfig.apiSecret,
// eslint-disable-next-line camelcase
access_token,
openid
})
}
module.exports = {
getmobileNumber
}
const {
userCollection
} = require('../../common/constants')
const {
USER_STATUS
} = require('../../common/constants')
async function setUserStatus (uid, status) {
const updateData = {
status
}
if (status !== USER_STATUS.NORMAL) {
updateData.valid_token_date = Date.now()
}
await userCollection.doc(uid).update({
status
})
// TODO 此接口尚不完善,例如注销后其他客户端可能存在有效token,支持Redis后此处会补充额外逻辑
return {
errCode: 0
}
}
module.exports = {
setUserStatus
}
let redisEnable = null
function getRedisEnable() {
// 未用到的时候不调用redis接口,节省一些连接数
if (redisEnable !== null) {
return redisEnable
}
try {
uniCloud.redis()
redisEnable = true
} catch (error) {
redisEnable = false
}
return redisEnable
}
module.exports = {
getRedisEnable
}
\ No newline at end of file
const {
dbCmd,
verifyCollection
} = require('../../common/constants')
const {
ERROR
} = require('../../common/error')
const {
getVerifyCode
} = require('../../common/utils')
async function setVerifyCode ({
mobile,
email,
code,
expiresIn,
scene
} = {}) {
const now = Date.now()
const record = {
mobile,
email,
scene,
code: code || getVerifyCode(),
state: 0,
ip: this.getUniversalClientInfo().clientIP,
created_date: now,
expired_date: now + expiresIn * 1000
}
await verifyCollection.add(record)
return {
errCode: 0
}
}
async function setEmailVerifyCode ({
email,
code,
expiresIn,
scene
} = {}) {
email = email && email.trim()
if (!email) {
throw {
errCode: ERROR.INVALID_EMAIL
}
}
email = email.toLowerCase()
return setVerifyCode.call(this, {
email,
code,
expiresIn,
scene
})
}
async function setMobileVerifyCode ({
mobile,
code,
expiresIn,
scene
} = {}) {
mobile = mobile && mobile.trim()
if (!mobile) {
throw {
errCode: ERROR.INVALID_MOBILE
}
}
return setVerifyCode.call(this, {
mobile,
code,
expiresIn,
scene
})
}
async function verifyEmailCode ({
email,
code,
scene
} = {}) {
email = email && email.trim()
if (!email) {
throw {
errCode: ERROR.INVALID_EMAIL
}
}
email = email.toLowerCase()
const {
data: codeRecord
} = await verifyCollection.where({
email,
scene,
code,
state: 0,
expired_date: dbCmd.gt(Date.now())
}).limit(1).get()
if (codeRecord.length === 0) {
throw {
errCode: ERROR.EMAIL_VERIFY_CODE_ERROR
}
}
await verifyCollection.doc(codeRecord[0]._id).update({
state: 1
})
return {
errCode: 0
}
}
async function verifyMobileCode ({
mobile,
code,
scene
} = {}) {
mobile = mobile && mobile.trim()
if (!mobile) {
throw {
errCode: ERROR.INVALID_MOBILE
}
}
const {
data: codeRecord
} = await verifyCollection.where({
mobile,
scene,
code,
state: 0,
expired_date: dbCmd.gt(Date.now())
}).limit(1).get()
if (codeRecord.length === 0) {
throw {
errCode: ERROR.MOBILE_VERIFY_CODE_ERROR
}
}
await verifyCollection.doc(codeRecord[0]._id).update({
state: 1
})
return {
errCode: 0
}
}
module.exports = {
verifyEmailCode,
verifyMobileCode,
setEmailVerifyCode,
setMobileVerifyCode
}
const crypto = require('crypto')
const {
userCollection
} = require('../../common/constants')
const {
ERROR
} = require('../../common/error')
const {
getRedisEnable
} = require('./utils')
const {
openDataCollection
} = require('../../common/constants')
function decryptWeixinData ({
encryptedData,
sessionKey,
iv
} = {}) {
const oauthConfig = this.configUtils.getOauthConfig({
provider: 'weixin'
})
const decipher = crypto.createDecipheriv(
'aes-128-cbc',
Buffer.from(sessionKey, 'base64'),
Buffer.from(iv, 'base64')
)
// 设置自动 padding 为 true,删除填充补位
decipher.setAutoPadding(true)
let decoded
decoded = decipher.update(encryptedData, 'base64', 'utf8')
decoded += decipher.final('utf8')
decoded = JSON.parse(decoded)
if (decoded.watermark.appid !== oauthConfig.appid) {
throw new Error('Invalid wechat appid in decode content')
}
return decoded
}
function getWeixinPlatform () {
const platform = this.clientPlatform
const userAgent = this.getUniversalClientInfo().userAgent
switch (platform) {
case 'app':
case 'app-plus':
return 'app'
case 'mp-weixin':
return 'mp'
case 'h5':
case 'web':
return userAgent.indexOf('MicroMessenger') > -1 ? 'h5' : 'web'
default:
throw new Error('Unsupported weixin platform')
}
}
async function saveWeixinUserKey ({
openid,
sessionKey, // 微信小程序用户sessionKey
accessToken, // App端微信用户accessToken
refreshToken, // App端微信用户refreshToken
accessTokenExpired // App端微信用户accessToken过期时间
} = {}) {
// 微信公众平台、开放平台refreshToken有效期均为30天(微信没有在网络请求里面返回30天这个值,务必注意未来可能出现调整,需及时更新此处逻辑)。
// 此前QQ开放平台有调整过accessToken的过期时间:[access_token有效期由90天缩短至30天](https://wiki.connect.qq.com/%E3%80%90qq%E4%BA%92%E8%81%94%E3%80%91access_token%E6%9C%89%E6%95%88%E6%9C%9F%E8%B0%83%E6%95%B4)
const appId = this.getUniversalClientInfo().appId
const weixinPlatform = getWeixinPlatform.call(this)
const keyObj = {
dcloudAppid: appId,
openid,
platform: 'weixin-' + weixinPlatform
}
switch (weixinPlatform) {
case 'mp':
await this.uniOpenBridge.setSessionKey(keyObj, {
session_key: sessionKey
}, 30 * 24 * 60 * 60)
break
case 'app':
case 'h5':
case 'web':
await this.uniOpenBridge.setUserAccessToken(keyObj, {
access_token: accessToken,
refresh_token: refreshToken,
access_token_expired: accessTokenExpired
}, 30 * 24 * 60 * 60)
break
default:
break
}
}
async function saveSecureNetworkCache ({
code,
openid,
unionid,
sessionKey
}) {
const {
appId
} = this.getUniversalClientInfo()
const key = `uni-id:${appId}:weixin-mp:code:${code}:secure-network-cache`
const value = JSON.stringify({
openid,
unionid,
session_key: sessionKey
})
// 此处存储的是code的缓存,设置有效期和token一致
const expiredSeconds = this.config.tokenExpiresIn || 3 * 24 * 60 * 60
await openDataCollection.doc(key).set({
value,
expired: Date.now() + expiredSeconds * 1000
})
const isRedisEnable = getRedisEnable()
if (isRedisEnable) {
const redis = uniCloud.redis()
await redis.set(key, value, 'EX', expiredSeconds)
}
}
function generateWeixinCache ({
sessionKey, // 微信小程序用户sessionKey
accessToken, // App端微信用户accessToken
refreshToken, // App端微信用户refreshToken
accessTokenExpired // App端微信用户accessToken过期时间
} = {}) {
const platform = getWeixinPlatform.call(this)
let cache
switch (platform) {
case 'app':
case 'h5':
case 'web':
cache = {
access_token: accessToken,
refresh_token: refreshToken,
access_token_expired: accessTokenExpired
}
break
case 'mp':
cache = {
session_key: sessionKey
}
break
default:
throw new Error('Unsupported weixin platform')
}
return {
third_party: {
[`${platform}_weixin`]: cache
}
}
}
function getWeixinOpenid ({
userRecord
} = {}) {
const weixinPlatform = getWeixinPlatform.call(this)
const appId = this.getUniversalClientInfo().appId
const wxOpenidObj = userRecord.wx_openid
if (!wxOpenidObj) {
return
}
return wxOpenidObj[`${weixinPlatform}_${appId}`] || wxOpenidObj[weixinPlatform]
}
async function getWeixinCacheFallback ({
userRecord,
key
} = {}) {
const platform = getWeixinPlatform.call(this)
const thirdParty = userRecord && userRecord.third_party
if (!thirdParty) {
return
}
const weixinCache = thirdParty[`${platform}_weixin`]
return weixinCache && weixinCache[key]
}
async function getWeixinCache ({
uid,
userRecord,
key
} = {}) {
const weixinPlatform = getWeixinPlatform.call(this)
const appId = this.getUniversalClientInfo().appId
if (!userRecord) {
const getUserRes = await userCollection.doc(uid).get()
userRecord = getUserRes.data[0]
}
if (!userRecord) {
throw {
errCode: ERROR.ACCOUNT_NOT_EXISTS
}
}
const openid = getWeixinOpenid.call(this, {
userRecord
})
const getCacheMethod = weixinPlatform === 'mp' ? 'getSessionKey' : 'getUserAccessToken'
const userKey = await this.uniOpenBridge[getCacheMethod]({
dcloudAppid: appId,
platform: 'weixin-' + weixinPlatform,
openid
})
if (userKey) {
return userKey[key]
}
return getWeixinCacheFallback({
userRecord,
key
})
}
async function getWeixinAccessToken () {
const weixinPlatform = getWeixinPlatform.call(this)
const appId = this.getUniversalClientInfo().appId
const cache = await this.uniOpenBridge.getAccessToken({
dcloudAppid: appId,
platform: 'weixin-' + weixinPlatform
})
return cache.access_token
}
module.exports = {
decryptWeixinData,
getWeixinPlatform,
generateWeixinCache,
getWeixinCache,
saveWeixinUserKey,
getWeixinAccessToken,
saveSecureNetworkCache
}
const methodPermission = require('../config/permission')
const {
ERROR
} = require('../common/error')
function isAccessAllowed (user, setting) {
const {
role: userRole = [],
permission: userPermission = []
} = user
const {
role: settingRole = [],
permission: settingPermission = []
} = setting
if (userRole.includes('admin')) {
return
}
if (
settingRole.length > 0 &&
settingRole.every(item => !userRole.includes(item))
) {
throw {
errCode: ERROR.PERMISSION_ERROR
}
}
if (
settingPermission.length > 0 &&
settingPermission.every(item => !userPermission.includes(item))
) {
throw {
errCode: ERROR.PERMISSION_ERROR
}
}
}
module.exports = async function () {
const methodName = this.getMethodName()
if (!(methodName in methodPermission)) {
return
}
const {
auth,
role,
permission
} = methodPermission[methodName]
if (auth || role || permission) {
await this.middleware.auth()
}
if (role && role.length === 0) {
throw new Error('[AccessControl]Empty role array is not supported')
}
if (permission && permission.length === 0) {
throw new Error('[AccessControl]Empty permission array is not supported')
}
return isAccessAllowed(this.authInfo, {
role,
permission
})
}
module.exports = async function () {
if (this.authInfo) { // 多次执行auth时如果第一次成功后续不再执行
return
}
const token = this.getUniversalUniIdToken()
const payload = await this.uniIdCommon.checkToken(token)
if (payload.errCode) {
throw payload
}
this.authInfo = payload
if (payload.token) {
this.response.newToken = {
token: payload.token,
tokenExpired: payload.tokenExpired
}
}
}
module.exports = {
auth: require('./auth'),
uniIdLog: require('./uni-id-log'),
validate: require('./validate'),
accessControl: require('./access-control'),
verifyRequestSign: require('./verify-request-sign'),
...require('./rbac')
}
const {
ERROR
} = require('../common/error')
function hasRole (...roleList) {
const userRole = this.authInfo.role || []
if (userRole.includes('admin')) {
return
}
const isMatch = roleList.every(roleItem => {
return userRole.includes(roleItem)
})
if (!isMatch) {
throw {
errCode: ERROR.PERMISSION_ERROR
}
}
}
function hasPermission (...permissionList) {
const userRole = this.authInfo.role || []
const userPermission = this.authInfo.permission || []
if (userRole.includes('admin')) {
return
}
const isMatch = permissionList.every(permissionItem => {
return userPermission.includes(permissionItem)
})
if (!isMatch) {
throw {
errCode: ERROR.PERMISSION_ERROR
}
}
}
module.exports = {
hasRole,
hasPermission
}
const db = uniCloud.database()
module.exports = async function ({
data = {},
success = true,
type = 'login'
} = {}) {
const now = Date.now()
const uniIdLogCollection = db.collection('uni-id-log')
const requiredDataKeyList = ['user_id', 'username', 'email', 'mobile']
const dataCopy = {}
for (let i = 0; i < requiredDataKeyList.length; i++) {
const key = requiredDataKeyList[i]
if (key in data && typeof data[key] === 'string') {
dataCopy[key] = data[key]
}
}
const {
appId,
clientIP,
deviceId,
userAgent
} = this.getUniversalClientInfo()
const logData = {
appid: appId,
device_id: deviceId,
ip: clientIP,
type,
ua: userAgent,
create_date: now,
...dataCopy
}
if (success) {
logData.state = 1
} else {
logData.state = 0
}
return uniIdLogCollection.add(logData)
}
module.exports = function (value = {}, schema = {}) {
const validateRes = this.validator.validate(value, schema)
if (validateRes) {
delete validateRes.schemaKey
throw validateRes
}
}
const crypto = require('crypto')
const createConfig = require('uni-config-center')
const { verifyHttpInfo } = require('uni-cloud-s2s')
const { ERROR } = require('../common/error')
const s2sConfig = createConfig({
pluginId: 'uni-cloud-s2s'
})
const needSignFunctions = new Set([
'externalRegister',
'externalLogin',
'updateUserInfoByExternal'
])
module.exports = function () {
const methodName = this.getMethodName()
const { source } = this.getUniversalClientInfo()
// 指定接口需要鉴权
if (!needSignFunctions.has(methodName)) return
// 非 HTTP 方式请求拒绝访问
if (source !== 'http') {
throw {
errCode: ERROR.ILLEGAL_REQUEST
}
}
// 支持 uni-cloud-s2s 验证请求
if (s2sConfig.hasFile('config.json')) {
try {
if (!verifyHttpInfo(this.getHttpInfo())) {
throw {
errCode: ERROR.ILLEGAL_REQUEST
}
}
} catch (e) {
if (e.errSubject === 'uni-cloud-s2s') {
throw {
errCode: ERROR.ILLEGAL_REQUEST,
errMsg: e.errMsg
}
}
throw e
}
return
}
if (!this.config.requestAuthSecret || typeof this.config.requestAuthSecret !== 'string') {
throw {
errCode: ERROR.CONFIG_FIELD_REQUIRED,
errMsgValue: {
field: 'requestAuthSecret'
}
}
}
const timeout = 20 * 1000 // 请求超过20秒不能再请求,防止重放攻击
const { headers, body: _body } = this.getHttpInfo()
const { 'uni-id-nonce': nonce, 'uni-id-timestamp': timestamp, 'uni-id-signature': signature } = headers
const body = JSON.parse(_body).params || {}
const bodyStr = Object.keys(body)
.sort()
.filter(item => typeof body[item] !== 'object')
.map(item => `${item}=${body[item]}`)
.join('&')
if (isNaN(Number(timestamp)) || (Number(timestamp) + timeout) < Date.now()) {
throw {
errCode: ERROR.ILLEGAL_REQUEST
}
}
const reSignature = crypto.createHmac('sha256', `${this.config.requestAuthSecret + nonce}`).update(`${timestamp}${bodyStr}`).digest('hex')
if (signature !== reSignature.toUpperCase()) {
throw {
errCode: ERROR.ILLEGAL_REQUEST
}
}
}
const {
setUserStatus
} = require('../../lib/utils/update-user-info')
const {
USER_STATUS
} = require('../../common/constants')
/**
* 注销账户
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#close-account
* @returns
*/
module.exports = async function () {
const { uid } = this.authInfo
return setUserStatus(uid, USER_STATUS.CLOSED)
}
const {
userCollection
} = require('../../common/constants')
const {
ERROR
} = require('../../common/error')
function isUsernameSet (userRecord) {
return !!userRecord.username
}
function isNicknameSet (userRecord) {
return !!userRecord.nickname
}
function isPasswordSet (userRecord) {
return !!userRecord.password
}
function isMobileBound (userRecord) {
return !!(userRecord.mobile && userRecord.mobile_confirmed)
}
function isEmailBound (userRecord) {
return !!(userRecord.email && userRecord.email_confirmed)
}
function isWeixinBound (userRecord) {
return !!(
userRecord.wx_unionid ||
Object.keys(userRecord.wx_openid || {}).length
)
}
function isQQBound (userRecord) {
return !!(
userRecord.qq_unionid ||
Object.keys(userRecord.qq_openid || {}).length
)
}
function isAlipayBound (userRecord) {
return !!userRecord.ali_openid
}
function isAppleBound (userRecord) {
return !!userRecord.apple_openid
}
/**
* 获取账户账户简略信息
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#get-account-info
*/
module.exports = async function () {
const {
uid
} = this.authInfo
const getUserRes = await userCollection.doc(uid).get()
const userRecord = getUserRes && getUserRes.data && getUserRes.data[0]
if (!userRecord) {
throw {
errCode: ERROR.ACCOUNT_NOT_EXISTS
}
}
return {
errCode: 0,
isUsernameSet: isUsernameSet(userRecord),
isNicknameSet: isNicknameSet(userRecord),
isPasswordSet: isPasswordSet(userRecord),
isMobileBound: isMobileBound(userRecord),
isEmailBound: isEmailBound(userRecord),
isWeixinBound: isWeixinBound(userRecord),
isQQBound: isQQBound(userRecord),
isAlipayBound: isAlipayBound(userRecord),
isAppleBound: isAppleBound(userRecord)
}
}
const { userCollection } = require('../../common/constants')
const { ERROR } = require('../../common/error')
const { decryptData } = require('../../common/sensitive-aes-cipher')
const { dataDesensitization } = require('../../common/utils')
/**
* 获取实名信息
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#get-realname-info
* @param {Object} params
* @param {Boolean} params.decryptData 是否解密数据
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
decryptData: {
required: false,
type: 'boolean'
}
}
this.middleware.validate(params, schema)
const { decryptData: isDecryptData = true } = params
const {
uid
} = this.authInfo
const getUserRes = await userCollection.doc(uid).get()
const userRecord = getUserRes && getUserRes.data && getUserRes.data[0]
if (!userRecord) {
throw {
errCode: ERROR.ACCOUNT_NOT_EXISTS
}
}
const { realname_auth: realNameAuth = {} } = userRecord
return {
errCode: 0,
type: realNameAuth.type,
authStatus: realNameAuth.auth_status,
realName: isDecryptData ? dataDesensitization(decryptData.call(this, realNameAuth.real_name), { onlyLast: true }) : realNameAuth.real_name,
identity: isDecryptData ? dataDesensitization(decryptData.call(this, realNameAuth.identity)) : realNameAuth.identity
}
}
module.exports = {
setPwd: require('./set-pwd'),
updatePwd: require('./update-pwd'),
resetPwdBySms: require('./reset-pwd-by-sms'),
resetPwdByEmail: require('./reset-pwd-by-email'),
closeAccount: require('./close-account'),
getAccountInfo: require('./get-account-info'),
getRealNameInfo: require('./get-realname-info')
}
const {
ERROR
} = require('../../common/error')
const {
getNeedCaptcha,
verifyCaptcha
} = require('../../lib/utils/captcha')
const {
verifyEmailCode
} = require('../../lib/utils/verify-code')
const {
userCollection,
EMAIL_SCENE,
CAPTCHA_SCENE,
LOG_TYPE
} = require('../../common/constants')
const {
findUser
} = require('../../lib/utils/account')
const PasswordUtils = require('../../lib/utils/password')
/**
* 通过邮箱验证码重置密码
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#reset-pwd-by-email
* @param {object} params
* @param {string} params.email 邮箱
* @param {string} params.code 邮箱验证码
* @param {string} params.password 密码
* @param {string} params.captcha 图形验证码
* @returns {object}
*/
module.exports = async function (params = {}) {
const schema = {
email: 'email',
code: 'string',
password: 'password',
captcha: {
required: false,
type: 'string'
}
}
this.middleware.validate(params, schema)
const {
email,
code,
password,
captcha
} = params
const needCaptcha = await getNeedCaptcha.call(this, {
email,
type: LOG_TYPE.RESET_PWD_BY_EMAIL
})
if (needCaptcha) {
await verifyCaptcha.call(this, {
captcha,
scene: CAPTCHA_SCENE.RESET_PWD_BY_EMAIL
})
}
try {
// 验证手机号验证码,验证不通过时写入失败日志
await verifyEmailCode({
email,
code,
scene: EMAIL_SCENE.RESET_PWD_BY_EMAIL
})
} catch (error) {
await this.middleware.uniIdLog({
data: {
email
},
type: LOG_TYPE.RESET_PWD_BY_EMAIL,
success: false
})
throw error
}
// 根据手机号查找匹配的用户
const {
total,
userMatched
} = await findUser.call(this, {
userQuery: {
email
},
authorizedApp: [this.getUniversalClientInfo().appId]
})
if (userMatched.length === 0) {
if (total > 0) {
throw {
errCode: ERROR.ACCOUNT_NOT_EXISTS_IN_CURRENT_APP
}
}
throw {
errCode: ERROR.ACCOUNT_NOT_EXISTS
}
} else if (userMatched.length > 1) {
throw {
errCode: ERROR.ACCOUNT_CONFLICT
}
}
const { _id: uid } = userMatched[0]
const {
passwordHash,
version
} = new PasswordUtils({
clientInfo: this.getUniversalClientInfo(),
passwordSecret: this.config.passwordSecret
}).generatePasswordHash({
password
})
// 更新用户密码
await userCollection.doc(uid).update({
password: passwordHash,
password_secret_version: version,
valid_token_date: Date.now()
})
// 写入成功日志
await this.middleware.uniIdLog({
data: {
email
},
type: LOG_TYPE.RESET_PWD_BY_SMS
})
return {
errCode: 0
}
}
const {
ERROR
} = require('../../common/error')
const {
getNeedCaptcha,
verifyCaptcha
} = require('../../lib/utils/captcha')
const {
verifyMobileCode
} = require('../../lib/utils/verify-code')
const {
userCollection,
SMS_SCENE,
CAPTCHA_SCENE,
LOG_TYPE
} = require('../../common/constants')
const {
findUser
} = require('../../lib/utils/account')
const PasswordUtils = require('../../lib/utils/password')
/**
* 通过短信验证码重置密码
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#reset-pwd-by-sms
* @param {object} params
* @param {string} params.mobile 手机号
* @param {string} params.mobile 短信验证码
* @param {string} params.password 密码
* @param {string} params.captcha 图形验证码
* @returns {object}
*/
module.exports = async function (params = {}) {
const schema = {
mobile: 'mobile',
code: 'string',
password: 'password',
captcha: {
required: false,
type: 'string'
}
}
this.middleware.validate(params, schema)
const {
mobile,
code,
password,
captcha
} = params
const needCaptcha = await getNeedCaptcha.call(this, {
mobile,
type: LOG_TYPE.RESET_PWD_BY_SMS
})
if (needCaptcha) {
await verifyCaptcha.call(this, {
captcha,
scene: CAPTCHA_SCENE.RESET_PWD_BY_SMS
})
}
try {
// 验证手机号验证码,验证不通过时写入失败日志
await verifyMobileCode({
mobile,
code,
scene: SMS_SCENE.RESET_PWD_BY_SMS
})
} catch (error) {
await this.middleware.uniIdLog({
data: {
mobile
},
type: LOG_TYPE.RESET_PWD_BY_SMS,
success: false
})
throw error
}
// 根据手机号查找匹配的用户
const {
total,
userMatched
} = await findUser.call(this, {
userQuery: {
mobile
},
authorizedApp: [this.getUniversalClientInfo().appId]
})
if (userMatched.length === 0) {
if (total > 0) {
throw {
errCode: ERROR.ACCOUNT_NOT_EXISTS_IN_CURRENT_APP
}
}
throw {
errCode: ERROR.ACCOUNT_NOT_EXISTS
}
} else if (userMatched.length > 1) {
throw {
errCode: ERROR.ACCOUNT_CONFLICT
}
}
const { _id: uid } = userMatched[0]
const {
passwordHash,
version
} = new PasswordUtils({
clientInfo: this.getUniversalClientInfo(),
passwordSecret: this.config.passwordSecret
}).generatePasswordHash({
password
})
// 更新用户密码
await userCollection.doc(uid).update({
password: passwordHash,
password_secret_version: version,
valid_token_date: Date.now()
})
// 写入成功日志
await this.middleware.uniIdLog({
data: {
mobile
},
type: LOG_TYPE.RESET_PWD_BY_SMS
})
return {
errCode: 0
}
}
const { userCollection, SMS_SCENE, LOG_TYPE, CAPTCHA_SCENE } = require('../../common/constants')
const { ERROR } = require('../../common/error')
const { verifyMobileCode } = require('../../lib/utils/verify-code')
const PasswordUtils = require('../../lib/utils/password')
const { getNeedCaptcha, verifyCaptcha } = require('../../lib/utils/captcha')
module.exports = async function (params = {}) {
const schema = {
password: 'password',
code: 'string',
captcha: {
required: false,
type: 'string'
}
}
this.middleware.validate(params, schema)
const { password, code, captcha } = params
const uid = this.authInfo.uid
const getUserRes = await userCollection.doc(uid).get()
const userRecord = getUserRes.data[0]
if (!userRecord) {
throw {
errCode: ERROR.ACCOUNT_NOT_EXISTS
}
}
const needCaptcha = await getNeedCaptcha.call(this, {
mobile: userRecord.mobile
})
if (needCaptcha) {
await verifyCaptcha.call(this, {
captcha,
scene: CAPTCHA_SCENE.SET_PWD_BY_SMS
})
}
try {
// 验证手机号验证码,验证不通过时写入失败日志
await verifyMobileCode({
mobile: userRecord.mobile,
code,
scene: SMS_SCENE.SET_PWD_BY_SMS
})
} catch (error) {
await this.middleware.uniIdLog({
data: {
mobile: userRecord.mobile
},
type: LOG_TYPE.SET_PWD_BY_SMS,
success: false
})
throw error
}
const {
passwordHash,
version
} = new PasswordUtils({
clientInfo: this.getUniversalClientInfo(),
passwordSecret: this.config.passwordSecret
}).generatePasswordHash({
password
})
// 更新用户密码
await userCollection.doc(uid).update({
password: passwordHash,
password_secret_version: version
})
await this.middleware.uniIdLog({
data: {
mobile: userRecord.mobile
},
type: LOG_TYPE.SET_PWD_BY_SMS
})
return {
errCode: 0
}
}
const {
userCollection
} = require('../../common/constants')
const {
ERROR
} = require('../../common/error')
const PasswordUtils = require('../../lib/utils/password')
/**
* 更新密码
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#update-pwd
* @param {object} params
* @param {string} params.oldPassword 旧密码
* @param {string} params.newPassword 新密码
* @returns {object}
*/
module.exports = async function (params = {}) {
const schema = {
oldPassword: 'string', // 防止密码规则调整导致旧密码无法更新
newPassword: 'password'
}
this.middleware.validate(params, schema)
const uid = this.authInfo.uid
const getUserRes = await userCollection.doc(uid).get()
const userRecord = getUserRes.data[0]
if (!userRecord) {
throw {
errCode: ERROR.ACCOUNT_NOT_EXISTS
}
}
const {
oldPassword,
newPassword
} = params
const passwordUtils = new PasswordUtils({
userRecord,
clientInfo: this.getUniversalClientInfo(),
passwordSecret: this.config.passwordSecret
})
const {
success: checkPasswordSuccess
} = passwordUtils.checkUserPassword({
password: oldPassword,
autoRefresh: false
})
if (!checkPasswordSuccess) {
throw {
errCode: ERROR.PASSWORD_ERROR
}
}
const {
passwordHash,
version
} = passwordUtils.generatePasswordHash({
password: newPassword
})
await userCollection.doc(uid).update({
password: passwordHash,
password_secret_version: version,
valid_token_date: Date.now() // refreshToken时会校验,如果创建token时间在此时间点之前,则拒绝下发新token,返回token失效错误码
})
// 执行更新密码操作后客户端应将用户退出重新登录
return {
errCode: 0
}
}
const {
findUser
} = require('../../lib/utils/account')
const {
ERROR
} = require('../../common/error')
const {
userCollection
} = require('../../common/constants')
const PasswordUtils = require('../../lib/utils/password')
/**
* 新增用户
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#add-user
* @param {Object} params
* @param {String} params.username 用户名
* @param {String} params.password 密码
* @param {String} params.nickname 昵称
* @param {Array} params.authorizedApp 允许登录的AppID列表
* @param {Array} params.role 用户角色列表
* @param {String} params.mobile 手机号
* @param {String} params.email 邮箱
* @param {Array} params.tags 用户标签
* @param {Number} params.status 用户状态
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
username: 'username',
password: 'password',
authorizedApp: {
required: false,
type: 'array<string>'
}, // 指定允许登录的app,传空数组或不传时表示可以不可以在任何端登录
nickname: {
required: false,
type: 'nickname'
},
role: {
require: false,
type: 'array<string>'
},
mobile: {
required: false,
type: 'mobile'
},
email: {
required: false,
type: 'email'
},
tags: {
required: false,
type: 'array<string>'
},
status: {
required: false,
type: 'number'
}
}
this.middleware.validate(params, schema)
const {
username,
password,
authorizedApp,
nickname,
role,
mobile,
email,
tags,
status
} = params
const {
userMatched
} = await findUser({
userQuery: {
username,
mobile,
email
},
authorizedApp
})
if (userMatched.length) {
throw {
errCode: ERROR.ACCOUNT_EXISTS
}
}
const passwordUtils = new PasswordUtils({
clientInfo: this.getUniversalClientInfo(),
passwordSecret: this.config.passwordSecret
})
const {
passwordHash,
version
} = passwordUtils.generatePasswordHash({
password
})
const data = {
username,
password: passwordHash,
password_secret_version: version,
dcloud_appid: authorizedApp || [],
nickname,
role: role || [],
mobile,
email,
tags: tags || [],
status
}
if (email) {
data.email_confirmed = 1
}
if (mobile) {
data.mobile_confirmed = 1
}
// 触发 beforeRegister 钩子
const beforeRegister = this.hooks.beforeRegister
let userRecord = data
if (beforeRegister) {
userRecord = await beforeRegister({
userRecord,
clientInfo: this.getUniversalClientInfo()
})
}
await userCollection.add(userRecord)
return {
errCode: 0,
errMsg: ''
}
}
module.exports = {
addUser: require('./add-user'),
updateUser: require('./update-user')
}
const {
findUser
} = require('../../lib/utils/account')
const {
ERROR
} = require('../../common/error')
const {
userCollection
} = require('../../common/constants')
const PasswordUtils = require('../../lib/utils/password')
/**
* 修改用户
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#update-user
* @param {Object} params
* @param {String} params.uid 要更新的用户id
* @param {String} params.username 用户名
* @param {String} params.password 密码
* @param {String} params.nickname 昵称
* @param {Array} params.authorizedApp 允许登录的AppID列表
* @param {Array} params.role 用户角色列表
* @param {String} params.mobile 手机号
* @param {String} params.email 邮箱
* @param {Array} params.tags 用户标签
* @param {Number} params.status 用户状态
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
uid: 'string',
username: 'username',
password: {
required: false,
type: 'password'
},
authorizedApp: {
required: false,
type: 'array<string>'
}, // 指定允许登录的app,传空数组或不传时表示可以不可以在任何端登录
nickname: {
required: false,
type: 'nickname'
},
role: {
require: false,
type: 'array<string>'
},
mobile: {
required: false,
type: 'mobile'
},
email: {
required: false,
type: 'email'
},
tags: {
required: false,
type: 'array<string>'
},
status: {
required: false,
type: 'number'
}
}
this.middleware.validate(params, schema)
const {
uid,
username,
password,
authorizedApp,
nickname,
role,
mobile,
email,
tags,
status
} = params
// 更新的用户数据字段
const data = {
username,
dcloud_appid: authorizedApp,
nickname,
role,
mobile,
email,
tags,
status
}
const realData = Object.keys(data).reduce((res, key) => {
const item = data[key]
if (item !== undefined) {
res[key] = item
}
return res
}, {})
// 更新用户名时验证用户名是否重新
if (username) {
const {
userMatched
} = await findUser({
userQuery: {
username
},
authorizedApp
})
if (userMatched.filter(user => user._id !== uid).length) {
throw {
errCode: ERROR.ACCOUNT_EXISTS
}
}
}
if (password) {
const passwordUtils = new PasswordUtils({
clientInfo: this.getUniversalClientInfo(),
passwordSecret: this.config.passwordSecret
})
const {
passwordHash,
version
} = passwordUtils.generatePasswordHash({
password
})
realData.password = passwordHash
realData.password_secret_version = version
}
await userCollection.doc(uid).update(realData)
return {
errCode: 0
}
}
function isMobileCodeSupported () {
const config = this.config
return !!(config.service && config.service.sms && config.service.sms.smsKey)
}
function isUniverifySupport () {
const config = this.config
return !!(config.service && config.service.univerify && config.service.univerify.apiKey)
}
function isWeixinSupported () {
this.configUtils.getOauthConfig({
provider: 'weixin'
})
return true
}
function isQQSupported () {
this.configUtils.getOauthConfig({
provider: 'qq'
})
return true
}
function isAppleSupported () {
this.configUtils.getOauthConfig({
provider: 'apple'
})
return true
}
function isAlipaySupported () {
this.configUtils.getOauthConfig({
provider: 'alipay'
})
return true
}
const loginTypeTester = {
'mobile-code': isMobileCodeSupported,
univerify: isUniverifySupport,
weixin: isWeixinSupported,
qq: isQQSupported,
apple: isAppleSupported,
alipay: isAlipaySupported
}
/**
* 获取支持的登录方式
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#get-supported-login-type
* @returns
*/
module.exports = async function () {
const supportedLoginType = [
'username-password',
'mobile-password',
'email-password'
]
for (const type in loginTypeTester) {
try {
if (loginTypeTester[type].call(this)) {
supportedLoginType.push(type)
}
} catch (error) { }
}
return {
errCode: 0,
errMsg: '',
supportedLoginType
}
}
module.exports = {
getSupportedLoginType: require('./get-supported-login-type')
}
module.exports = {
externalRegister: require('./register'),
externalLogin: require('./login'),
updateUserInfoByExternal: require('./update-user-info')
}
const { preLogin, postLogin } = require('../../lib/utils/login')
const { EXTERNAL_DIRECT_CONNECT_PROVIDER } = require('../../common/constants')
const { ERROR } = require('../../common/error')
/**
* 外部用户登录
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#external-login
* @param {object} params
* @param {string} params.uid uni-id体系用户id
* @param {string} params.externalUid 业务系统的用户id
* @returns {object}
*/
module.exports = async function (params = {}) {
const schema = {
uid: {
required: false,
type: 'string'
},
externalUid: {
required: false,
type: 'string'
}
}
this.middleware.validate(params, schema)
const {
uid,
externalUid
} = params
if (!uid && !externalUid) {
throw {
errCode: ERROR.PARAM_REQUIRED,
errMsgValue: {
param: 'uid or externalUid'
}
}
}
let query
if (uid) {
query = {
_id: uid
}
} else {
query = {
identities: {
provider: EXTERNAL_DIRECT_CONNECT_PROVIDER,
uid: externalUid
}
}
}
const user = await preLogin.call(this, {
user: query
})
const result = await postLogin.call(this, {
user
})
return {
errCode: result.errCode,
newToken: result.newToken,
uid: result.uid
}
}
const url = require('url')
const { preRegister, postRegister } = require('../../lib/utils/register')
const { EXTERNAL_DIRECT_CONNECT_PROVIDER } = require('../../common/constants')
/**
* 外部注册用户
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#external-register
* @param {object} params
* @param {string} params.externalUid 业务系统的用户id
* @param {string} params.nickname 昵称
* @param {number} params.gender 性别
* @param {string} params.avatar 头像
* @returns {object}
*/
module.exports = async function (params = {}) {
const schema = {
externalUid: 'string',
nickname: {
required: false,
type: 'nickname'
},
gender: {
required: false,
type: 'number'
},
avatar: {
required: false,
type: 'string'
}
}
this.middleware.validate(params, schema)
const {
externalUid,
avatar,
gender,
nickname
} = params
await preRegister.call(this, {
user: {
identities: {
provider: EXTERNAL_DIRECT_CONNECT_PROVIDER,
uid: externalUid
}
}
})
const extraData = {}
if (avatar) {
// eslint-disable-next-line n/no-deprecated-api
const avatarPath = url.parse(avatar).pathname
const extName = avatarPath.indexOf('.') > -1 ? avatarPath.split('.').pop() : ''
extraData.avatar_file = {
name: avatarPath,
extname: extName,
url: avatar
}
}
const result = await postRegister.call(this, {
user: {
avatar,
gender,
nickname,
identities: [
{
provider: EXTERNAL_DIRECT_CONNECT_PROVIDER,
userInfo: {
avatar,
gender,
nickname
},
uid: externalUid
}
]
},
extraData
})
return {
errCode: result.errCode,
newToken: result.newToken,
externalUid,
avatar,
gender,
nickname,
uid: result.uid
}
}
const url = require('url')
const { userCollection, EXTERNAL_DIRECT_CONNECT_PROVIDER } = require('../../common/constants')
const { ERROR } = require('../../common/error')
const { findUser } = require('../../lib/utils/account')
const PasswordUtils = require('../../lib/utils/password')
/**
* 使用 uid 或 externalUid 获取用户信息
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#external-update-userinfo
* @param {object} params
* @param {string} params.uid uni-id体系的用户id
* @param {string} params.externalUid 业务系统的用户id
* @param {string} params.nickname 昵称
* @param {string} params.gender 性别
* @param {string} params.avatar 头像
* @returns {object}
*/
module.exports = async function (params = {}) {
const schema = {
uid: {
required: false,
type: 'string'
},
externalUid: {
required: false,
type: 'string'
},
username: {
required: false,
type: 'string'
},
password: {
required: false,
type: 'password'
},
authorizedApp: {
required: false,
type: 'array<string>'
}, // 指定允许登录的app,传空数组或不传时表示可以不可以在任何端登录
nickname: {
required: false,
type: 'nickname'
},
role: {
require: false,
type: 'array<string>'
},
mobile: {
required: false,
type: 'mobile'
},
email: {
required: false,
type: 'email'
},
tags: {
required: false,
type: 'array<string>'
},
status: {
required: false,
type: 'number'
},
gender: {
required: false,
type: 'number'
},
avatar: {
required: false,
type: 'string'
}
}
this.middleware.validate(params, schema)
const {
uid,
externalUid,
username,
password,
authorizedApp,
nickname,
role,
mobile,
email,
tags,
status,
avatar,
gender
} = params
if (!uid && !externalUid) {
throw {
errCode: ERROR.PARAM_REQUIRED,
errMsgValue: {
param: 'uid or externalUid'
}
}
}
let query
if (uid) {
query = {
_id: uid
}
} else {
query = {
identities: {
provider: EXTERNAL_DIRECT_CONNECT_PROVIDER,
uid: externalUid
}
}
}
const users = await userCollection.where(query).get()
const user = users.data && users.data[0]
if (!user) {
throw {
errCode: ERROR.ACCOUNT_NOT_EXISTS
}
}
// 更新的用户数据字段
const data = {
username,
dcloud_appid: authorizedApp,
nickname,
role,
mobile,
email,
tags,
status,
avatar,
gender
}
const realData = Object.keys(data).reduce((res, key) => {
const item = data[key]
if (item !== undefined) {
res[key] = item
}
return res
}, {})
// 更新用户名时验证用户名是否重新
if (username) {
const {
userMatched
} = await findUser({
userQuery: {
username
},
authorizedApp
})
if (userMatched.filter(user => user._id !== uid).length) {
throw {
errCode: ERROR.ACCOUNT_EXISTS
}
}
}
if (password) {
const passwordUtils = new PasswordUtils({
clientInfo: this.getUniversalClientInfo(),
passwordSecret: this.config.passwordSecret
})
const {
passwordHash,
version
} = passwordUtils.generatePasswordHash({
password
})
realData.password = passwordHash
realData.password_secret_version = version
}
if (avatar) {
// eslint-disable-next-line n/no-deprecated-api
const avatarPath = url.parse(avatar).pathname
const extName = avatarPath.indexOf('.') > -1 ? avatarPath.split('.').pop() : ''
realData.avatar_file = {
name: avatarPath,
extname: extName,
url: avatar
}
}
if (user.identities.length) {
const identity = user.identities.find(item => item.provider === EXTERNAL_DIRECT_CONNECT_PROVIDER)
if (identity) {
identity.userInfo = {
avatar,
gender,
nickname
}
}
realData.identities = user.identities
}
await userCollection.where(query).update(realData)
return {
errCode: 0
}
}
const { userCollection, REAL_NAME_STATUS, frvLogsCollection } = require('../../common/constants')
const { dataDesensitization, catchAwait } = require('../../common/utils')
const { encryptData, decryptData } = require('../../common/sensitive-aes-cipher')
const { ERROR } = require('../../common/error')
/**
* 查询认证结果
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#get-frv-auth-result
* @param {Object} params
* @param {String} params.certifyId 认证ID
* @returns
*/
module.exports = async function (params) {
const schema = {
certifyId: 'string'
}
this.middleware.validate(params, schema)
const { uid } = this.authInfo // 从authInfo中取出uid属性
const { certifyId } = params // 从params中取出certifyId属性
const user = await userCollection.doc(uid).get() // 根据uid查询用户信息
const userInfo = user.data && user.data[0] // 从查询结果中获取userInfo对象
// 如果用户不存在,抛出账户不存在的错误
if (!userInfo) {
throw {
errCode: ERROR.ACCOUNT_NOT_EXISTS
}
}
const { realname_auth: realNameAuth = {} } = userInfo
// 如果用户已经实名认证,抛出已实名认证的错误
if (realNameAuth.auth_status === REAL_NAME_STATUS.CERTIFIED) {
throw {
errCode: ERROR.REAL_NAME_VERIFIED
}
}
// 初始化实人认证服务
const frvManager = uniCloud.getFacialRecognitionVerifyManager({
requestId: this.getUniCloudRequestId()
})
// 调用frvManager的getAuthResult方法,获取认证结果
const [error, res] = await catchAwait(frvManager.getAuthResult({
certifyId
}))
// 如果出现错误,抛出未知错误并打印日志
if (error) {
console.log(ERROR.UNKNOWN_ERROR, 'error: ', error)
throw error
}
// 如果认证状态为“PROCESSING”,抛出认证正在处理中的错误
if (res.authState === 'PROCESSING') {
throw {
errCode: ERROR.FRV_PROCESSING
}
}
// 如果认证状态为“FAIL”,更新认证日志的状态并抛出认证失败的错误
if (res.authState === 'FAIL') {
await frvLogsCollection.where({
certify_id: certifyId
}).update({
status: REAL_NAME_STATUS.CERTIFY_FAILED
})
console.log(ERROR.FRV_FAIL, 'error: ', res)
throw {
errCode: ERROR.FRV_FAIL
}
}
// 如果认证状态不为“SUCCESS”,抛出未知错误并打印日志
if (res.authState !== 'SUCCESS') {
console.log(ERROR.UNKNOWN_ERROR, 'source res: ', res)
throw {
errCode: ERROR.UNKNOWN_ERROR
}
}
// 根据certifyId查询认证记录
const frvLogs = await frvLogsCollection.where({
certify_id: certifyId
}).get()
const log = frvLogs.data && frvLogs.data[0]
const updateData = {
realname_auth: {
auth_status: REAL_NAME_STATUS.CERTIFIED,
real_name: log.real_name,
identity: log.identity,
auth_date: Date.now(),
type: 0
}
}
// 如果获取到了认证照片的地址,则会对其进行下载,并使用uniCloud.uploadFile方法将其上传到云存储,并将上传后的fileID保存起来。
if (res.pictureUrl) {
const pictureRes = await uniCloud.httpclient.request(res.pictureUrl)
if (pictureRes.status < 400) {
const {
fileID
} = await uniCloud.uploadFile({
cloudPath: `user/id-card/${uid}.b64`,
fileContent: Buffer.from(encryptData.call(this, pictureRes.data.toString('base64')))
})
updateData.realname_auth.in_hand = fileID
}
}
await Promise.all([
// 更新用户认证状态
userCollection.doc(uid).update(updateData),
// 更新实人认证记录状态
frvLogsCollection.where({
certify_id: certifyId
}).update({
status: REAL_NAME_STATUS.CERTIFIED
})
])
return {
errCode: 0,
authStatus: REAL_NAME_STATUS.CERTIFIED,
realName: dataDesensitization(decryptData.call(this, log.real_name), { onlyLast: true }), // 对姓名进行脱敏处理
identity: dataDesensitization(decryptData.call(this, log.identity)) // 对身份证号进行脱敏处理
}
}
const { userCollection, REAL_NAME_STATUS, frvLogsCollection, dbCmd } = require('../../common/constants')
const { ERROR } = require('../../common/error')
const { encryptData } = require('../../common/sensitive-aes-cipher')
const { getCurrentDateTimestamp } = require('../../common/utils')
// const CertifyIdExpired = 25 * 60 * 1000 // certifyId 过期时间为30分钟,在25分时置为过期
/**
* 获取认证ID
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#get-frv-certify-id
* @param {Object} params
* @param {String} params.realName 真实姓名
* @param {String} params.idCard 身份证号码
* @param {String} params.metaInfo 客户端初始化时返回的metaInfo
* @returns
*/
module.exports = async function (params) {
const schema = {
realName: 'realName',
idCard: 'idCard',
metaInfo: 'string'
}
this.middleware.validate(params, schema)
const { realName: originalRealName, idCard: originalIdCard, metaInfo } = params // 解构出传入参数的真实姓名、身份证号码、其他元数据
const realName = encryptData.call(this, originalRealName) // 对真实姓名进行加密处理
const idCard = encryptData.call(this, originalIdCard) // 对身份证号码进行加密处理
const { uid } = this.authInfo // 获取当前用户的 ID
const idCardCertifyLimit = this.config.idCardCertifyLimit || 1 // 获取身份证认证限制次数,默认为1次
const realNameCertifyLimit = this.config.realNameCertifyLimit || 5 // 获取实名认证限制次数,默认为5次
const frvNeedAlivePhoto = this.config.frvNeedAlivePhoto || false // 是否需要拍摄活体照片,默认为 false
const user = await userCollection.doc(uid).get() // 获取用户信息
const userInfo = user.data && user.data[0] // 获取用户信息对象中的实名认证信息
const { realname_auth: realNameAuth = {} } = userInfo // 解构出实名认证信息中的认证状态对象,默认为空对象
// 如果用户已经实名认证过,不能再次认证
if (realNameAuth.auth_status === REAL_NAME_STATUS.CERTIFIED) {
throw {
errCode: ERROR.REAL_NAME_VERIFIED
}
}
// 查询已经使用同一个身份证认证的账号数量,如果超过限制则不能认证
const idCardAccount = await userCollection.where({
realname_auth: {
type: 0, // 用户认证状态是个人
auth_status: REAL_NAME_STATUS.CERTIFIED, // 认证状态为已认证
identity: idCard // 身份证号码和传入参数的身份证号码相同
}
}).get()
if (idCardAccount.data.length >= idCardCertifyLimit) {
throw {
errCode: ERROR.ID_CARD_EXISTS
}
}
// 查询用户今天已经进行的实名认证次数,如果超过限制则不能认证
const userFrvLogs = await frvLogsCollection.where({
user_id: uid,
created_date: dbCmd.gt(getCurrentDateTimestamp()) // 查询今天的认证记录
}).get()
// 限制用户每日认证次数
if (userFrvLogs.data && userFrvLogs.data.length >= realNameCertifyLimit) {
throw {
errCode: ERROR.REAL_NAME_VERIFY_UPPER_LIMIT
}
}
// 初始化实人认证服务
const frvManager = uniCloud.getFacialRecognitionVerifyManager({
requestId: this.getUniCloudRequestId() // 获取当前
})
// 调用实人认证服务,获取认证 ID
const res = await frvManager.getCertifyId({
realName: originalRealName,
idCard: originalIdCard,
needPicture: frvNeedAlivePhoto,
metaInfo
})
// 将认证记录插入到实名认证日志中
await frvLogsCollection.add({
user_id: uid,
certify_id: res.certifyId,
real_name: realName,
identity: idCard,
status: REAL_NAME_STATUS.WAITING_CERTIFIED,
created_date: Date.now()
})
// 返回认证ID
return {
certifyId: res.certifyId
}
}
module.exports = {
getFrvCertifyId: require('./get-certify-id'),
getFrvAuthResult: require('./get-auth-result')
}
const {
acceptInvite
} = require('../../lib/utils/fission')
/**
* 接受邀请
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#accept-invite
* @param {Object} params
* @param {String} params.inviteCode 邀请码
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
inviteCode: 'string'
}
this.middleware.validate(params, schema)
const {
inviteCode
} = params
const uid = this.authInfo.uid
return acceptInvite({
uid,
inviteCode
})
}
const {
userCollection
} = require('../../common/constants')
const {
coverMobile
} = require('../../common/utils')
/**
* 获取受邀用户
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#get-invited-user
* @param {Object} params
* @param {Number} params.level 获取受邀用户的级数,1表示直接邀请的用户
* @param {Number} params.limit 返回数据大小
* @param {Number} params.offset 返回数据偏移
* @param {Boolean} params.needTotal 是否需要返回总数
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
level: 'number',
limit: {
required: false,
type: 'number'
},
offset: {
required: false,
type: 'number'
},
needTotal: {
required: false,
type: 'boolean'
}
}
this.middleware.validate(params, schema)
const {
level,
limit = 20,
offset = 0,
needTotal = false
} = params
const uid = this.authInfo.uid
const query = {
[`inviter_uid.${level - 1}`]: uid
}
const getUserRes = await userCollection.where(query)
.field({
_id: true,
avatar: true,
avatar_file: true,
username: true,
nickname: true,
mobile: true,
invite_time: true
})
.orderBy('invite_time', 'desc')
.skip(offset)
.limit(limit)
.get()
const invitedUser = getUserRes.data.map(item => {
return {
uid: item._id,
username: item.username,
nickname: item.nickname,
mobile: coverMobile(item.mobile),
inviteTime: item.invite_time,
avatar: item.avatar,
avatarFile: item.avatar_file
}
})
const result = {
errCode: 0,
invitedUser
}
if (needTotal) {
const getTotalRes = await userCollection.where(query).count()
result.total = getTotalRes.total
}
return result
}
module.exports = {
acceptInvite: require('./accept-invite'),
getInvitedUser: require('./get-invited-user')
}
module.exports = {
login: require('./login'),
loginBySms: require('./login-by-sms'),
loginByUniverify: require('./login-by-univerify'),
loginByWeixin: require('./login-by-weixin'),
loginByAlipay: require('./login-by-alipay'),
loginByQQ: require('./login-by-qq'),
loginByApple: require('./login-by-apple'),
loginByBaidu: require('./login-by-baidu'),
loginByDingtalk: require('./login-by-dingtalk'),
loginByToutiao: require('./login-by-toutiao'),
loginByDouyin: require('./login-by-douyin'),
loginByWeibo: require('./login-by-weibo'),
loginByTaobao: require('./login-by-taobao'),
loginByEmailLink: require('./login-by-email-link'),
loginByEmailCode: require('./login-by-email-code'),
loginByFacebook: require('./login-by-facebook'),
loginByGoogle: require('./login-by-google'),
loginByWeixinMobile: require('./login-by-weixin-mobile')
}
const {
initAlipay
} = require('../../lib/third-party/index')
const {
ERROR
} = require('../../common/error')
const {
preUnifiedLogin,
postUnifiedLogin
} = require('../../lib/utils/unified-login')
const {
LOG_TYPE
} = require('../../common/constants')
/**
* 支付宝登录
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#login-by-alipay
* @param {Object} params
* @param {String} params.code 支付宝小程序客户端登录返回的code
* @param {String} params.inviteCode 邀请码
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
code: 'string',
inviteCode: {
type: 'string',
required: false
}
}
this.middleware.validate(params, schema)
const {
code,
inviteCode
} = params
const alipayApi = initAlipay.call(this)
let getAlipayAccountResult
try {
getAlipayAccountResult = await alipayApi.code2Session(code)
} catch (error) {
console.error(error)
await this.middleware.uniIdLog({
success: false,
type: LOG_TYPE.LOGIN
})
throw {
errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED
}
}
const {
openid
} = getAlipayAccountResult
const {
type,
user
} = await preUnifiedLogin.call(this, {
user: {
ali_openid: openid
}
})
return postUnifiedLogin.call(this, {
user,
extraData: {},
isThirdParty: true,
type,
inviteCode
})
}
const {
initApple
} = require('../../lib/third-party/index')
const {
ERROR
} = require('../../common/error')
const {
preUnifiedLogin,
postUnifiedLogin
} = require('../../lib/utils/unified-login')
const {
LOG_TYPE
} = require('../../common/constants')
/**
* 苹果登录
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#login-by-apple
* @param {Object} params
* @param {String} params.identityToken 苹果登录返回的identityToken
* @param {String} params.nickname 用户昵称
* @param {String} params.inviteCode 邀请码
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
identityToken: 'string',
nickname: {
required: false,
type: 'nickname'
},
inviteCode: {
required: false,
type: 'string'
}
}
this.middleware.validate(params, schema)
const {
identityToken,
nickname,
inviteCode
} = params
const appleApi = initApple.call(this)
let verifyResult
try {
verifyResult = await appleApi.verifyIdentityToken(identityToken)
} catch (error) {
console.error(error)
await this.middleware.uniIdLog({
success: false,
type: LOG_TYPE.LOGIN
})
throw {
errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED
}
}
const {
openid
} = verifyResult
const {
type,
user
} = await preUnifiedLogin.call(this, {
user: {
apple_openid: openid
}
})
return postUnifiedLogin.call(this, {
user,
extraData: {
nickname
},
isThirdParty: true,
type,
inviteCode
})
}
/**
* 百度登录
* @param {Object} params
* @returns
*/
module.exports = async function (params = {}) {
// 此接口暂未实现,欢迎向我们提交pr
throw new Error('api[loginByBaidu] is not yet implemented')
}
/**
* 钉钉登录
* @param {Object} params
* @returns
*/
module.exports = async function (params = {}) {
// 此接口暂未实现,欢迎向我们提交pr
throw new Error('api[loginByDingtalk] is not yet implemented')
}
/**
* 抖音登录
* @param {Object} params
* @returns
*/
module.exports = async function (params = {}) {
// 此接口暂未实现,欢迎向我们提交pr
throw new Error('api[loginByDouyin] is not yet implemented')
}
/**
* 邮箱验证码登录
* @param {Object} params
* @returns
*/
module.exports = async function (params = {}) {
// 此接口暂未实现,欢迎向我们提交pr
throw new Error('api[loginByEmailCode] is not yet implemented')
}
/**
* 邮箱点击链接登录
* @param {Object} params
* @returns
*/
module.exports = async function (params = {}) {
// 此接口暂未实现,欢迎向我们提交pr
throw new Error('api[loginByEmailLink] is not yet implemented')
}
/**
* Facebook登录
* @param {Object} params
* @returns
*/
module.exports = async function (params = {}) {
// 此接口暂未实现,欢迎向我们提交pr
throw new Error('api[loginByFacebook] is not yet implemented')
}
/**
* Google登录
* @param {Object} params
* @returns
*/
module.exports = async function (params = {}) {
// 此接口暂未实现,欢迎向我们提交pr
throw new Error('api[loginByGoogle] is not yet implemented')
}
const {
initQQ
} = require('../../lib/third-party/index')
const {
ERROR
} = require('../../common/error')
const {
preUnifiedLogin,
postUnifiedLogin
} = require('../../lib/utils/unified-login')
const {
LOG_TYPE
} = require('../../common/constants')
const {
getQQPlatform,
generateQQCache,
saveQQUserKey
} = require('../../lib/utils/qq')
const url = require('url')
/**
* QQ登录
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#login-by-qq
* @param {Object} params
* @param {String} params.code QQ小程序登录返回的code参数
* @param {String} params.accessToken App端QQ登录返回的accessToken参数
* @param {String} params.accessTokenExpired accessToken过期时间,由App端QQ登录返回的expires_in参数计算而来,单位:毫秒
* @param {String} params.inviteCode 邀请码
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
code: {
type: 'string',
required: false
},
accessToken: {
type: 'string',
required: false
},
accessTokenExpired: {
type: 'number',
required: false
},
inviteCode: {
type: 'string',
required: false
}
}
this.middleware.validate(params, schema)
const {
code,
accessToken,
accessTokenExpired,
inviteCode
} = params
const {
appId
} = this.getUniversalClientInfo()
const qqApi = initQQ.call(this)
const qqPlatform = getQQPlatform.call(this)
let apiName
switch (qqPlatform) {
case 'mp':
apiName = 'code2Session'
break
case 'app':
apiName = 'getOpenidByToken'
break
default:
throw new Error('Unsupported qq platform')
}
let getQQAccountResult
try {
getQQAccountResult = await qqApi[apiName]({
code,
accessToken
})
} catch (error) {
console.error(error)
await this.middleware.uniIdLog({
success: false,
type: LOG_TYPE.LOGIN
})
throw {
errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED
}
}
const {
openid,
unionid,
// 保存下面的字段
sessionKey // QQ小程序用户sessionKey
} = getQQAccountResult
const {
type,
user
} = await preUnifiedLogin.call(this, {
user: {
qq_openid: {
[qqPlatform]: openid
},
qq_unionid: unionid
}
})
const extraData = {
qq_openid: {
[`${qqPlatform}_${appId}`]: openid
},
qq_unionid: unionid
}
if (type === 'register' && qqPlatform !== 'mp') {
const {
nickname,
avatar
} = await qqApi.getUserInfo({
accessToken,
openid
})
// eslint-disable-next-line n/no-deprecated-api
const extName = url.parse(avatar).pathname.split('.').pop()
const cloudPath = `user/avatar/${openid.slice(-8) + Date.now()}-avatar.${extName}`
const getAvatarRes = await uniCloud.httpclient.request(avatar)
if (getAvatarRes.status >= 400) {
throw {
errCode: ERROR.GET_THIRD_PARTY_USER_INFO_FAILED
}
}
const {
fileID
} = await uniCloud.uploadFile({
cloudPath,
fileContent: getAvatarRes.data
})
extraData.nickname = nickname
extraData.avatar_file = {
name: cloudPath,
extname: extName,
url: fileID
}
}
await saveQQUserKey.call(this, {
openid,
sessionKey,
accessToken,
accessTokenExpired
})
return postUnifiedLogin.call(this, {
user,
extraData: {
...extraData,
...generateQQCache.call(this, {
openid,
sessionKey, // QQ小程序用户sessionKey
accessToken, // App端QQ用户accessToken
accessTokenExpired // App端QQ用户accessToken过期时间
})
},
isThirdParty: true,
type,
inviteCode
})
}
const {
getNeedCaptcha,
verifyCaptcha
} = require('../../lib/utils/captcha')
const {
verifyMobileCode
} = require('../../lib/utils/verify-code')
const {
preUnifiedLogin,
postUnifiedLogin
} = require('../../lib/utils/unified-login')
const {
CAPTCHA_SCENE,
SMS_SCENE,
LOG_TYPE
} = require('../../common/constants')
/**
* 短信验证码登录
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#login-by-sms
* @param {Object} params
* @param {String} params.mobile 手机号
* @param {String} params.code 短信验证码
* @param {String} params.captcha 图形验证码
* @param {String} params.inviteCode 邀请码
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
mobile: 'mobile',
code: 'string',
captcha: {
required: false,
type: 'string'
},
inviteCode: {
required: false,
type: 'string'
}
}
this.middleware.validate(params, schema)
const {
mobile,
code,
captcha,
inviteCode
} = params
const needCaptcha = await getNeedCaptcha.call(this, {
mobile
})
if (needCaptcha) {
await verifyCaptcha.call(this, {
captcha,
scene: CAPTCHA_SCENE.LOGIN_BY_SMS
})
}
try {
await verifyMobileCode({
mobile,
code,
scene: SMS_SCENE.LOGIN_BY_SMS
})
} catch (error) {
console.log(error, {
mobile,
code,
type: SMS_SCENE.LOGIN_BY_SMS
})
await this.middleware.uniIdLog({
success: false,
data: {
mobile
},
type: LOG_TYPE.LOGIN
})
throw error
}
const {
type,
user
} = await preUnifiedLogin.call(this, {
user: {
mobile
}
})
return postUnifiedLogin.call(this, {
user,
extraData: {
mobile_confirmed: 1
},
isThirdParty: false,
type,
inviteCode
})
}
/**
* 淘宝登录
* @param {Object} params
* @returns
*/
module.exports = async function (params = {}) {
// 此接口暂未实现,欢迎向我们提交pr
throw new Error('api[loginByTaobao] is not yet implemented')
}
/**
* 头条登录
* @param {Object} params
* @returns
*/
module.exports = async function (params = {}) {
// 此接口暂未实现,欢迎向我们提交pr
throw new Error('api[loginByToutiao] is not yet implemented')
}
const {
getmobileNumber
} = require('../../lib/utils/univerify')
const {
preUnifiedLogin,
postUnifiedLogin
} = require('../../lib/utils/unified-login')
const {
LOG_TYPE
} = require('../../common/constants')
/**
* App端一键登录
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#login-by-univerify
* @param {Object} params
* @param {String} params.access_token APP端一键登录返回的access_token
* @param {String} params.openid APP端一键登录返回的openid
* @param {String} params.inviteCode 邀请码
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
access_token: 'string',
openid: 'string',
inviteCode: {
required: false,
type: 'string'
}
}
this.middleware.validate(params, schema)
const {
// eslint-disable-next-line camelcase
access_token,
openid,
inviteCode
} = params
let mobile
try {
const mobileInfo = await getmobileNumber.call(this, {
// eslint-disable-next-line camelcase
access_token,
openid
})
mobile = mobileInfo.mobileNumber
} catch (error) {
await this.middleware.uniIdLog({
success: false,
type: LOG_TYPE.LOGIN
})
throw error
}
const {
user,
type
} = await preUnifiedLogin.call(this, {
user: {
mobile
}
})
return postUnifiedLogin.call(this, {
user,
extraData: {
mobile_confirmed: 1
},
type,
inviteCode
})
}
/**
* 微博登录
* @param {Object} params
* @returns
*/
module.exports = async function (params = {}) {
// 此接口暂未实现,欢迎向我们提交pr
throw new Error('api[loginByWeibo] is not yet implemented')
}
const {
initWeixin
} = require('../../lib/third-party/index')
const {
getWeixinAccessToken
} = require('../../lib/utils/weixin')
const {
ERROR
} = require('../../common/error')
const {
preUnifiedLogin,
postUnifiedLogin
} = require('../../lib/utils/unified-login')
const {
LOG_TYPE
} = require('../../common/constants')
const {
preBind,
postBind
} = require('../../lib/utils/relate')
/**
* 微信授权手机号登录
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#login-by-weixin-mobile
* @param {Object} params
* @param {String} params.mobileCode 微信手机号返回的code
* @param {String} params.inviteCode 邀请码
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
mobileCode: 'string',
inviteCode: {
type: 'string',
required: false
}
}
this.middleware.validate(params, schema)
const { mobileCode, inviteCode } = params
const weixinApi = initWeixin.call(this)
let mobile
try {
const accessToken = await getWeixinAccessToken.call(this)
const mobileRes = await weixinApi.getmobileNumber(accessToken, mobileCode)
mobile = mobileRes.puremobileNumber
} catch (error) {
console.error(error)
await this.middleware.uniIdLog({
success: false,
type: LOG_TYPE.LOGIN
})
throw {
errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED
}
}
const { type, user } = await preUnifiedLogin.call(this, {
user: {
mobile
}
})
let extraData = {
mobile_confirmed: 1
}
if (type === 'login') {
// 绑定手机号
if (!user.mobile_confirmed) {
const bindAccount = {
mobile
}
await preBind.call(this, {
uid: user._id,
bindAccount,
logType: LOG_TYPE.BIND_MOBILE
})
await postBind.call(this, {
uid: user._id,
bindAccount,
extraData: {
mobile_confirmed: 1
},
logType: LOG_TYPE.BIND_MOBILE
})
extraData = {
...extraData,
...bindAccount
}
}
}
return postUnifiedLogin.call(this, {
user,
extraData: {
...extraData
},
isThirdParty: false,
type,
inviteCode
})
}
const {
initWeixin
} = require('../../lib/third-party/index')
const {
ERROR
} = require('../../common/error')
const {
preUnifiedLogin,
postUnifiedLogin
} = require('../../lib/utils/unified-login')
const {
generateWeixinCache,
getWeixinPlatform,
saveWeixinUserKey,
saveSecureNetworkCache
} = require('../../lib/utils/weixin')
const {
LOG_TYPE
} = require('../../common/constants')
const url = require('url')
/**
* 微信登录
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#login-by-weixin
* @param {Object} params
* @param {String} params.code 微信登录返回的code
* @param {String} params.inviteCode 邀请码
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
code: 'string',
inviteCode: {
type: 'string',
required: false
}
}
this.middleware.validate(params, schema)
const {
code,
inviteCode,
// 内部参数,暂不暴露
secureNetworkCache = false
} = params
const {
appId
} = this.getUniversalClientInfo()
const weixinApi = initWeixin.call(this)
const weixinPlatform = getWeixinPlatform.call(this)
let apiName
switch (weixinPlatform) {
case 'mp':
apiName = 'code2Session'
break
case 'app':
case 'h5':
case 'web':
apiName = 'getOauthAccessToken'
break
default:
throw new Error('Unsupported weixin platform')
}
let getWeixinAccountResult
try {
getWeixinAccountResult = await weixinApi[apiName](code)
} catch (error) {
console.error(error)
await this.middleware.uniIdLog({
success: false,
type: LOG_TYPE.LOGIN
})
throw {
errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED
}
}
const {
openid,
unionid,
// 保存下面四个字段
sessionKey, // 微信小程序用户sessionKey
accessToken, // App端微信用户accessToken
refreshToken, // App端微信用户refreshToken
expired: accessTokenExpired // App端微信用户accessToken过期时间
} = getWeixinAccountResult
if (secureNetworkCache) {
if (weixinPlatform !== 'mp') {
throw new Error('Unsupported weixin platform, expect mp-weixin')
}
await saveSecureNetworkCache.call(this, {
code,
openid,
unionid,
sessionKey
})
}
const {
type,
user
} = await preUnifiedLogin.call(this, {
user: {
wx_openid: {
[weixinPlatform]: openid
},
wx_unionid: unionid
}
})
const extraData = {
wx_openid: {
[`${weixinPlatform}_${appId}`]: openid
},
wx_unionid: unionid
}
if (type === 'register' && weixinPlatform !== 'mp') {
const {
nickname,
avatar
} = await weixinApi.getUserInfo({
accessToken,
openid
})
if (avatar) {
// eslint-disable-next-line n/no-deprecated-api
const avatarPath = url.parse(avatar).pathname
const extName = avatarPath.indexOf('.') > -1 ? url.parse(avatar).pathname.split('.').pop() : 'jpg'
const cloudPath = `user/avatar/${openid.slice(-8) + Date.now()}-avatar.${extName}`
const getAvatarRes = await uniCloud.httpclient.request(avatar)
if (getAvatarRes.status >= 400) {
throw {
errCode: ERROR.GET_THIRD_PARTY_USER_INFO_FAILED
}
}
const {
fileID
} = await uniCloud.uploadFile({
cloudPath,
fileContent: getAvatarRes.data
})
extraData.avatar_file = {
name: cloudPath,
extname: extName,
url: fileID
}
}
extraData.nickname = nickname
}
await saveWeixinUserKey.call(this, {
openid,
sessionKey,
accessToken,
refreshToken,
accessTokenExpired
})
return postUnifiedLogin.call(this, {
user,
extraData: {
...extraData,
...generateWeixinCache.call(this, {
openid,
sessionKey, // 微信小程序用户sessionKey
accessToken, // App端微信用户accessToken
refreshToken, // App端微信用户refreshToken
accessTokenExpired // App端微信用户accessToken过期时间
})
},
isThirdParty: true,
type,
inviteCode
})
}
const {
preLoginWithPassword,
postLogin
} = require('../../lib/utils/login')
const {
getNeedCaptcha,
verifyCaptcha
} = require('../../lib/utils/captcha')
const {
CAPTCHA_SCENE
} = require('../../common/constants')
const {
ERROR
} = require('../../common/error')
/**
* 用户名密码登录
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#login
* @param {Object} params
* @param {String} params.username 用户名
* @param {String} params.mobile 手机号
* @param {String} params.email 邮箱
* @param {String} params.password 密码
* @param {String} params.captcha 图形验证码
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
username: {
required: false,
type: 'username'
},
mobile: {
required: false,
type: 'mobile'
},
email: {
required: false,
type: 'email'
},
password: 'password',
captcha: {
required: false,
type: 'string'
}
}
this.middleware.validate(params, schema)
const {
username,
mobile,
email,
password,
captcha
} = params
if (!username && !mobile && !email) {
throw {
errCode: ERROR.INVALID_USERNAME
}
} else if (
(username && email) ||
(username && mobile) ||
(email && mobile)
) {
throw {
errCode: ERROR.INVALID_PARAM
}
}
const needCaptcha = await getNeedCaptcha.call(this, {
username,
mobile,
email
})
if (needCaptcha) {
await verifyCaptcha.call(this, {
captcha,
scene: CAPTCHA_SCENE.LOGIN_BY_PWD
})
}
const {
user,
extraData
} = await preLoginWithPassword.call(this, {
user: {
username,
mobile,
email
},
password
})
return postLogin.call(this, {
user,
extraData
})
}
const {
logout
} = require('../../lib/utils/logout')
/**
* 用户退出登录
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#logout
* @returns
*/
module.exports = async function () {
await logout.call(this)
return {
errCode: 0
}
}
const {
isAuthorizeApproved
} = require('./utils')
const {
dbCmd,
userCollection
} = require('../../common/constants')
/**
* 授权用户登录应用
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#authorize-app-login
* @param {Object} params
* @param {String} params.uid 用户id
* @param {String} params.appId 授权的应用的AppId
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
uid: 'string',
appId: 'string'
}
this.middleware.validate(params, schema)
const {
uid,
appId
} = params
await isAuthorizeApproved({
uid,
appIdList: [appId]
})
await userCollection.doc(uid).update({
dcloud_appid: dbCmd.push(appId)
})
return {
errCode: 0
}
}
module.exports = {
authorizeAppLogin: require('./authorize-app-login'),
removeAuthorizedApp: require('./remove-authorized-app'),
setAuthorizedApp: require('./set-authorized-app')
}
const {
dbCmd,
userCollection
} = require('../../common/constants')
/**
* 移除用户登录授权
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#remove-authorized-app
* @param {Object} params
* @param {String} params.uid 用户id
* @param {String} params.appId 取消授权的应用的AppId
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
uid: 'string',
appId: 'string'
}
this.middleware.validate(params, schema)
const {
uid,
appId
} = params
await userCollection.doc(uid).update({
dcloud_appid: dbCmd.pull(appId)
})
return {
errCode: 0
}
}
const {
isAuthorizeApproved
} = require('./utils')
const {
userCollection
} = require('../../common/constants')
/**
* 设置用户允许登录的应用列表
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#set-authorized-app
* @param {Object} params
* @param {String} params.uid 用户id
* @param {Array} params.appIdList 允许登录的应用AppId列表
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
uid: 'string',
appIdList: 'array<string>'
}
this.middleware.validate(params, schema)
const {
uid,
appIdList
} = params
await isAuthorizeApproved({
uid,
appIdList
})
await userCollection.doc(uid).update({
dcloud_appid: appIdList
})
return {
errCode: 0
}
}
const {
userCollection
} = require('../../common/constants')
const {
ERROR
} = require('../../common/error')
const {
findUser
} = require('../../lib/utils/account')
async function isAuthorizeApproved ({
uid,
appIdList
} = {}) {
const getUserRes = await userCollection.doc(uid).get()
const userRecord = getUserRes.data[0]
if (!userRecord) {
throw {
errCode: ERROR.ACCOUNT_NOT_EXISTS
}
}
const {
userMatched
} = await findUser({
userQuery: userRecord,
authorizedApp: appIdList
})
if (userMatched.some(item => item._id !== uid)) {
throw {
errCode: ERROR.ACCOUNT_CONFLICT
}
}
}
module.exports = {
isAuthorizeApproved
}
module.exports = {
registerUser: require('./register-user'),
registerAdmin: require('./register-admin'),
registerUserByEmail: require('./register-user-by-email')
}
const {
userCollection
} = require('../../common/constants')
const {
ERROR
} = require('../../common/error')
const {
preRegisterWithPassword,
postRegister
} = require('../../lib/utils/register')
/**
* 注册管理员
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#register-admin
* @param {Object} params
* @param {String} params.username 用户名
* @param {String} params.password 密码
* @param {String} params.nickname 昵称
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
username: 'username',
password: 'password',
nickname: {
type: 'nickname',
required: false
}
}
this.middleware.validate(params, schema)
const {
username,
password,
nickname
} = params
const getAdminRes = await userCollection.where({
role: 'admin'
}).limit(1).get()
if (getAdminRes.data.length > 0) {
const [admin] = getAdminRes.data
const appId = this.getUniversalClientInfo().appId
if (!admin.dcloud_appid || (admin.dcloud_appid && admin.dcloud_appid.includes(appId))) {
return {
errCode: ERROR.ADMIN_EXISTS,
errMsg: this.t('uni-id-admin-exists')
}
} else {
return {
errCode: ERROR.ADMIN_EXISTS,
errMsg: this.t('uni-id-admin-exist-in-other-apps')
}
}
}
const {
user,
extraData
} = await preRegisterWithPassword.call(this, {
user: {
username
},
password
})
return postRegister.call(this, {
user,
extraData: {
...extraData,
nickname,
role: ['admin']
}
})
}
const {
postRegister,
preRegisterWithPassword
} = require('../../lib/utils/register')
const {
verifyCaptcha
} = require('../../lib/utils/captcha')
const {
CAPTCHA_SCENE,
EMAIL_SCENE,
LOG_TYPE
} = require('../../common/constants')
const {
verifyEmailCode
} = require('../../lib/utils/verify-code')
/**
* 通过邮箱+验证码注册普通用户
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#register-user-by-email
* @param {Object} params
* @param {String} params.email 邮箱
* @param {String} params.password 密码
* @param {String} params.nickname 昵称
* @param {String} params.code 邮箱验证码
* @param {String} params.inviteCode 邀请码
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
email: 'email',
password: 'password',
nickname: {
required: false,
type: 'nickname'
},
code: 'string',
inviteCode: {
required: false,
type: 'string'
}
}
this.middleware.validate(params, schema)
const {
email,
password,
nickname,
code,
inviteCode
} = params
try {
// 验证邮箱验证码,验证不通过时写入失败日志
await verifyEmailCode({
email,
code,
scene: EMAIL_SCENE.REGISTER
})
} catch (error) {
await this.middleware.uniIdLog({
data: {
email
},
type: LOG_TYPE.REGISTER,
success: false
})
throw error
}
const {
user,
extraData
} = await preRegisterWithPassword.call(this, {
user: {
email
},
password
})
return postRegister.call(this, {
user,
extraData: {
...extraData,
nickname,
email_confirmed: 1
},
inviteCode
})
}
const {
postRegister,
preRegisterWithPassword
} = require('../../lib/utils/register')
const {
verifyCaptcha
} = require('../../lib/utils/captcha')
const {
CAPTCHA_SCENE
} = require('../../common/constants')
/**
* 注册普通用户
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#register-user
* @param {Object} params
* @param {String} params.username 用户名
* @param {String} params.password 密码
* @param {String} params.captcha 图形验证码
* @param {String} params.nickname 昵称
* @param {String} params.inviteCode 邀请码
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
username: 'username',
password: 'password',
captcha: 'string',
nickname: {
required: false,
type: 'nickname'
},
inviteCode: {
required: false,
type: 'string'
}
}
this.middleware.validate(params, schema)
const {
username,
password,
nickname,
captcha,
inviteCode
} = params
await verifyCaptcha.call(this, {
captcha,
scene: CAPTCHA_SCENE.REGISTER
})
const {
user,
extraData
} = await preRegisterWithPassword.call(this, {
user: {
username
},
password
})
return postRegister.call(this, {
user,
extraData: {
...extraData,
nickname
},
inviteCode
})
}
const {
preBind,
postBind
} = require('../../lib/utils/relate')
const {
LOG_TYPE
} = require('../../common/constants')
const {
ERROR
} = require('../../common/error')
const {
initAlipay
} = require('../../lib/third-party/index')
/**
* 绑定支付宝账号
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#bind-alipay
* @param {Object} params
* @param {String} params.code 支付宝小程序登录返回的code参数
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
code: 'string'
}
this.middleware.validate(params, schema)
const uid = this.authInfo.uid
const {
code
} = params
const alipayApi = initAlipay.call(this)
let getAlipayAccountResult
try {
getAlipayAccountResult = await alipayApi().code2Session(code)
} catch (error) {
await this.middleware.uniIdLog({
success: false,
type: LOG_TYPE.BIND_ALIPAY
})
throw {
errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED
}
}
const {
openid
} = getAlipayAccountResult
const bindAccount = {
ali_openid: openid
}
await preBind.call(this, {
uid,
bindAccount,
logType: LOG_TYPE.BIND_APPLE
})
return postBind.call(this, {
uid,
bindAccount,
extraData: {},
logType: LOG_TYPE.BIND_APPLE
})
}
const {
preBind,
postBind
} = require('../../lib/utils/relate')
const {
LOG_TYPE
} = require('../../common/constants')
const {
ERROR
} = require('../../common/error')
const {
initApple
} = require('../../lib/third-party/index')
/**
* 绑定苹果账号
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#bind-apple
* @param {Object} params
* @param {String} params.identityToken 苹果登录返回identityToken
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
identityToken: 'string'
}
this.middleware.validate(params, schema)
const uid = this.authInfo.uid
const {
identityToken
} = params
const appleApi = initApple.call(this)
let verifyResult
try {
verifyResult = await appleApi.verifyIdentityToken(identityToken)
} catch (error) {
await this.middleware.uniIdLog({
success: false,
type: LOG_TYPE.BIND_APPLE
})
throw {
errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED
}
}
const {
openid
} = verifyResult
const bindAccount = {
apple_openid: openid
}
await preBind.call(this, {
uid,
bindAccount,
logType: LOG_TYPE.BIND_APPLE
})
return postBind.call(this, {
uid,
bindAccount,
extraData: {},
logType: LOG_TYPE.BIND_APPLE
})
}
const {
preBind,
postBind
} = require('../../lib/utils/relate')
const {
LOG_TYPE
} = require('../../common/constants')
const {
decryptWeixinData,
getWeixinCache, getWeixinAccessToken
} = require('../../lib/utils/weixin')
const { initWeixin } = require('../../lib/third-party')
const { ERROR } = require('../../common/error')
/**
* 通过微信绑定手机号
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#bind-mobile-by-mp-weixin
* @param {Object} params
* @param {String} params.encryptedData 微信获取手机号返回的加密信息
* @param {String} params.iv 微信获取手机号返回的初始向量
* @param {String} params.code 微信获取手机号返回的code
* @returns
*/
module.exports = async function (params = {}) {
/**
* 微信小程序的规则是客户端应先使用checkSession接口检测上次获取的sessionKey是否仍有效
* 如果有效则直接使用上次存储的sessionKey即可
* 如果无效应重新调用login接口再次刷新sessionKey
* 因此此接口不应直接使用客户端login获取的code,只能使用缓存的sessionKey
*/
const schema = {
encryptedData: {
required: false,
type: 'string'
},
iv: {
required: false,
type: 'string'
},
code: {
required: false,
type: 'string'
}
}
const {
encryptedData,
iv,
code
} = params
this.middleware.validate(params, schema)
if ((!encryptedData && !iv) && !code) {
return {
errCode: ERROR.INVALID_PARAM
}
}
const uid = this.authInfo.uid
let mobile
if (code) {
// 区分客户端类型 小程序还是App
const accessToken = await getWeixinAccessToken.call(this)
const weixinApi = initWeixin.call(this)
const res = await weixinApi.getmobileNumber(accessToken, code)
mobile = res.puremobileNumber
} else {
const sessionKey = await getWeixinCache.call(this, {
uid,
key: 'session_key'
})
if (!sessionKey) {
throw new Error('Session key not found')
}
const res = decryptWeixinData.call(this, {
encryptedData,
sessionKey,
iv
})
mobile = res.puremobileNumber
}
const bindAccount = {
mobile
}
await preBind.call(this, {
uid,
bindAccount,
logType: LOG_TYPE.BIND_MOBILE
})
await postBind.call(this, {
uid,
bindAccount,
extraData: {
mobile_confirmed: 1
},
logType: LOG_TYPE.BIND_MOBILE
})
return {
errCode: 0
}
}
const {
getNeedCaptcha,
verifyCaptcha
} = require('../../lib/utils/captcha')
const {
LOG_TYPE,
SMS_SCENE,
CAPTCHA_SCENE
} = require('../../common/constants')
const {
verifyMobileCode
} = require('../../lib/utils/verify-code')
const {
preBind,
postBind
} = require('../../lib/utils/relate')
/**
* 通过短信验证码绑定手机号
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#bind-mobile-by-sms
* @param {Object} params
* @param {String} params.mobile 手机号
* @param {String} params.code 短信验证码
* @param {String} params.captcha 图形验证码
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
mobile: 'mobile',
code: 'string',
captcha: {
type: 'string',
required: false
}
}
const {
mobile,
code,
captcha
} = params
this.middleware.validate(params, schema)
const uid = this.authInfo.uid
// 判断是否需要验证码
const needCaptcha = await getNeedCaptcha.call(this, {
uid,
type: LOG_TYPE.BIND_MOBILE
})
if (needCaptcha) {
await verifyCaptcha.call(this, {
captcha,
scene: CAPTCHA_SCENE.BIND_MOBILE_BY_SMS
})
}
try {
// 验证手机号验证码,验证不通过时写入失败日志
await verifyMobileCode({
mobile,
code,
scene: SMS_SCENE.BIND_MOBILE_BY_SMS
})
} catch (error) {
await this.middleware.uniIdLog({
data: {
user_id: uid
},
type: LOG_TYPE.BIND_MOBILE,
success: false
})
throw error
}
const bindAccount = {
mobile
}
await preBind.call(this, {
uid,
bindAccount,
logType: LOG_TYPE.BIND_MOBILE
})
await postBind.call(this, {
uid,
bindAccount,
extraData: {
mobile_confirmed: 1
},
logType: LOG_TYPE.BIND_MOBILE
})
return {
errCode: 0
}
}
const {
getmobileNumber
} = require('../../lib/utils/univerify')
const {
LOG_TYPE
} = require('../../common/constants')
const {
preBind,
postBind
} = require('../../lib/utils/relate')
/**
* 通过一键登录绑定手机号
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#bind-mobile-by-univerify
* @param {Object} params
* @param {String} params.openid APP端一键登录返回的openid
* @param {String} params.access_token APP端一键登录返回的access_token
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
openid: 'string',
access_token: 'string'
}
const {
openid,
// eslint-disable-next-line camelcase
access_token
} = params
this.middleware.validate(params, schema)
const uid = this.authInfo.uid
let mobile
try {
const mobileInfo = await getmobileNumber.call(this, {
// eslint-disable-next-line camelcase
access_token,
openid
})
mobile = mobileInfo.mobileNumber
} catch (error) {
await this.middleware.uniIdLog({
success: false,
data: {
user_id: uid
},
type: LOG_TYPE.BIND_MOBILE
})
throw error
}
const bindAccount = {
mobile
}
await preBind.call(this, {
uid,
bindAccount,
logType: LOG_TYPE.BIND_MOBILE
})
await postBind.call(this, {
uid,
bindAccount,
extraData: {
mobile_confirmed: 1
},
logType: LOG_TYPE.BIND_MOBILE
})
return {
errCode: 0
}
}
const {
preBind,
postBind
} = require('../../lib/utils/relate')
const {
LOG_TYPE
} = require('../../common/constants')
const {
ERROR
} = require('../../common/error')
const {
initQQ
} = require('../../lib/third-party/index')
const {
generateQQCache,
getQQPlatform,
saveQQUserKey
} = require('../../lib/utils/qq')
/**
* 绑定QQ
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#bind-qq
* @param {Object} params
* @param {String} params.code 小程序端QQ登录返回的code
* @param {String} params.accessToken APP端QQ登录返回的accessToken
* @param {String} params.accessTokenExpired accessToken过期时间,由App端QQ登录返回的expires_in参数计算而来,单位:毫秒
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
code: {
type: 'string',
required: false
},
accessToken: {
type: 'string',
required: false
},
accessTokenExpired: {
type: 'number',
required: false
}
}
this.middleware.validate(params, schema)
const uid = this.authInfo.uid
const {
code,
accessToken,
accessTokenExpired
} = params
const qqPlatform = getQQPlatform.call(this)
const appId = this.getUniversalClientInfo().appId
const qqApi = initQQ.call(this)
const clientPlatform = this.clientPlatform
const apiName = clientPlatform === 'mp-qq' ? 'code2Session' : 'getOpenidByToken'
let getQQAccountResult
try {
getQQAccountResult = await qqApi[apiName]({
code,
accessToken
})
} catch (error) {
await this.middleware.uniIdLog({
success: false,
type: LOG_TYPE.BIND_QQ
})
throw {
errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED
}
}
const {
openid,
unionid,
// 保存下面四个字段
sessionKey // 微信小程序用户sessionKey
} = getQQAccountResult
const bindAccount = {
qq_openid: {
[qqPlatform]: openid
},
qq_unionid: unionid
}
await preBind.call(this, {
uid,
bindAccount,
logType: LOG_TYPE.BIND_QQ
})
await saveQQUserKey.call(this, {
openid,
sessionKey,
accessToken,
accessTokenExpired
})
return postBind.call(this, {
uid,
bindAccount,
extraData: {
qq_openid: {
[`${qqPlatform}_${appId}`]: openid
},
...generateQQCache.call(this, {
openid,
sessionKey
})
},
logType: LOG_TYPE.BIND_QQ
})
}
const {
preBind,
postBind
} = require('../../lib/utils/relate')
const {
LOG_TYPE
} = require('../../common/constants')
const {
generateWeixinCache,
saveWeixinUserKey,
getWeixinPlatform
} = require('../../lib/utils/weixin')
const {
initWeixin
} = require('../../lib/third-party/index')
const {
ERROR
} = require('../../common/error')
/**
* 绑定微信
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#bind-weixin
* @param {Object} params
* @param {String} params.code 微信登录返回的code
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
code: 'string'
}
this.middleware.validate(params, schema)
const uid = this.authInfo.uid
const {
code
} = params
const weixinPlatform = getWeixinPlatform.call(this)
const appId = this.getUniversalClientInfo().appId
const weixinApi = initWeixin.call(this)
const clientPlatform = this.clientPlatform
const apiName = clientPlatform === 'mp-weixin' ? 'code2Session' : 'getOauthAccessToken'
let getWeixinAccountResult
try {
getWeixinAccountResult = await weixinApi[apiName](code)
} catch (error) {
await this.middleware.uniIdLog({
success: false,
type: LOG_TYPE.BIND_WEIXIN
})
throw {
errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED
}
}
const {
openid,
unionid,
// 保存下面四个字段
sessionKey, // 微信小程序用户sessionKey
accessToken, // App端微信用户accessToken
refreshToken, // App端微信用户refreshToken
expired: accessTokenExpired // App端微信用户accessToken过期时间
} = getWeixinAccountResult
const bindAccount = {
wx_openid: {
[weixinPlatform]: openid
},
wx_unionid: unionid
}
await preBind.call(this, {
uid,
bindAccount,
logType: LOG_TYPE.BIND_WEIXIN
})
await saveWeixinUserKey.call(this, {
openid,
sessionKey,
accessToken,
refreshToken,
accessTokenExpired
})
return postBind.call(this, {
uid,
bindAccount,
extraData: {
wx_openid: {
[`${weixinPlatform}_${appId}`]: openid
},
...generateWeixinCache.call(this, {
openid,
sessionKey, // 微信小程序用户sessionKey
accessToken, // App端微信用户accessToken
refreshToken, // App端微信用户refreshToken
accessTokenExpired // App端微信用户accessToken过期时间
})
},
logType: LOG_TYPE.BIND_WEIXIN
})
}
module.exports = {
bindMobileBySms: require('./bind-mobile-by-sms'),
bindMobileByUniverify: require('./bind-mobile-by-univerify'),
bindMobileByMpWeixin: require('./bind-mobile-by-mp-weixin'),
bindAlipay: require('./bind-alipay'),
bindApple: require('./bind-apple'),
bindQQ: require('./bind-qq'),
bindWeixin: require('./bind-weixin'),
unbindWeixin: require('./unbind-weixin'),
unbindAlipay: require('./unbind-alipay'),
unbindQQ: require('./unbind-qq'),
unbindApple: require('./unbind-apple')
}
const {
preUnBind,
postUnBind
} = require('../../lib/utils/relate')
const {
LOG_TYPE, dbCmd
} = require('../../common/constants')
/**
* 解绑支付宝
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#unbind-alipay
* @returns
*/
module.exports = async function () {
const { uid } = this.authInfo
await preUnBind.call(this, {
uid,
unBindAccount: {
ali_openid: dbCmd.exists(true)
},
logType: LOG_TYPE.UNBIND_ALIPAY
})
return await postUnBind.call(this, {
uid,
unBindAccount: {
ali_openid: dbCmd.remove()
},
logType: LOG_TYPE.UNBIND_ALIPAY
})
}
const {
preUnBind,
postUnBind
} = require('../../lib/utils/relate')
const {
LOG_TYPE, dbCmd
} = require('../../common/constants')
/**
* 解绑apple
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#unbind-apple
* @returns
*/
module.exports = async function () {
const { uid } = this.authInfo
await preUnBind.call(this, {
uid,
unBindAccount: {
apple_openid: dbCmd.exists(true)
},
logType: LOG_TYPE.UNBIND_APPLE
})
return await postUnBind.call(this, {
uid,
unBindAccount: {
apple_openid: dbCmd.remove()
},
logType: LOG_TYPE.UNBIND_APPLE
})
}
const {
preUnBind,
postUnBind
} = require('../../lib/utils/relate')
const {
LOG_TYPE, dbCmd
} = require('../../common/constants')
/**
* 解绑QQ
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#unbind-qq
* @returns
*/
module.exports = async function () {
const { uid } = this.authInfo
await preUnBind.call(this, {
uid,
unBindAccount: {
qq_openid: dbCmd.exists(true),
qq_unionid: dbCmd.exists(true)
},
logType: LOG_TYPE.UNBIND_QQ
})
return await postUnBind.call(this, {
uid,
unBindAccount: {
qq_openid: dbCmd.remove(),
qq_unionid: dbCmd.remove()
},
logType: LOG_TYPE.UNBIND_QQ
})
}
const {
preUnBind,
postUnBind
} = require('../../lib/utils/relate')
const {
LOG_TYPE, dbCmd
} = require('../../common/constants')
const {
getWeixinPlatform
} = require('../../lib/utils/weixin')
/**
* 解绑微信
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#unbind-weixin
* @returns
*/
module.exports = async function () {
const { uid } = this.authInfo
// const weixinPlatform = getWeixinPlatform.call(this)
await preUnBind.call(this, {
uid,
unBindAccount: {
wx_openid: dbCmd.exists(true),
wx_unionid: dbCmd.exists(true)
},
logType: LOG_TYPE.UNBIND_WEIXIN
})
return await postUnBind.call(this, {
uid,
unBindAccount: {
wx_openid: dbCmd.remove(),
wx_unionid: dbCmd.remove()
},
logType: LOG_TYPE.UNBIND_WEIXIN
})
}
module.exports = {
refreshToken: require('./refresh-token'),
setPushCid: require('./set-push-cid'),
secureNetworkHandshakeByWeixin: require('./secure-network-handshake-by-weixin')
}
/**
* 刷新token
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#refresh-token
*/
module.exports = async function () {
const refreshTokenRes = await this.uniIdCommon.refreshToken({
token: this.getUniversalUniIdToken()
})
const {
errCode,
token,
tokenExpired
} = refreshTokenRes
if (errCode) {
throw refreshTokenRes
}
return {
errCode: 0,
newToken: {
token,
tokenExpired
}
}
}
const {
ERROR
} = require('../../common/error')
const {
initWeixin
} = require('../../lib/third-party/index')
const {
saveWeixinUserKey,
saveSecureNetworkCache
} = require('../../lib/utils/weixin')
const loginByWeixin = require('../login/login-by-weixin')
/**
* 微信安全网络握手
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#set-push-cid
* @param {object} params
* @param {string} params.code 微信登录返回的code
* @param {boolean} params.callLoginByWeixin 是否同时调用一次微信登录
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
code: 'string',
callLoginByWeixin: {
type: 'boolean',
required: false
}
}
this.middleware.validate(params, schema)
let platform = this.clientPlatform
if (platform !== 'mp-weixin') {
throw new Error(`[secureNetworkHandshake] platform ${platform} is not supported`)
}
const {
code,
callLoginByWeixin = false
} = params
if (callLoginByWeixin) {
return loginByWeixin.call(this, {
code,
secureNetworkCache: true
})
}
const weixinApi = initWeixin.call(this)
let getWeixinAccountResult
try {
getWeixinAccountResult = await weixinApi.code2Session(code)
} catch (error) {
console.error(error)
throw {
errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED
}
}
const {
openid,
unionid,
sessionKey // 微信小程序用户sessionKey
} = getWeixinAccountResult
await saveSecureNetworkCache.call(this, {
code,
openid,
unionid,
sessionKey
})
await saveWeixinUserKey.call(this, {
openid,
sessionKey
})
return {
errCode: 0
}
}
const {
deviceCollection
} = require('../../common/constants')
const {
ERROR
} = require('../../common/error')
async function setOpendbDevice ({
pushClientId
} = {}) {
// 仅新增,如果存在进行更新操作
const {
appId,
deviceId,
deviceBrand,
deviceModel,
osName,
osVersion,
osLanguage,
osTheme,
devicePixelRatio,
windowWidth,
windowHeight,
screenWidth,
screenHeight,
romName,
romVersion
} = this.getUniversalClientInfo()
const platform = this.clientPlatform
const now = Date.now()
const db = uniCloud.database()
const opendbDeviceCollection = db.collection('opendb-device')
const getDeviceRes = await opendbDeviceCollection.where({
device_id: deviceId
}).get()
const data = {
appid: appId,
device_id: deviceId,
vendor: deviceBrand,
model: deviceModel,
uni_platform: platform,
os_name: osName,
os_version: osVersion,
os_language: osLanguage,
os_theme: osTheme,
pixel_ratio: devicePixelRatio,
window_width: windowWidth,
window_height: windowHeight,
screen_width: screenWidth,
screen_height: screenHeight,
rom_name: romName,
rom_version: romVersion,
last_update_date: now,
push_clientid: pushClientId
}
if (getDeviceRes.data.length > 0) {
await opendbDeviceCollection.where({
device_id: deviceId
}).update(data)
return
}
data.create_date = now
await opendbDeviceCollection.add(data)
}
/**
* 更新device表的push_clien_id
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#set-push-cid
* @param {object} params
* @param {string} params.pushClientId 客户端pushClientId
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
pushClientId: 'string'
}
this.middleware.validate(params, schema)
const {
deviceId,
appId,
osName
} = this.getUniversalClientInfo()
let platform = this.clientPlatform
if (platform === 'app') {
platform += osName
}
const {
uid,
exp
} = this.authInfo
const { pushClientId } = params
const tokenExpired = exp * 1000
const getDeviceRes = await deviceCollection.where({
device_id: deviceId
}).get()
// console.log(getDeviceRes)
if (getDeviceRes.data.length > 1) {
return {
errCode: ERROR.SYSTEM_ERROR
}
}
const deviceRecord = getDeviceRes.data[0]
await setOpendbDevice.call(this, {
pushClientId
})
if (!deviceRecord) {
await deviceCollection.add({
user_id: uid,
device_id: deviceId,
token_expired: tokenExpired,
push_clientid: pushClientId,
appid: appId
})
return {
errCode: 0
}
}
await deviceCollection.where({
device_id: deviceId
}).update({
user_id: uid,
token_expired: tokenExpired,
push_clientid: pushClientId,
appid: appId
})
return {
errCode: 0
}
}
const {
CAPTCHA_SCENE
} = require('../../common/constants')
const {
ERROR
} = require('../../common/error')
/**
* 创建图形验证码
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#create-captcha
* @param {Object} params
* @param {String} params.scene 图形验证码使用场景
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
scene: 'string'
}
this.middleware.validate(params, schema)
const { deviceId, platform } = this.getUniversalClientInfo()
const {
scene
} = params
if (!(Object.values(CAPTCHA_SCENE).includes(scene))) {
throw {
errCode: ERROR.INVALID_PARAM
}
}
return this.uniCaptcha.create({
deviceId,
scene,
uniPlatform: platform
})
}
module.exports = {
createCaptcha: require('./create-captcha'),
refreshCaptcha: require('./refresh-captcha'),
sendSmsCode: require('./send-sms-code'),
sendEmailLink: require('./send-email-link'),
sendEmailCode: require('./send-email-code')
}
const {
CAPTCHA_SCENE
} = require('../../common/constants')
const {
ERROR
} = require('../../common/error')
/**
* 刷新图形验证码
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#refresh-captcha
* @param {Object} params
* @param {String} params.scene 图形验证码使用场景
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
scene: 'string'
}
this.middleware.validate(params, schema)
const { deviceId, platform } = this.getUniversalClientInfo()
const {
scene
} = params
if (!(Object.values(CAPTCHA_SCENE).includes(scene))) {
throw {
errCode: ERROR.INVALID_PARAM
}
}
return this.uniCaptcha.refresh({
deviceId,
scene,
uniPlatform: platform
})
}
const {
verifyCaptcha
} = require('../../lib/utils/captcha')
const {
EMAIL_SCENE
} = require('../../common/constants')
const {
ERROR
} = require('../../common/error')
/**
* 发送邮箱验证码,可用于登录、注册、绑定邮箱、修改密码等操作
* @tutorial
* @param {Object} params
* @param {String} params.email 邮箱
* @param {String} params.captcha 图形验证码
* @param {String} params.scene 使用场景
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
email: 'email',
captcha: 'string',
scene: 'string'
}
this.middleware.validate(params, schema)
const {
email,
captcha,
scene
} = params
if (!(Object.values(EMAIL_SCENE).includes(scene))) {
throw {
errCode: ERROR.INVALID_PARAM
}
}
await verifyCaptcha.call(this, {
scene: 'send-email-code',
captcha
})
// -- 测试代码
await require('../../lib/utils/verify-code')
.setEmailVerifyCode.call(this, {
email,
code: '123456',
expiresIn: 180,
scene
})
return {
errCode: 'uni-id-invalid-mail-template',
errMsg: `已启动测试模式,直接使用:123456作为邮箱验证码即可。\n如果是正式项目,需自行实现发送邮件的相关功能`
}
// -- 测试代码
//发送邮件--需自行实现
}
/**
* 发送邮箱链接,可用于登录、注册、绑定邮箱、修改密码等操作
* @tutorial
* @param {Object} params
* @param {String} params.email 邮箱
* @param {String} params.scene 使用场景
* @returns
*/
module.exports = async function (params = {}) {
// 此接口暂未实现,欢迎向我们提交pr
throw new Error('api[sendEmailLink] is not yet implemented')
}
const {
sendSmsCode
} = require('../../lib/utils/sms')
const {
verifyCaptcha
} = require('../../lib/utils/captcha')
const {
SMS_SCENE
} = require('../../common/constants')
const {
ERROR
} = require('../../common/error')
/**
* 发送短信验证码
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages-x.html#send-sms-code
* @param {Object} params
* @param {String} params.mobile 手机号
* @param {String} params.captcha 图形验证码
* @param {String} params.scene 短信验证码使用场景
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
mobile: 'mobile',
captcha: 'string',
scene: 'string'
}
this.middleware.validate(params, schema)
const {
mobile,
captcha,
scene
} = params
if (!(Object.values(SMS_SCENE).includes(scene))) {
throw {
errCode: ERROR.INVALID_PARAM
}
}
await verifyCaptcha.call(this, {
scene: 'send-sms-code',
captcha
})
// -- 测试代码
const {
templateId
} = (this.config.service &&
this.config.service.sms &&
this.config.service.sms.scene &&
this.config.service.sms.scene[scene]) || {}
if (!templateId) {
await require('../../lib/utils/verify-code')
.setMobileVerifyCode.call(this, {
mobile: params.mobile,
code: '123456',
expiresIn: 180,
scene
})
return {
errCode: 'uni-id-invalid-sms-template-id',
errMsg: `未找到scene=${scene},的短信模版templateId。\n已启动测试模式,直接使用:123456作为短信验证码即可。\n如果是正式项目,请在路径:/common/uni-config-center/uni-id/config.json中service->sms中配置密钥等信息\n更多详情:https://uniapp.dcloud.io/uniCloud/uni-id.html#config`
}
}
// -- 测试代码
return sendSmsCode.call(this, {
mobile,
scene
})
}
{
"name": "uni-id-co",
"version": "1.1.14",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@aashutoshrathi/word-wrap": {
"version": "1.2.6",
"resolved": "https://registry.npmmirror.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
"integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
"dev": true
},
"@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
"integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
"dev": true,
"requires": {
"eslint-visitor-keys": "^3.3.0"
}
},
"@eslint-community/regexpp": {
"version": "4.10.0",
"resolved": "https://registry.npmmirror.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz",
"integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==",
"dev": true
},
"@eslint/eslintrc": {
"version": "2.1.2",
"resolved": "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-2.1.2.tgz",
"integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==",
"dev": true,
"requires": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
"espree": "^9.6.0",
"globals": "^13.19.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
"js-yaml": "^4.1.0",
"minimatch": "^3.1.2",
"strip-json-comments": "^3.1.1"
}
},
"@eslint/js": {
"version": "8.52.0",
"resolved": "https://registry.npmmirror.com/@eslint/js/-/js-8.52.0.tgz",
"integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==",
"dev": true
},
"@humanwhocodes/config-array": {
"version": "0.11.13",
"resolved": "https://registry.npmmirror.com/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
"integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==",
"dev": true,
"requires": {
"@humanwhocodes/object-schema": "^2.0.1",
"debug": "^4.1.1",
"minimatch": "^3.0.5"
}
},
"@humanwhocodes/module-importer": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
"integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
"dev": true
},
"@humanwhocodes/object-schema": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz",
"integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==",
"dev": true
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
"dev": true,
"requires": {
"@nodelib/fs.stat": "2.0.5",
"run-parallel": "^1.1.9"
}
},
"@nodelib/fs.stat": {
"version": "2.0.5",
"resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
"dev": true
},
"@nodelib/fs.walk": {
"version": "1.2.8",
"resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
"dev": true,
"requires": {
"@nodelib/fs.scandir": "2.1.5",
"fastq": "^1.6.0"
}
},
"@types/json5": {
"version": "0.0.29",
"resolved": "https://registry.npmmirror.com/@types/json5/-/json5-0.0.29.tgz",
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true
},
"@ungap/structured-clone": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
"dev": true
},
"acorn": {
"version": "8.11.1",
"resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.11.1.tgz",
"integrity": "sha512-IJTNCJMRHfRfb8un89z1QtS0x890C2QUrUxFMK8zy+RizcId6mfnqOf68Bu9YkDgpLYuvCm6aYbwDatXVZPjMQ==",
"dev": true
},
"acorn-jsx": {
"version": "5.3.2",
"resolved": "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
"dev": true
},
"ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true
},
"array-buffer-byte-length": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz",
"integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"is-array-buffer": "^3.0.1"
}
},
"array-includes": {
"version": "3.1.7",
"resolved": "https://registry.npmmirror.com/array-includes/-/array-includes-3.1.7.tgz",
"integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.2.0",
"es-abstract": "^1.22.1",
"get-intrinsic": "^1.2.1",
"is-string": "^1.0.7"
}
},
"array.prototype.findlastindex": {
"version": "1.2.3",
"resolved": "https://registry.npmmirror.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz",
"integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.2.0",
"es-abstract": "^1.22.1",
"es-shim-unscopables": "^1.0.0",
"get-intrinsic": "^1.2.1"
}
},
"array.prototype.flat": {
"version": "1.3.2",
"resolved": "https://registry.npmmirror.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz",
"integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.2.0",
"es-abstract": "^1.22.1",
"es-shim-unscopables": "^1.0.0"
}
},
"array.prototype.flatmap": {
"version": "1.3.2",
"resolved": "https://registry.npmmirror.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz",
"integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.2.0",
"es-abstract": "^1.22.1",
"es-shim-unscopables": "^1.0.0"
}
},
"arraybuffer.prototype.slice": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz",
"integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==",
"dev": true,
"requires": {
"array-buffer-byte-length": "^1.0.0",
"call-bind": "^1.0.2",
"define-properties": "^1.2.0",
"es-abstract": "^1.22.1",
"get-intrinsic": "^1.2.1",
"is-array-buffer": "^3.0.2",
"is-shared-array-buffer": "^1.0.2"
}
},
"available-typed-arrays": {
"version": "1.0.5",
"resolved": "https://registry.npmmirror.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
"integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==",
"dev": true
},
"balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
},
"builtins": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/builtins/-/builtins-5.0.1.tgz",
"integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==",
"dev": true,
"requires": {
"semver": "^7.0.0"
},
"dependencies": {
"semver": {
"version": "7.5.4",
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
}
}
},
"call-bind": {
"version": "1.0.5",
"resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.5.tgz",
"integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==",
"dev": true,
"requires": {
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.1",
"set-function-length": "^1.1.1"
}
},
"callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true
},
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true
},
"cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
"dev": true,
"requires": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
}
},
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"requires": {
"ms": "2.1.2"
},
"dependencies": {
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
}
}
},
"deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz",
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
"dev": true
},
"define-data-property": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.1.tgz",
"integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==",
"dev": true,
"requires": {
"get-intrinsic": "^1.2.1",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.0"
}
},
"define-properties": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz",
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
"dev": true,
"requires": {
"define-data-property": "^1.0.1",
"has-property-descriptors": "^1.0.0",
"object-keys": "^1.1.1"
}
},
"doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/doctrine/-/doctrine-3.0.0.tgz",
"integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
"dev": true,
"requires": {
"esutils": "^2.0.2"
}
},
"ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmmirror.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"requires": {
"safe-buffer": "^5.0.1"
}
},
"es-abstract": {
"version": "1.22.3",
"resolved": "https://registry.npmmirror.com/es-abstract/-/es-abstract-1.22.3.tgz",
"integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==",
"dev": true,
"requires": {
"array-buffer-byte-length": "^1.0.0",
"arraybuffer.prototype.slice": "^1.0.2",
"available-typed-arrays": "^1.0.5",
"call-bind": "^1.0.5",
"es-set-tostringtag": "^2.0.1",
"es-to-primitive": "^1.2.1",
"function.prototype.name": "^1.1.6",
"get-intrinsic": "^1.2.2",
"get-symbol-description": "^1.0.0",
"globalthis": "^1.0.3",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.0",
"has-proto": "^1.0.1",
"has-symbols": "^1.0.3",
"hasown": "^2.0.0",
"internal-slot": "^1.0.5",
"is-array-buffer": "^3.0.2",
"is-callable": "^1.2.7",
"is-negative-zero": "^2.0.2",
"is-regex": "^1.1.4",
"is-shared-array-buffer": "^1.0.2",
"is-string": "^1.0.7",
"is-typed-array": "^1.1.12",
"is-weakref": "^1.0.2",
"object-inspect": "^1.13.1",
"object-keys": "^1.1.1",
"object.assign": "^4.1.4",
"regexp.prototype.flags": "^1.5.1",
"safe-array-concat": "^1.0.1",
"safe-regex-test": "^1.0.0",
"string.prototype.trim": "^1.2.8",
"string.prototype.trimend": "^1.0.7",
"string.prototype.trimstart": "^1.0.7",
"typed-array-buffer": "^1.0.0",
"typed-array-byte-length": "^1.0.0",
"typed-array-byte-offset": "^1.0.0",
"typed-array-length": "^1.0.4",
"unbox-primitive": "^1.0.2",
"which-typed-array": "^1.1.13"
}
},
"es-set-tostringtag": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz",
"integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==",
"dev": true,
"requires": {
"get-intrinsic": "^1.2.2",
"has-tostringtag": "^1.0.0",
"hasown": "^2.0.0"
}
},
"es-shim-unscopables": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz",
"integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==",
"dev": true,
"requires": {
"hasown": "^2.0.0"
}
},
"es-to-primitive": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
"integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
"dev": true,
"requires": {
"is-callable": "^1.1.4",
"is-date-object": "^1.0.1",
"is-symbol": "^1.0.2"
}
},
"escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true
},
"eslint": {
"version": "8.52.0",
"resolved": "https://registry.npmmirror.com/eslint/-/eslint-8.52.0.tgz",
"integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==",
"dev": true,
"requires": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.2",
"@eslint/js": "8.52.0",
"@humanwhocodes/config-array": "^0.11.13",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
"@ungap/structured-clone": "^1.2.0",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
"debug": "^4.3.2",
"doctrine": "^3.0.0",
"escape-string-regexp": "^4.0.0",
"eslint-scope": "^7.2.2",
"eslint-visitor-keys": "^3.4.3",
"espree": "^9.6.1",
"esquery": "^1.4.2",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
"file-entry-cache": "^6.0.1",
"find-up": "^5.0.0",
"glob-parent": "^6.0.2",
"globals": "^13.19.0",
"graphemer": "^1.4.0",
"ignore": "^5.2.0",
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
"is-path-inside": "^3.0.3",
"js-yaml": "^4.1.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.4.1",
"lodash.merge": "^4.6.2",
"minimatch": "^3.1.2",
"natural-compare": "^1.4.0",
"optionator": "^0.9.3",
"strip-ansi": "^6.0.1",
"text-table": "^0.2.0"
}
},
"eslint-config-standard": {
"version": "17.1.0",
"resolved": "https://registry.npmmirror.com/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz",
"integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==",
"dev": true
},
"eslint-import-resolver-node": {
"version": "0.3.9",
"resolved": "https://registry.npmmirror.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
"integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==",
"dev": true,
"requires": {
"debug": "^3.2.7",
"is-core-module": "^2.13.0",
"resolve": "^1.22.4"
},
"dependencies": {
"debug": {
"version": "3.2.7",
"resolved": "https://registry.npmmirror.com/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"dev": true,
"requires": {
"ms": "^2.1.1"
}
}
}
},
"eslint-module-utils": {
"version": "2.8.0",
"resolved": "https://registry.npmmirror.com/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz",
"integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==",
"dev": true,
"requires": {
"debug": "^3.2.7"
},
"dependencies": {
"debug": {
"version": "3.2.7",
"resolved": "https://registry.npmmirror.com/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"dev": true,
"requires": {
"ms": "^2.1.1"
}
}
}
},
"eslint-plugin-es": {
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz",
"integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==",
"dev": true,
"requires": {
"eslint-utils": "^2.0.0",
"regexpp": "^3.0.0"
},
"dependencies": {
"eslint-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/eslint-utils/-/eslint-utils-2.1.0.tgz",
"integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
"dev": true,
"requires": {
"eslint-visitor-keys": "^1.1.0"
}
},
"eslint-visitor-keys": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
"integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
"dev": true
}
}
},
"eslint-plugin-import": {
"version": "2.29.0",
"resolved": "https://registry.npmmirror.com/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz",
"integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==",
"dev": true,
"requires": {
"array-includes": "^3.1.7",
"array.prototype.findlastindex": "^1.2.3",
"array.prototype.flat": "^1.3.2",
"array.prototype.flatmap": "^1.3.2",
"debug": "^3.2.7",
"doctrine": "^2.1.0",
"eslint-import-resolver-node": "^0.3.9",
"eslint-module-utils": "^2.8.0",
"hasown": "^2.0.0",
"is-core-module": "^2.13.1",
"is-glob": "^4.0.3",
"minimatch": "^3.1.2",
"object.fromentries": "^2.0.7",
"object.groupby": "^1.0.1",
"object.values": "^1.1.7",
"semver": "^6.3.1",
"tsconfig-paths": "^3.14.2"
},
"dependencies": {
"debug": {
"version": "3.2.7",
"resolved": "https://registry.npmmirror.com/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"dev": true,
"requires": {
"ms": "^2.1.1"
}
},
"doctrine": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/doctrine/-/doctrine-2.1.0.tgz",
"integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
"dev": true,
"requires": {
"esutils": "^2.0.2"
}
},
"semver": {
"version": "6.3.1",
"resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true
}
}
},
"eslint-plugin-n": {
"version": "15.7.0",
"resolved": "https://registry.npmmirror.com/eslint-plugin-n/-/eslint-plugin-n-15.7.0.tgz",
"integrity": "sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==",
"dev": true,
"requires": {
"builtins": "^5.0.1",
"eslint-plugin-es": "^4.1.0",
"eslint-utils": "^3.0.0",
"ignore": "^5.1.1",
"is-core-module": "^2.11.0",
"minimatch": "^3.1.2",
"resolve": "^1.22.1",
"semver": "^7.3.8"
},
"dependencies": {
"semver": {
"version": "7.5.4",
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
}
}
},
"eslint-plugin-promise": {
"version": "6.1.1",
"resolved": "https://registry.npmmirror.com/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz",
"integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==",
"dev": true
},
"eslint-scope": {
"version": "7.2.2",
"resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-7.2.2.tgz",
"integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
"dev": true,
"requires": {
"esrecurse": "^4.3.0",
"estraverse": "^5.2.0"
}
},
"eslint-utils": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/eslint-utils/-/eslint-utils-3.0.0.tgz",
"integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
"dev": true,
"requires": {
"eslint-visitor-keys": "^2.0.0"
},
"dependencies": {
"eslint-visitor-keys": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
"integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
"dev": true
}
}
},
"eslint-visitor-keys": {
"version": "3.4.3",
"resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true
},
"espree": {
"version": "9.6.1",
"resolved": "https://registry.npmmirror.com/espree/-/espree-9.6.1.tgz",
"integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
"dev": true,
"requires": {
"acorn": "^8.9.0",
"acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^3.4.1"
}
},
"esquery": {
"version": "1.5.0",
"resolved": "https://registry.npmmirror.com/esquery/-/esquery-1.5.0.tgz",
"integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==",
"dev": true,
"requires": {
"estraverse": "^5.1.0"
}
},
"esrecurse": {
"version": "4.3.0",
"resolved": "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz",
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
"dev": true,
"requires": {
"estraverse": "^5.2.0"
}
},
"estraverse": {
"version": "5.3.0",
"resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true
},
"esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"dev": true
},
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true
},
"fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"dev": true
},
"fast-levenshtein": {
"version": "2.0.6",
"resolved": "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true
},
"fastq": {
"version": "1.15.0",
"resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.15.0.tgz",
"integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
"dev": true,
"requires": {
"reusify": "^1.0.4"
}
},
"file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
"integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
"dev": true,
"requires": {
"flat-cache": "^3.0.4"
}
},
"find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz",
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
"dev": true,
"requires": {
"locate-path": "^6.0.0",
"path-exists": "^4.0.0"
}
},
"flat-cache": {
"version": "3.1.1",
"resolved": "https://registry.npmmirror.com/flat-cache/-/flat-cache-3.1.1.tgz",
"integrity": "sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==",
"dev": true,
"requires": {
"flatted": "^3.2.9",
"keyv": "^4.5.3",
"rimraf": "^3.0.2"
}
},
"flatted": {
"version": "3.2.9",
"resolved": "https://registry.npmmirror.com/flatted/-/flatted-3.2.9.tgz",
"integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==",
"dev": true
},
"for-each": {
"version": "0.3.3",
"resolved": "https://registry.npmmirror.com/for-each/-/for-each-0.3.3.tgz",
"integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
"dev": true,
"requires": {
"is-callable": "^1.1.3"
}
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"dev": true
},
"function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true
},
"function.prototype.name": {
"version": "1.1.6",
"resolved": "https://registry.npmmirror.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz",
"integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.2.0",
"es-abstract": "^1.22.1",
"functions-have-names": "^1.2.3"
}
},
"functions-have-names": {
"version": "1.2.3",
"resolved": "https://registry.npmmirror.com/functions-have-names/-/functions-have-names-1.2.3.tgz",
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
"dev": true
},
"get-intrinsic": {
"version": "1.2.2",
"resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.2.2.tgz",
"integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==",
"dev": true,
"requires": {
"function-bind": "^1.1.2",
"has-proto": "^1.0.1",
"has-symbols": "^1.0.3",
"hasown": "^2.0.0"
}
},
"get-symbol-description": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
"integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"get-intrinsic": "^1.1.1"
}
},
"glob": {
"version": "7.2.3",
"resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"dev": true,
"requires": {
"is-glob": "^4.0.3"
}
},
"globals": {
"version": "13.23.0",
"resolved": "https://registry.npmmirror.com/globals/-/globals-13.23.0.tgz",
"integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==",
"dev": true,
"requires": {
"type-fest": "^0.20.2"
}
},
"globalthis": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/globalthis/-/globalthis-1.0.3.tgz",
"integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==",
"dev": true,
"requires": {
"define-properties": "^1.1.3"
}
},
"gopd": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.0.1.tgz",
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
"dev": true,
"requires": {
"get-intrinsic": "^1.1.3"
}
},
"graphemer": {
"version": "1.4.0",
"resolved": "https://registry.npmmirror.com/graphemer/-/graphemer-1.4.0.tgz",
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
"dev": true
},
"has-bigints": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/has-bigints/-/has-bigints-1.0.2.tgz",
"integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
"dev": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"has-property-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz",
"integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==",
"dev": true,
"requires": {
"get-intrinsic": "^1.2.2"
}
},
"has-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/has-proto/-/has-proto-1.0.1.tgz",
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
"dev": true
},
"has-symbols": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
"dev": true
},
"has-tostringtag": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
"integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
"dev": true,
"requires": {
"has-symbols": "^1.0.2"
}
},
"hasown": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.0.tgz",
"integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
"dev": true,
"requires": {
"function-bind": "^1.1.2"
}
},
"ignore": {
"version": "5.2.4",
"resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.2.4.tgz",
"integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
"dev": true
},
"import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.0.tgz",
"integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
"dev": true,
"requires": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
}
},
"imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
"dev": true
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"dev": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"internal-slot": {
"version": "1.0.6",
"resolved": "https://registry.npmmirror.com/internal-slot/-/internal-slot-1.0.6.tgz",
"integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==",
"dev": true,
"requires": {
"get-intrinsic": "^1.2.2",
"hasown": "^2.0.0",
"side-channel": "^1.0.4"
}
},
"is-array-buffer": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
"integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"get-intrinsic": "^1.2.0",
"is-typed-array": "^1.1.10"
}
},
"is-bigint": {
"version": "1.0.4",
"resolved": "https://registry.npmmirror.com/is-bigint/-/is-bigint-1.0.4.tgz",
"integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
"dev": true,
"requires": {
"has-bigints": "^1.0.1"
}
},
"is-boolean-object": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
"integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0"
}
},
"is-callable": {
"version": "1.2.7",
"resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.7.tgz",
"integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
"dev": true
},
"is-core-module": {
"version": "2.13.1",
"resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.13.1.tgz",
"integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
"dev": true,
"requires": {
"hasown": "^2.0.0"
}
},
"is-date-object": {
"version": "1.0.5",
"resolved": "https://registry.npmmirror.com/is-date-object/-/is-date-object-1.0.5.tgz",
"integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
"dev": true,
"requires": {
"has-tostringtag": "^1.0.0"
}
},
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true
},
"is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"requires": {
"is-extglob": "^2.1.1"
}
},
"is-negative-zero": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
"integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==",
"dev": true
},
"is-number-object": {
"version": "1.0.7",
"resolved": "https://registry.npmmirror.com/is-number-object/-/is-number-object-1.0.7.tgz",
"integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
"dev": true,
"requires": {
"has-tostringtag": "^1.0.0"
}
},
"is-path-inside": {
"version": "3.0.3",
"resolved": "https://registry.npmmirror.com/is-path-inside/-/is-path-inside-3.0.3.tgz",
"integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
"dev": true
},
"is-regex": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.1.4.tgz",
"integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0"
}
},
"is-shared-array-buffer": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
"integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
"dev": true,
"requires": {
"call-bind": "^1.0.2"
}
},
"is-string": {
"version": "1.0.7",
"resolved": "https://registry.npmmirror.com/is-string/-/is-string-1.0.7.tgz",
"integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
"dev": true,
"requires": {
"has-tostringtag": "^1.0.0"
}
},
"is-symbol": {
"version": "1.0.4",
"resolved": "https://registry.npmmirror.com/is-symbol/-/is-symbol-1.0.4.tgz",
"integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
"dev": true,
"requires": {
"has-symbols": "^1.0.2"
}
},
"is-typed-array": {
"version": "1.1.12",
"resolved": "https://registry.npmmirror.com/is-typed-array/-/is-typed-array-1.1.12.tgz",
"integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==",
"dev": true,
"requires": {
"which-typed-array": "^1.1.11"
}
},
"is-weakref": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/is-weakref/-/is-weakref-1.0.2.tgz",
"integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==",
"dev": true,
"requires": {
"call-bind": "^1.0.2"
}
},
"isarray": {
"version": "2.0.5",
"resolved": "https://registry.npmmirror.com/isarray/-/isarray-2.0.5.tgz",
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
"dev": true
},
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true
},
"js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"requires": {
"argparse": "^2.0.1"
}
},
"json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.1.tgz",
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
"dev": true
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"json-stable-stringify-without-jsonify": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
"dev": true
},
"json5": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/json5/-/json5-1.0.2.tgz",
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true,
"requires": {
"minimist": "^1.2.0"
}
},
"jsonwebtoken": {
"version": "8.5.1",
"resolved": "https://registry.npmmirror.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
"integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
"requires": {
"jws": "^3.2.2",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1",
"semver": "^5.6.0"
}
},
"jwa": {
"version": "1.4.1",
"resolved": "https://registry.npmmirror.com/jwa/-/jwa-1.4.1.tgz",
"integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
"requires": {
"buffer-equal-constant-time": "1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"jws": {
"version": "3.2.2",
"resolved": "https://registry.npmmirror.com/jws/-/jws-3.2.2.tgz",
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
"requires": {
"jwa": "^1.4.1",
"safe-buffer": "^5.0.1"
}
},
"keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz",
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
"dev": true,
"requires": {
"json-buffer": "3.0.1"
}
},
"levn": {
"version": "0.4.1",
"resolved": "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz",
"integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
"dev": true,
"requires": {
"prelude-ls": "^1.2.1",
"type-check": "~0.4.0"
}
},
"locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz",
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
"dev": true,
"requires": {
"p-locate": "^5.0.0"
}
},
"lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmmirror.com/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
},
"lodash.isboolean": {
"version": "3.0.3",
"resolved": "https://registry.npmmirror.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
},
"lodash.isinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmmirror.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
},
"lodash.isnumber": {
"version": "3.0.3",
"resolved": "https://registry.npmmirror.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
},
"lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmmirror.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
},
"lodash.isstring": {
"version": "4.0.1",
"resolved": "https://registry.npmmirror.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
},
"lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
},
"lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmmirror.com/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"requires": {
"yallist": "^4.0.0"
}
},
"minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true
},
"ms": {
"version": "2.1.3",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz",
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true
},
"object-inspect": {
"version": "1.13.1",
"resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.1.tgz",
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
"dev": true
},
"object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"dev": true
},
"object.assign": {
"version": "4.1.4",
"resolved": "https://registry.npmmirror.com/object.assign/-/object.assign-4.1.4.tgz",
"integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.4",
"has-symbols": "^1.0.3",
"object-keys": "^1.1.1"
}
},
"object.fromentries": {
"version": "2.0.7",
"resolved": "https://registry.npmmirror.com/object.fromentries/-/object.fromentries-2.0.7.tgz",
"integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.2.0",
"es-abstract": "^1.22.1"
}
},
"object.groupby": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/object.groupby/-/object.groupby-1.0.1.tgz",
"integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.2.0",
"es-abstract": "^1.22.1",
"get-intrinsic": "^1.2.1"
}
},
"object.values": {
"version": "1.1.7",
"resolved": "https://registry.npmmirror.com/object.values/-/object.values-1.1.7.tgz",
"integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.2.0",
"es-abstract": "^1.22.1"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dev": true,
"requires": {
"wrappy": "1"
}
},
"optionator": {
"version": "0.9.3",
"resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.9.3.tgz",
"integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==",
"dev": true,
"requires": {
"@aashutoshrathi/word-wrap": "^1.2.3",
"deep-is": "^0.1.3",
"fast-levenshtein": "^2.0.6",
"levn": "^0.4.1",
"prelude-ls": "^1.2.1",
"type-check": "^0.4.0"
}
},
"p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz",
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"dev": true,
"requires": {
"yocto-queue": "^0.1.0"
}
},
"p-locate": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz",
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
"dev": true,
"requires": {
"p-limit": "^3.0.2"
}
},
"parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"dev": true,
"requires": {
"callsites": "^3.0.0"
}
},
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"dev": true
},
"path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true
},
"path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
},
"prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz",
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
"dev": true
},
"punycode": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.0.tgz",
"integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
"dev": true
},
"queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
"dev": true
},
"regexp.prototype.flags": {
"version": "1.5.1",
"resolved": "https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz",
"integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.2.0",
"set-function-name": "^2.0.0"
}
},
"regexpp": {
"version": "3.2.0",
"resolved": "https://registry.npmmirror.com/regexpp/-/regexpp-3.2.0.tgz",
"integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
"dev": true
},
"resolve": {
"version": "1.22.8",
"resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.8.tgz",
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
"dev": true,
"requires": {
"is-core-module": "^2.13.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
}
},
"resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true
},
"reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.0.4.tgz",
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
"dev": true
},
"rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"dev": true,
"requires": {
"glob": "^7.1.3"
}
},
"run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz",
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
"dev": true,
"requires": {
"queue-microtask": "^1.2.2"
}
},
"safe-array-concat": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz",
"integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"get-intrinsic": "^1.2.1",
"has-symbols": "^1.0.3",
"isarray": "^2.0.5"
}
},
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
},
"safe-regex-test": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
"integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"get-intrinsic": "^1.1.3",
"is-regex": "^1.1.4"
}
},
"semver": {
"version": "5.7.2",
"resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz",
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="
},
"set-function-length": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.1.1.tgz",
"integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==",
"dev": true,
"requires": {
"define-data-property": "^1.1.1",
"get-intrinsic": "^1.2.1",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.0"
}
},
"set-function-name": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/set-function-name/-/set-function-name-2.0.1.tgz",
"integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==",
"dev": true,
"requires": {
"define-data-property": "^1.0.1",
"functions-have-names": "^1.2.3",
"has-property-descriptors": "^1.0.0"
}
},
"shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"requires": {
"shebang-regex": "^3.0.0"
}
},
"shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true
},
"side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"dev": true,
"requires": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
"object-inspect": "^1.9.0"
}
},
"string.prototype.trim": {
"version": "1.2.8",
"resolved": "https://registry.npmmirror.com/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz",
"integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.2.0",
"es-abstract": "^1.22.1"
}
},
"string.prototype.trimend": {
"version": "1.0.7",
"resolved": "https://registry.npmmirror.com/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz",
"integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.2.0",
"es-abstract": "^1.22.1"
}
},
"string.prototype.trimstart": {
"version": "1.0.7",
"resolved": "https://registry.npmmirror.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz",
"integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.2.0",
"es-abstract": "^1.22.1"
}
},
"strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"requires": {
"ansi-regex": "^5.0.1"
}
},
"strip-bom": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/strip-bom/-/strip-bom-3.0.0.tgz",
"integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
"dev": true
},
"strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true
},
"text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz",
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true
},
"tsconfig-paths": {
"version": "3.14.2",
"resolved": "https://registry.npmmirror.com/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz",
"integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==",
"dev": true,
"requires": {
"@types/json5": "^0.0.29",
"json5": "^1.0.2",
"minimist": "^1.2.6",
"strip-bom": "^3.0.0"
}
},
"type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz",
"integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
"dev": true,
"requires": {
"prelude-ls": "^1.2.1"
}
},
"type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.20.2.tgz",
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"dev": true
},
"typed-array-buffer": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz",
"integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"get-intrinsic": "^1.2.1",
"is-typed-array": "^1.1.10"
}
},
"typed-array-byte-length": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz",
"integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"for-each": "^0.3.3",
"has-proto": "^1.0.1",
"is-typed-array": "^1.1.10"
}
},
"typed-array-byte-offset": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz",
"integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==",
"dev": true,
"requires": {
"available-typed-arrays": "^1.0.5",
"call-bind": "^1.0.2",
"for-each": "^0.3.3",
"has-proto": "^1.0.1",
"is-typed-array": "^1.1.10"
}
},
"typed-array-length": {
"version": "1.0.4",
"resolved": "https://registry.npmmirror.com/typed-array-length/-/typed-array-length-1.0.4.tgz",
"integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"for-each": "^0.3.3",
"is-typed-array": "^1.1.9"
}
},
"unbox-primitive": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
"integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"has-bigints": "^1.0.2",
"has-symbols": "^1.0.3",
"which-boxed-primitive": "^1.0.2"
}
},
"uni-captcha": {
"version": "file:../../../../uni-captcha/uniCloud/cloudfunctions/common/uni-captcha",
"requires": {
"uni-config-center": "file:../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
},
"dependencies": {
"uni-config-center": {
"version": "file:../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
}
}
},
"uni-cloud-s2s": {
"version": "file:../../../../uni-cloud-s2s/uniCloud/cloudfunctions/common/uni-cloud-s2s",
"requires": {
"uni-config-center": "file:../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
},
"dependencies": {
"uni-config-center": {
"version": "file:../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
}
}
},
"uni-config-center": {
"version": "file:../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
},
"uni-id-common": {
"version": "file:../../../../uni-id-common/uniCloud/cloudfunctions/common/uni-id-common",
"requires": {
"uni-config-center": "file:../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
},
"dependencies": {
"uni-config-center": {
"version": "file:../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
}
}
},
"uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"dev": true,
"requires": {
"punycode": "^2.1.0"
}
},
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"requires": {
"isexe": "^2.0.0"
}
},
"which-boxed-primitive": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
"integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
"dev": true,
"requires": {
"is-bigint": "^1.0.1",
"is-boolean-object": "^1.1.0",
"is-number-object": "^1.0.4",
"is-string": "^1.0.5",
"is-symbol": "^1.0.3"
}
},
"which-typed-array": {
"version": "1.1.13",
"resolved": "https://registry.npmmirror.com/which-typed-array/-/which-typed-array-1.1.13.tgz",
"integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==",
"dev": true,
"requires": {
"available-typed-arrays": "^1.0.5",
"call-bind": "^1.0.4",
"for-each": "^0.3.3",
"gopd": "^1.0.1",
"has-tostringtag": "^1.0.0"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"dev": true
}
}
}
{
"name": "uni-id-co",
"version": "1.1.14",
"description": "",
"main": "index.js",
"keywords": [],
"author": "DCloud",
"dependencies": {
"jsonwebtoken": "8.5.1",
"lodash.merge": "^4.6.2",
"uni-captcha": "file:../../../../uni-captcha/uniCloud/cloudfunctions/common/uni-captcha",
"uni-config-center": "file:../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center",
"uni-id-common": "file:../../../../uni-id-common/uniCloud/cloudfunctions/common/uni-id-common",
"uni-cloud-s2s": "file:../../../../uni-cloud-s2s/uniCloud/cloudfunctions/common/uni-cloud-s2s"
},
"extensions": {
"uni-cloud-redis": {},
"uni-cloud-sms": {},
"uni-cloud-verify": {}
},
"cloudfunction-config": {
"keepRunningAfterReturn": false
},
"devDependencies": {
"eslint": "^8.18.0",
"eslint-config-standard": "^17.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-n": "^15.2.3",
"eslint-plugin-promise": "^6.0.0"
}
}
{
"bsonType": "object",
"required": [],
"permission": {
"read": false,
"create": true,
"update": false,
"delete": false
},
"properties": {
"_id": {
"description": "ID,系统自动生成"
},
"appid": {
"bsonType": "string",
"description": "DCloud appid"
},
"device_id": {
"bsonType": "string",
"description": "设备唯一标识"
},
"vendor": {
"bsonType": "string",
"description": "设备厂商"
},
"push_clientid": {
"bsonType": "string",
"description": "推送设备客户端标识"
},
"imei": {
"bsonType": "string",
"description": "国际移动设备识别码IMEI(International Mobile Equipment Identity)"
},
"oaid": {
"bsonType": "string",
"description": "移动智能设备标识公共服务平台提供的匿名设备标识符(OAID)"
},
"idfa": {
"bsonType": "string",
"description": "iOS平台配置应用使用广告标识(IDFA)"
},
"imsi": {
"bsonType": "string",
"description": "国际移动用户识别码(International Mobile Subscriber Identification Number)"
},
"model": {
"bsonType": "string",
"description": "设备型号"
},
"platform": {
"bsonType": "string",
"description": "平台类型"
},
"uni_platform": {
"bsonType": "string",
"description": "uni-app 运行平台,与条件编译平台相同。"
},
"os_name": {
"bsonType": "string",
"description": "ios|android|windows|mac|linux "
},
"os_version": {
"bsonType": "string",
"description": "操作系统版本号 "
},
"os_language": {
"bsonType": "string",
"description": "操作系统语言 "
},
"os_theme": {
"bsonType": "string",
"description": "操作系统主题 light|dark"
},
"pixel_ratio": {
"bsonType": "string",
"description": "设备像素比 "
},
"network_model": {
"bsonType": "string",
"description": "设备网络型号wifi\/3G\/4G\/"
},
"window_width": {
"bsonType": "string",
"description": "设备窗口宽度 "
},
"window_height": {
"bsonType": "string",
"description": "设备窗口高度"
},
"screen_width": {
"bsonType": "string",
"description": "设备屏幕宽度"
},
"screen_height": {
"bsonType": "string",
"description": "设备屏幕高度"
},
"rom_name": {
"bsonType": "string",
"description": "rom 名称"
},
"rom_version": {
"bsonType": "string",
"description": "rom 版本"
},
"location_latitude": {
"bsonType": "double",
"description": "纬度"
},
"location_longitude": {
"bsonType": "double",
"description": "经度"
},
"location_country": {
"bsonType": "string",
"description": "国家"
},
"location_province": {
"bsonType": "string",
"description": "省份"
},
"location_city": {
"bsonType": "string",
"description": "城市"
},
"create_date": {
"bsonType": "timestamp",
"description": "创建时间",
"forceDefaultValue": {
"$env": "now"
}
},
"last_update_date": {
"bsonType": "timestamp",
"description": "最后一次修改时间",
"forceDefaultValue": {
"$env": "now"
}
}
},
"version": "0.0.1"
}
\ No newline at end of file
{
"bsonType": "object",
"permission": {
"read": "doc._id == auth.uid || 'CREATE_UNI_ID_USERS' in auth.permission",
"create": "'CREATE_UNI_ID_USERS' in auth.permission",
"update": "doc._id == auth.uid || 'UPDATE_UNI_ID_USERS' in auth.permission",
"delete": "'DELETE_UNI_ID_USERS' in auth.permission"
},
"properties": {
"_id": {
"description": "存储文档 ID(用户 ID),系统自动生成"
},
"certify_id": {
"bsonType": "string",
"description": "认证id"
},
"user_id": {
"bsonType": "string",
"description": "用户id"
},
"real_name": {
"bsonType": "string",
"description": "姓名"
},
"identity": {
"bsonType": "string",
"description": "身份证号码"
},
"status": {
"bsonType": "int",
"description": "认证状态:0 未认证 1 等待认证 2 认证通过 3 认证失败",
"maximum": 3,
"minimum": 0
},
"created_date": {
"bsonType": "timestamp",
"description": "创建时间",
"forceDefaultValue": {
"$env": "now"
}
}
},
"required": []
}
{
"bsonType": "object",
"required": [
"user_id"
],
"properties": {
"_id": {
"description": "ID,系统自动生成"
},
"user_id": {
"bsonType": "string",
"description": "用户id,参考uni-id-users表"
},
"ua": {
"bsonType": "string",
"description": "userAgent"
},
"uuid": {
"bsonType": "string",
"description": "设备唯一标识(需要加密存储)"
},
"os_name": {
"bsonType": "string",
"description": "ios|android|windows|mac|linux "
},
"os_version": {
"bsonType": "string",
"description": "操作系统版本号 "
},
"os_language": {
"bsonType": "string",
"description": "操作系统语言 "
},
"os_theme": {
"bsonType": "string",
"description": "操作系统主题 light|dark"
},
"vendor": {
"bsonType": "string",
"description": "设备厂商"
},
"push_clientid": {
"bsonType": "string",
"description": "推送设备客户端标识"
},
"imei": {
"bsonType": "string",
"description": "国际移动设备识别码IMEI(International Mobile Equipment Identity)"
},
"oaid": {
"bsonType": "string",
"description": "移动智能设备标识公共服务平台提供的匿名设备标识符(OAID)"
},
"idfa": {
"bsonType": "string",
"description": "iOS平台配置应用使用广告标识(IDFA)"
},
"model": {
"bsonType": "string",
"description": "设备型号"
},
"platform": {
"bsonType": "string",
"description": "平台类型"
},
"create_date": {
"bsonType": "timestamp",
"description": "创建时间",
"forceDefaultValue": {
"$env": "now"
}
},
"last_active_date": {
"bsonType": "timestamp",
"description": "最后登录时间"
},
"last_active_ip": {
"bsonType": "string",
"description": "最后登录IP"
}
},
"version": "0.0.1"
}
\ No newline at end of file
{
"bsonType": "object",
"required": ["user_id"],
"permission": {
"read": "'READ_UNI_ID_LOG' in auth.permission"
},
"properties": {
"_id": {
"description": "ID,系统自动生成"
},
"create_date": {
"bsonType": "timestamp",
"description": "创建时间",
"forceDefaultValue": {
"$env": "now"
}
},
"device_uuid": {
"bsonType": "string",
"description": "设备唯一标识"
},
"ip": {
"bsonType": "string",
"description": "ip地址"
},
"state": {
"bsonType": "int",
"description": "结果:0 失败、1 成功"
},
"type": {
"bsonType": "string",
"description": "操作类型",
"enum": [
"logout",
"login",
"register",
"reset-pwd",
"bind-mobile",
"bind-weixin",
"bind-qq",
"bind-apple",
"bind-alipay"
]
},
"ua": {
"bsonType": "string",
"description": "userAgent"
},
"user_id": {
"bsonType": "string",
"foreignKey": "uni-id-users._id",
"description": "用户id,参考uni-id-users表"
},
"username": {
"bsonType": "string",
"description": "用户名"
},
"email": {
"bsonType": "string",
"description": "邮箱"
},
"mobile": {
"bsonType": "string",
"description": "手机号"
},
"appid": {
"bsonType": "string",
"description": "客户端DCloud AppId"
}
}
}
{
"bsonType": "object",
"required": ["permission_id", "permission_name"],
"permission": {
"read": "'READ_UNI_ID_PERMISSIONS' in auth.permission",
"create": "'CREATE_UNI_ID_PERMISSIONS' in auth.permission",
"update": "'UPDATE_UNI_ID_PERMISSIONS' in auth.permission",
"delete": "'DELETE_UNI_ID_PERMISSIONS' in auth.permission"
},
"properties": {
"_id": {
"description": "存储文档 ID,系统自动生成"
},
"comment": {
"bsonType": "string",
"component": {
"name": "textarea"
},
"description": "备注",
"label": "备注",
"title": "备注",
"trim": "both"
},
"create_date": {
"bsonType": "timestamp",
"description": "创建时间",
"forceDefaultValue": {
"$env": "now"
}
},
"permission_id": {
"bsonType": "string",
"component": {
"name": "input"
},
"description": "权限唯一标识,不可修改,不允许重复",
"label": "权限标识",
"title": "权限ID",
"trim": "both"
},
"permission_name": {
"bsonType": "string",
"component": {
"name": "input"
},
"description": "权限名称",
"label": "权限名称",
"title": "权限名称",
"trim": "both"
}
}
}
{
"bsonType": "object",
"required": ["role_id", "role_name"],
"permission": {
"read": "'READ_UNI_ID_ROLES' in auth.permission",
"create": "'CREATE_UNI_ID_ROLES' in auth.permission",
"update": "'UPDATE_UNI_ID_ROLES' in auth.permission",
"delete": "'DELETE_UNI_ID_ROLES' in auth.permission"
},
"properties": {
"_id": {
"description": "存储文档 ID,系统自动生成"
},
"comment": {
"title": "备注",
"bsonType": "string",
"description": "备注",
"trim": "both"
},
"create_date": {
"bsonType": "timestamp",
"description": "创建时间",
"forceDefaultValue": {
"$env": "now"
}
},
"permission": {
"title": "权限",
"bsonType": "array",
"foreignKey": "uni-id-permissions.permission_id",
"description": "角色拥有的权限列表",
"enum": {
"collection": "uni-id-permissions",
"field": "permission_name as text, permission_id as value"
}
},
"role_id": {
"title": "唯一ID",
"bsonType": "string",
"description": "角色唯一标识,不可修改,不允许重复",
"trim": "both"
},
"role_name": {
"title": "名称",
"bsonType": "string",
"description": "角色名称",
"trim": "both"
}
}
}
{
"bsonType": "object",
"permission": {
"read": true,
"create": "'CREATE_UNI_ID_USERS' in auth.permission",
"update": "doc._id == auth.uid || 'UPDATE_UNI_ID_USERS' in auth.permission",
"delete": "'DELETE_UNI_ID_USERS' in auth.permission"
},
"properties": {
"_id": {
"description": "存储文档 ID(用户 ID),系统自动生成"
},
"ali_openid": {
"bsonType": "string",
"description": "支付宝平台openid",
"permission": {
"read": "'READ_UNI_ID_USERS' in auth.permission",
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
}
},
"apple_openid": {
"bsonType": "string",
"description": "苹果登录openid",
"permission": {
"read": "'READ_UNI_ID_USERS' in auth.permission",
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
}
},
"avatar": {
"bsonType": "string",
"description": "头像地址",
"title": "头像地址",
"trim": "both",
"permission": {
"read": true,
"write": "doc._id == auth.uid || 'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
}
},
"avatar_file": {
"bsonType": "file",
"description": "用file类型方便使用uni-file-picker组件",
"title": "头像文件",
"permission": {
"read": true,
"write": "doc._id == auth.uid || 'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
}
},
"comment": {
"bsonType": "string",
"description": "备注",
"title": "备注",
"trim": "both",
"permission": {
"read": "'READ_UNI_ID_USERS' in auth.permission",
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
}
},
"dcloud_appid": {
"bsonType": "array",
"description": "允许登录的客户端的appid列表",
"foreignKey": "opendb-app-list.appid",
"permission": {
"read": "'READ_UNI_ID_USERS' in auth.permission",
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
}
},
"department_id": {
"bsonType": "array",
"description": "部门ID",
"enum": {
"collection": "opendb-department",
"field": "_id as value, name as text",
"orderby": "name asc"
},
"enumType": "tree",
"title": "部门",
"permission": {
"read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission",
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
}
},
"email": {
"bsonType": "string",
"description": "邮箱地址",
"format": "email",
"title": "邮箱",
"trim": "both",
"permission": {
"read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission",
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
}
},
"email_confirmed": {
"bsonType": "int",
"defaultValue": 0,
"description": "邮箱验证状态:0 未验证 1 已验证",
"enum": [{
"text": "未验证",
"value": 0
},
{
"text": "已验证",
"value": 1
}
],
"title": "邮箱验证状态",
"permission": {
"read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission",
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
}
},
"gender": {
"bsonType": "int",
"defaultValue": 0,
"description": "用户性别:0 未知 1 男性 2 女性",
"enum": [{
"text": "未知",
"value": 0
},
{
"text": "男",
"value": 1
},
{
"text": "女",
"value": 2
}
],
"title": "性别",
"permission": {
"read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission",
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
}
},
"invite_time": {
"bsonType": "timestamp",
"description": "受邀时间",
"permission": {
"read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission",
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
}
},
"inviter_uid": {
"bsonType": "array",
"description": "用户全部上级邀请者",
"trim": "both",
"permission": {
"read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission",
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
}
},
"last_login_date": {
"bsonType": "timestamp",
"description": "最后登录时间",
"permission": {
"read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission",
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
}
},
"last_login_ip": {
"bsonType": "string",
"description": "最后登录时 IP 地址",
"permission": {
"read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission",
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
}
},
"mobile": {
"bsonType": "string",
"description": "手机号码",
"pattern": "^\\+?[0-9-]{3,20}$",
"title": "手机号码",
"trim": "both",
"permission": {
"read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission",
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
}
},
"mobile_confirmed": {
"bsonType": "int",
"defaultValue": 0,
"description": "手机号验证状态:0 未验证 1 已验证",
"enum": [{
"text": "未验证",
"value": 0
},
{
"text": "已验证",
"value": 1
}
],
"title": "手机号验证状态",
"permission": {
"read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission",
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
}
},
"my_invite_code": {
"bsonType": "string",
"description": "用户自身邀请码",
"permission": {
"read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission",
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
}
},
"nickname": {
"bsonType": "string",
"description": "用户昵称",
"title": "昵称",
"trim": "both",
"permission": {
"read": true,
"write": "doc._id == auth.uid || 'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
}
},
"password": {
"bsonType": "password",
"description": "密码,加密存储",
"title": "密码",
"trim": "both"
},
"password_secret_version": {
"bsonType": "int",
"description": "密码使用的passwordSecret版本",
"title": "passwordSecret",
"permission": {
"read": false,
"write": false
}
},
"realname_auth": {
"bsonType": "object",
"description": "实名认证信息",
"permission": {
"read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission",
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
},
"properties": {
"auth_date": {
"bsonType": "timestamp",
"description": "认证通过时间"
},
"auth_status": {
"bsonType": "int",
"description": "认证状态:0 未认证 1 等待认证 2 认证通过 3 认证失败",
"maximum": 3,
"minimum": 0
},
"contact_email": {
"bsonType": "string",
"description": "联系人邮箱"
},
"contact_mobile": {
"bsonType": "string",
"description": "联系人手机号码"
},
"contact_person": {
"bsonType": "string",
"description": "联系人姓名"
},
"id_card_back": {
"bsonType": "string",
"description": "身份证反面照 URL"
},
"id_card_front": {
"bsonType": "string",
"description": "身份证正面照 URL"
},
"identity": {
"bsonType": "string",
"description": "身份证号码/营业执照号码"
},
"in_hand": {
"bsonType": "string",
"description": "手持身份证照片 URL"
},
"license": {
"bsonType": "string",
"description": "营业执照 URL"
},
"real_name": {
"bsonType": "string",
"description": "真实姓名/企业名称"
},
"type": {
"bsonType": "int",
"description": "用户类型:0 个人用户 1 企业用户",
"maximum": 1,
"minimum": 0
}
},
"required": [
"type",
"auth_status"
]
},
"register_date": {
"bsonType": "timestamp",
"description": "注册时间",
"forceDefaultValue": {
"$env": "now"
},
"permission": {
"read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission",
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
}
},
"register_ip": {
"bsonType": "string",
"description": "注册时 IP 地址",
"forceDefaultValue": {
"$env": "clientIP"
},
"permission": {
"read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission",
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
}
},
"role": {
"bsonType": "array",
"description": "用户角色",
"enum": {
"collection": "uni-id-roles",
"field": "role_id as value, role_name as text"
},
"foreignKey": "uni-id-roles.role_id",
"permission": {
"read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission",
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
},
"title": "角色"
},
"tags":{
"bsonType": "array",
"description": "用户标签",
"permission": {
"read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission",
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
},
"title": "标签"
},
"score": {
"bsonType": "int",
"description": "用户积分,积分变更记录可参考:uni-id-scores表定义",
"permission": {
"read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission",
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
}
},
"status": {
"bsonType": "int",
"defaultValue": 0,
"description": "用户状态:0 正常 1 禁用 2 审核中 3 审核拒绝",
"permission": {
"read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission",
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
},
"enum": [{
"text": "正常",
"value": 0
},
{
"text": "禁用",
"value": 1
},
{
"text": "审核中",
"value": 2
},
{
"text": "审核拒绝",
"value": 3
}
],
"title": "用户状态"
},
"token": {
"bsonType": "array",
"description": "用户token",
"permission": {
"read": false,
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
}
},
"username": {
"bsonType": "string",
"description": "用户名,不允许重复",
"title": "用户名",
"trim": "both",
"permission": {
"read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission",
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
}
},
"wx_openid": {
"bsonType": "object",
"description": "微信各个平台openid",
"properties": {
"app": {
"bsonType": "string",
"description": "app平台微信openid"
},
"mp": {
"bsonType": "string",
"description": "微信小程序平台openid"
},
"h5": {
"bsonType": "string",
"description": "微信公众号登录openid"
},
"web": {
"bsonType": "string",
"description": "PC页面扫码登录openid"
}
},
"permission": {
"read": "'READ_UNI_ID_USERS' in auth.permission",
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
}
},
"wx_unionid": {
"bsonType": "string",
"description": "微信unionid",
"permission": {
"read": "'READ_UNI_ID_USERS' in auth.permission",
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
}
},
"qq_openid": {
"bsonType": "object",
"description": "QQ各个平台openid",
"properties": {
"app": {
"bsonType": "string",
"description": "app平台QQ openid"
},
"mp": {
"bsonType": "string",
"description": "QQ小程序平台openid"
}
},
"permission": {
"read": "'READ_UNI_ID_USERS' in auth.permission",
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
}
},
"qq_unionid": {
"bsonType": "string",
"description": "QQ unionid",
"permission": {
"read": "'READ_UNI_ID_USERS' in auth.permission",
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
}
},
"third_party": {
"bsonType": "object",
"description": "三方平台凭证",
"permission": {
"read": false,
"write": false
}
},
"identities": {
"bsonType": "array",
"description": "三方平台身份信息;一个对象代表一个身份,参数支持: provider 身份源, userInfo 三方用户信息, openid 三方openid, unionid 三方unionid, uid 三方uid",
"permission": {
"read": "'READ_UNI_ID_USERS' in auth.permission",
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
}
}
},
"required": []
}
## 1.8.4(2023-11-15)
- 新增 uni-popup 支持uni-app-x 注意暂时仅支持 `maskClick` `@open` `@close`
## 1.8.3(2023-04-17)
- 修复 uni-popup 重复打开时的 bug
## 1.8.2(2023-02-02)
- uni-popup-dialog 组件新增 inputType 属性
## 1.8.1(2022-12-01)
- 修复 nvue 下 v-show 报错
## 1.8.0(2022-11-29)
- 优化 主题样式
## 1.7.9(2022-04-02)
- 修复 弹出层内部无法滚动的bug
## 1.7.8(2022-03-28)
- 修复 小程序中高度错误的bug
## 1.7.7(2022-03-17)
- 修复 快速调用open出现问题的Bug
## 1.7.6(2022-02-14)
- 修复 safeArea 属性不能设置为false的bug
## 1.7.5(2022-01-19)
- 修复 isMaskClick 失效的bug
## 1.7.4(2022-01-19)
- 新增 cancelText \ confirmText 属性 ,可自定义文本
- 新增 maskBackgroundColor 属性 ,可以修改蒙版颜色
- 优化 maskClick属性 更新为 isMaskClick ,解决微信小程序警告的问题
## 1.7.3(2022-01-13)
- 修复 设置 safeArea 属性不生效的bug
## 1.7.2(2021-11-26)
- 优化 组件示例
## 1.7.1(2021-11-26)
- 修复 vuedoc 文字错误
## 1.7.0(2021-11-19)
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-popup](https://uniapp.dcloud.io/component/uniui/uni-popup)
## 1.6.2(2021-08-24)
- 新增 支持国际化
## 1.6.1(2021-07-30)
- 优化 vue3下事件警告的问题
## 1.6.0(2021-07-13)
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.5.0(2021-06-23)
- 新增 mask-click 遮罩层点击事件
## 1.4.5(2021-06-22)
- 修复 nvue 平台中间弹出后,点击内容,再点击遮罩无法关闭的Bug
## 1.4.4(2021-06-18)
- 修复 H5平台中间弹出后,点击内容,再点击遮罩无法关闭的Bug
## 1.4.3(2021-06-08)
- 修复 错误的 watch 字段
- 修复 safeArea 属性不生效的问题
- 修复 点击内容,再点击遮罩无法关闭的Bug
## 1.4.2(2021-05-12)
- 新增 组件示例地址
## 1.4.1(2021-04-29)
- 修复 组件内放置 input 、textarea 组件,无法聚焦的问题
## 1.4.0 (2021-04-29)
- 新增 type 属性的 left\right 值,支持左右弹出
- 新增 open(String:type) 方法参数 ,可以省略 type 属性 ,直接传入类型打开指定弹窗
- 新增 backgroundColor 属性,可定义主窗口背景色,默认不显示背景色
- 新增 safeArea 属性,是否适配底部安全区
- 修复 App\h5\微信小程序底部安全区占位不对的Bug
- 修复 App 端弹出等待的Bug
- 优化 提升低配设备性能,优化动画卡顿问题
- 优化 更简单的组件自定义方式
## 1.2.9(2021-02-05)
- 优化 组件引用关系,通过uni_modules引用组件
## 1.2.8(2021-02-05)
- 调整为uni_modules目录规范
## 1.2.7(2021-02-05)
- 调整为uni_modules目录规范
- 新增 支持 PC 端
- 新增 uni-popup-message 、uni-popup-dialog扩展组件支持 PC 端
// #ifdef H5
export default {
name: 'Keypress',
props: {
disable: {
type: Boolean,
default: false
}
},
mounted () {
const keyNames = {
esc: ['Esc', 'Escape'],
tab: 'Tab',
enter: 'Enter',
space: [' ', 'Spacebar'],
up: ['Up', 'ArrowUp'],
left: ['Left', 'ArrowLeft'],
right: ['Right', 'ArrowRight'],
down: ['Down', 'ArrowDown'],
delete: ['Backspace', 'Delete', 'Del']
}
const listener = ($event) => {
if (this.disable) {
return
}
const keyName = Object.keys(keyNames).find(key => {
const keyName = $event.key
const value = keyNames[key]
return value === keyName || (Array.isArray(value) && value.includes(keyName))
})
if (keyName) {
// 避免和其他按键事件冲突
setTimeout(() => {
this.$emit(keyName, {})
}, 0)
}
}
document.addEventListener('keyup', listener)
this.$once('hook:beforeDestroy', () => {
document.removeEventListener('keyup', listener)
})
},
render: () => {}
}
// #endif
<template>
<view class="uni-popup-dialog">
<view class="uni-dialog-title">
<text class="uni-dialog-title-text" :class="['uni-popup__'+dialogType]">{{titleText}}</text>
</view>
<view v-if="mode === 'base'" class="uni-dialog-content">
<slot>
<text class="uni-dialog-content-text">{{content}}</text>
</slot>
</view>
<view v-else class="uni-dialog-content">
<slot>
<input class="uni-dialog-input" v-model="val" :type="inputType" :placeholder="placeholderText" :focus="focus" >
</slot>
</view>
<view class="uni-dialog-button-group">
<view class="uni-dialog-button" @click="closeDialog">
<text class="uni-dialog-button-text">{{closeText}}</text>
</view>
<view class="uni-dialog-button uni-border-left" @click="onOk">
<text class="uni-dialog-button-text uni-button-color">{{okText}}</text>
</view>
</view>
</view>
</template>
<script>
import popup from '../uni-popup/popup.js'
import {
initVueI18n
} from '@dcloudio/uni-i18n'
import messages from '../uni-popup/i18n/index.js'
const { t } = initVueI18n(messages)
/**
* PopUp 弹出层-对话框样式
* @description 弹出层-对话框样式
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
* @property {String} value input 模式下的默认值
* @property {String} placeholder input 模式下输入提示
* @property {String} type = [success|warning|info|error] 主题样式
* @value success 成功
* @value warning 提示
* @value info 消息
* @value error 错误
* @property {String} mode = [base|input] 模式、
* @value base 基础对话框
* @value input 可输入对话框
* @property {String} content 对话框内容
* @property {Boolean} beforeClose 是否拦截取消事件
* @event {Function} confirm 点击确认按钮触发
* @event {Function} close 点击取消按钮触发
*/
export default {
name: "uniPopupDialog",
mixins: [popup],
emits:['confirm','close'],
props: {
inputType:{
type: String,
default: 'text'
},
value: {
type: [String, Number],
default: ''
},
placeholder: {
type: [String, Number],
default: ''
},
type: {
type: String,
default: 'error'
},
mode: {
type: String,
default: 'base'
},
title: {
type: String,
default: ''
},
content: {
type: String,
default: ''
},
beforeClose: {
type: Boolean,
default: false
},
cancelText:{
type: String,
default: ''
},
confirmText:{
type: String,
default: ''
}
},
data() {
return {
dialogType: 'error',
focus: false,
val: ""
}
},
computed: {
okText() {
return this.confirmText || t("uni-popup.ok")
},
closeText() {
return this.cancelText || t("uni-popup.cancel")
},
placeholderText() {
return this.placeholder || t("uni-popup.placeholder")
},
titleText() {
return this.title || t("uni-popup.title")
}
},
watch: {
type(val) {
this.dialogType = val
},
mode(val) {
if (val === 'input') {
this.dialogType = 'info'
}
},
value(val) {
this.val = val
}
},
created() {
// 对话框遮罩不可点击
this.popup.disableMask()
// this.popup.closeMask()
if (this.mode === 'input') {
this.dialogType = 'info'
this.val = this.value
} else {
this.dialogType = this.type
}
},
mounted() {
this.focus = true
},
methods: {
/**
* 点击确认按钮
*/
onOk() {
if (this.mode === 'input'){
this.$emit('confirm', this.val)
}else{
this.$emit('confirm')
}
if(this.beforeClose) return
this.popup.close()
},
/**
* 点击取消按钮
*/
closeDialog() {
this.$emit('close')
if(this.beforeClose) return
this.popup.close()
},
close(){
this.popup.close()
}
}
}
</script>
<style lang="scss" >
.uni-popup-dialog {
width: 300px;
border-radius: 11px;
background-color: #fff;
}
.uni-dialog-title {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
padding-top: 25px;
}
.uni-dialog-title-text {
font-size: 16px;
font-weight: 500;
}
.uni-dialog-content {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
align-items: center;
padding: 20px;
}
.uni-dialog-content-text {
font-size: 14px;
color: #6C6C6C;
}
.uni-dialog-button-group {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
border-top-color: #f5f5f5;
border-top-style: solid;
border-top-width: 1px;
}
.uni-dialog-button {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex: 1;
flex-direction: row;
justify-content: center;
align-items: center;
height: 45px;
}
.uni-border-left {
border-left-color: #f0f0f0;
border-left-style: solid;
border-left-width: 1px;
}
.uni-dialog-button-text {
font-size: 16px;
color: #333;
}
.uni-button-color {
color: #007aff;
}
.uni-dialog-input {
flex: 1;
font-size: 14px;
border: 1px #eee solid;
height: 40px;
padding: 0 10px;
border-radius: 5px;
color: #555;
}
.uni-popup__success {
color: #4cd964;
}
.uni-popup__warn {
color: #f0ad4e;
}
.uni-popup__error {
color: #dd524d;
}
.uni-popup__info {
color: #909399;
}
</style>
<template>
<view class="uni-popup-message">
<view class="uni-popup-message__box fixforpc-width" :class="'uni-popup__'+type">
<slot>
<text class="uni-popup-message-text" :class="'uni-popup__'+type+'-text'">{{message}}</text>
</slot>
</view>
</view>
</template>
<script>
import popup from '../uni-popup/popup.js'
/**
* PopUp 弹出层-消息提示
* @description 弹出层-消息提示
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
* @property {String} type = [success|warning|info|error] 主题样式
* @value success 成功
* @value warning 提示
* @value info 消息
* @value error 错误
* @property {String} message 消息提示文字
* @property {String} duration 显示时间,设置为 0 则不会自动关闭
*/
export default {
name: 'uniPopupMessage',
mixins:[popup],
props: {
/**
* 主题 success/warning/info/error 默认 success
*/
type: {
type: String,
default: 'success'
},
/**
* 消息文字
*/
message: {
type: String,
default: ''
},
/**
* 显示时间,设置为 0 则不会自动关闭
*/
duration: {
type: Number,
default: 3000
},
maskShow:{
type:Boolean,
default:false
}
},
data() {
return {}
},
created() {
this.popup.maskShow = this.maskShow
this.popup.messageChild = this
},
methods: {
timerClose(){
if(this.duration === 0) return
clearTimeout(this.timer)
this.timer = setTimeout(()=>{
this.popup.close()
},this.duration)
}
}
}
</script>
<style lang="scss" >
.uni-popup-message {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
}
.uni-popup-message__box {
background-color: #e1f3d8;
padding: 10px 15px;
border-color: #eee;
border-style: solid;
border-width: 1px;
flex: 1;
}
@media screen and (min-width: 500px) {
.fixforpc-width {
margin-top: 20px;
border-radius: 4px;
flex: none;
min-width: 380px;
/* #ifndef APP-NVUE */
max-width: 50%;
/* #endif */
/* #ifdef APP-NVUE */
max-width: 500px;
/* #endif */
}
}
.uni-popup-message-text {
font-size: 14px;
padding: 0;
}
.uni-popup__success {
background-color: #e1f3d8;
}
.uni-popup__success-text {
color: #67C23A;
}
.uni-popup__warn {
background-color: #faecd8;
}
.uni-popup__warn-text {
color: #E6A23C;
}
.uni-popup__error {
background-color: #fde2e2;
}
.uni-popup__error-text {
color: #F56C6C;
}
.uni-popup__info {
background-color: #F2F6FC;
}
.uni-popup__info-text {
color: #909399;
}
</style>
<template>
<view class="uni-popup-share">
<view class="uni-share-title"><text class="uni-share-title-text">{{shareTitleText}}</text></view>
<view class="uni-share-content">
<view class="uni-share-content-box">
<view class="uni-share-content-item" v-for="(item,index) in bottomData" :key="index" @click.stop="select(item,index)">
<image class="uni-share-image" :src="item.icon" mode="aspectFill"></image>
<text class="uni-share-text">{{item.text}}</text>
</view>
</view>
</view>
<view class="uni-share-button-box">
<button class="uni-share-button" @click="close">{{cancelText}}</button>
</view>
</view>
</template>
<script>
import popup from '../uni-popup/popup.js'
import {
initVueI18n
} from '@dcloudio/uni-i18n'
import messages from '../uni-popup/i18n/index.js'
const { t } = initVueI18n(messages)
export default {
name: 'UniPopupShare',
mixins:[popup],
emits:['select'],
props: {
title: {
type: String,
default: ''
},
beforeClose: {
type: Boolean,
default: false
}
},
data() {
return {
bottomData: [{
text: '微信',
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/c2b17470-50be-11eb-b680-7980c8a877b8.png',
name: 'wx'
},
{
text: '支付宝',
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/d684ae40-50be-11eb-8ff1-d5dcf8779628.png',
name: 'wx'
},
{
text: 'QQ',
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/e7a79520-50be-11eb-b997-9918a5dda011.png',
name: 'qq'
},
{
text: '新浪',
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/0dacdbe0-50bf-11eb-8ff1-d5dcf8779628.png',
name: 'sina'
},
// {
// text: '百度',
// icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/1ec6e920-50bf-11eb-8a36-ebb87efcf8c0.png',
// name: 'copy'
// },
// {
// text: '其他',
// icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/2e0fdfe0-50bf-11eb-b997-9918a5dda011.png',
// name: 'more'
// }
]
}
},
created() {},
computed: {
cancelText() {
return t("uni-popup.cancel")
},
shareTitleText() {
return this.title || t("uni-popup.shareTitle")
}
},
methods: {
/**
* 选择内容
*/
select(item, index) {
this.$emit('select', {
item,
index
})
this.close()
},
/**
* 关闭窗口
*/
close() {
if(this.beforeClose) return
this.popup.close()
}
}
}
</script>
<style lang="scss" >
.uni-popup-share {
background-color: #fff;
border-top-left-radius: 11px;
border-top-right-radius: 11px;
}
.uni-share-title {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
justify-content: center;
height: 40px;
}
.uni-share-title-text {
font-size: 14px;
color: #666;
}
.uni-share-content {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
padding-top: 10px;
}
.uni-share-content-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
flex-wrap: wrap;
width: 360px;
}
.uni-share-content-item {
width: 90px;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: center;
padding: 10px 0;
align-items: center;
}
.uni-share-content-item:active {
background-color: #f5f5f5;
}
.uni-share-image {
width: 30px;
height: 30px;
}
.uni-share-text {
margin-top: 10px;
font-size: 14px;
color: #3B4144;
}
.uni-share-button-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
padding: 10px 15px;
}
.uni-share-button {
flex: 1;
border-radius: 50px;
color: #666;
font-size: 16px;
}
.uni-share-button::after {
border-radius: 50px;
}
</style>
{
"uni-popup.cancel": "cancel",
"uni-popup.ok": "ok",
"uni-popup.placeholder": "pleace enter",
"uni-popup.title": "Hint",
"uni-popup.shareTitle": "Share to"
}
import en from './en.json'
import zhHans from './zh-Hans.json'
import zhHant from './zh-Hant.json'
export default {
en,
'zh-Hans': zhHans,
'zh-Hant': zhHant
}
{
"uni-popup.cancel": "取消",
"uni-popup.ok": "确定",
"uni-popup.placeholder": "请输入",
"uni-popup.title": "提示",
"uni-popup.shareTitle": "分享到"
}
{
"uni-popup.cancel": "取消",
"uni-popup.ok": "確定",
"uni-popup.placeholder": "請輸入",
"uni-popup.title": "提示",
"uni-popup.shareTitle": "分享到"
}
// #ifdef H5
export default {
name: 'Keypress',
props: {
disable: {
type: Boolean,
default: false
}
},
mounted () {
const keyNames = {
esc: ['Esc', 'Escape'],
tab: 'Tab',
enter: 'Enter',
space: [' ', 'Spacebar'],
up: ['Up', 'ArrowUp'],
left: ['Left', 'ArrowLeft'],
right: ['Right', 'ArrowRight'],
down: ['Down', 'ArrowDown'],
delete: ['Backspace', 'Delete', 'Del']
}
const listener = ($event) => {
if (this.disable) {
return
}
const keyName = Object.keys(keyNames).find(key => {
const keyName = $event.key
const value = keyNames[key]
return value === keyName || (Array.isArray(value) && value.includes(keyName))
})
if (keyName) {
// 避免和其他按键事件冲突
setTimeout(() => {
this.$emit(keyName, {})
}, 0)
}
}
document.addEventListener('keyup', listener)
// this.$once('hook:beforeDestroy', () => {
// document.removeEventListener('keyup', listener)
// })
},
render: () => {}
}
// #endif
export default {
data() {
return {
}
},
created(){
this.popup = this.getParent()
},
methods:{
/**
* 获取父元素实例
*/
getParent(name = 'uniPopup') {
let parent = this.$parent;
let parentName = parent.$options.name;
while (parentName !== name) {
parent = parent.$parent;
if (!parent) return false
parentName = parent.$options.name;
}
return parent;
},
}
}
<template>
<view class="popup-root" v-if="isOpen" v-show="isShow" @click="clickMask">
<view class="popup-container" @click.stop>
<slot></slot>
</view>
</view>
</template>
<script>
type CloseCallBack = ()=> void;
let closeCallBack:CloseCallBack = () :void => {};
export default {
emits:["close","clickMask"],
data() {
return {
isShow:false,
isOpen:false
}
},
props: {
maskClick: {
type: Boolean,
default: true
},
},
watch: {
// 设置show = true 时,如果没有 open 需要设置为 open
isShow:{
handler(isShow) {
// console.log("isShow",isShow)
if(isShow && this.isOpen == false){
this.isOpen = true
}
},
immediate:true
},
// 设置isOpen = true 时,如果没有 isShow 需要设置为 isShow
isOpen:{
handler(isOpen) {
// console.log("isOpen",isOpen)
if(isOpen && this.isShow == false){
this.isShow = true
}
},
immediate:true
}
},
methods:{
open(){
// ...funs : CloseCallBack[]
// if(funs.length > 0){
// closeCallBack = funs[0]
// }
this.isOpen = true;
},
clickMask(){
if(this.maskClick == true){
this.$emit('clickMask')
this.close()
}
},
close(): void{
this.isOpen = false;
this.$emit('close')
closeCallBack()
},
hiden(){
this.isShow = false
},
show(){
this.isShow = true
}
}
}
</script>
<style>
.popup-root {
position: fixed;
top: 0;
left: 0;
width: 750rpx;
height: 100%;
flex: 1;
background-color: rgba(0, 0, 0, 0.3);
justify-content: center;
align-items: center;
z-index: 99;
}
.popup-container {
margin-top: -120px;
}
</style>
\ No newline at end of file
<template>
<view v-if="showPopup" class="uni-popup" :class="[popupstyle, isDesktop ? 'fixforpc-z-index' : '']">
<view @touchstart="touchstart">
<uni-transition key="1" v-if="maskShow" name="mask" mode-class="fade" :styles="maskClass"
:duration="duration" :show="showTrans" @click="onTap" />
<uni-transition key="2" :mode-class="ani" name="content" :styles="transClass" :duration="duration"
:show="showTrans" @click="onTap">
<view class="uni-popup__wrapper" :style="{ backgroundColor: bg }" :class="[popupstyle]" @click="clear">
<slot />
</view>
</uni-transition>
</view>
<!-- #ifdef H5 -->
<keypress v-if="maskShow" @esc="onTap" />
<!-- #endif -->
</view>
</template>
<script>
// #ifdef H5
import keypress from './keypress.js'
// #endif
/**
* PopUp 弹出层
* @description 弹出层组件,为了解决遮罩弹层的问题
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
* @property {String} type = [top|center|bottom|left|right|message|dialog|share] 弹出方式
* @value top 顶部弹出
* @value center 中间弹出
* @value bottom 底部弹出
* @value left 左侧弹出
* @value right 右侧弹出
* @value message 消息提示
* @value dialog 对话框
* @value share 底部分享示例
* @property {Boolean} animation = [true|false] 是否开启动画
* @property {Boolean} maskClick = [true|false] 蒙版点击是否关闭弹窗(废弃)
* @property {Boolean} isMaskClick = [true|false] 蒙版点击是否关闭弹窗
* @property {String} backgroundColor 主窗口背景色
* @property {String} maskBackgroundColor 蒙版颜色
* @property {Boolean} safeArea 是否适配底部安全区
* @event {Function} change 打开关闭弹窗触发,e={show: false}
* @event {Function} maskClick 点击遮罩触发
*/
export default {
name: 'uniPopup',
components: {
// #ifdef H5
keypress
// #endif
},
emits: ['change', 'maskClick'],
props: {
// 开启动画
animation: {
type: Boolean,
default: true
},
// 弹出层类型,可选值,top: 顶部弹出层;bottom:底部弹出层;center:全屏弹出层
// message: 消息提示 ; dialog : 对话框
type: {
type: String,
default: 'center'
},
// maskClick
isMaskClick: {
type: Boolean,
default: null
},
// TODO 2 个版本后废弃属性 ,使用 isMaskClick
maskClick: {
type: Boolean,
default: null
},
backgroundColor: {
type: String,
default: 'none'
},
safeArea: {
type: Boolean,
default: true
},
maskBackgroundColor: {
type: String,
default: 'rgba(0, 0, 0, 0.4)'
},
},
watch: {
/**
* 监听type类型
*/
type: {
handler: function(type) {
if (!this.config[type]) return
this[this.config[type]](true)
},
immediate: true
},
isDesktop: {
handler: function(newVal) {
if (!this.config[newVal]) return
this[this.config[this.type]](true)
},
immediate: true
},
/**
* 监听遮罩是否可点击
* @param {Object} val
*/
maskClick: {
handler: function(val) {
this.mkclick = val
},
immediate: true
},
isMaskClick: {
handler: function(val) {
this.mkclick = val
},
immediate: true
},
// H5 下禁止底部滚动
showPopup(show) {
// #ifdef H5
// fix by mehaotian 处理 h5 滚动穿透的问题
document.getElementsByTagName('body')[0].style.overflow = show ? 'hidden' : 'visible'
// #endif
}
},
data() {
return {
duration: 300,
ani: [],
showPopup: false,
showTrans: false,
popupWidth: 0,
popupHeight: 0,
config: {
top: 'top',
bottom: 'bottom',
center: 'center',
left: 'left',
right: 'right',
message: 'top',
dialog: 'center',
share: 'bottom'
},
maskClass: {
position: 'fixed',
bottom: 0,
top: 0,
left: 0,
right: 0,
backgroundColor: 'rgba(0, 0, 0, 0.4)'
},
transClass: {
position: 'fixed',
left: 0,
right: 0
},
maskShow: true,
mkclick: true,
popupstyle: 'top'
}
},
computed: {
isDesktop() {
return this.popupWidth >= 500 && this.popupHeight >= 500
},
bg() {
if (this.backgroundColor === '' || this.backgroundColor === 'none') {
return 'transparent'
}
return this.backgroundColor
}
},
mounted() {
const fixSize = () => {
const {
windowWidth,
windowHeight,
windowTop,
safeArea,
screenHeight,
safeAreaInsets
} = uni.getSystemInfoSync()
this.popupWidth = windowWidth
this.popupHeight = windowHeight + (windowTop || 0)
// TODO fix by mehaotian 是否适配底部安全区 ,目前微信ios 、和 app ios 计算有差异,需要框架修复
if (safeArea && this.safeArea) {
// #ifdef MP-WEIXIN
this.safeAreaInsets = screenHeight - safeArea.bottom
// #endif
// #ifndef MP-WEIXIN
this.safeAreaInsets = safeAreaInsets.bottom
// #endif
} else {
this.safeAreaInsets = 0
}
}
fixSize()
// #ifdef H5
// window.addEventListener('resize', fixSize)
// this.$once('hook:beforeDestroy', () => {
// window.removeEventListener('resize', fixSize)
// })
// #endif
},
// #ifndef VUE3
// TODO vue2
destroyed() {
this.setH5Visible()
},
// #endif
// #ifdef VUE3
// TODO vue3
unmounted() {
this.setH5Visible()
},
// #endif
created() {
// this.mkclick = this.isMaskClick || this.maskClick
if (this.isMaskClick === null && this.maskClick === null) {
this.mkclick = true
} else {
this.mkclick = this.isMaskClick !== null ? this.isMaskClick : this.maskClick
}
if (this.animation) {
this.duration = 300
} else {
this.duration = 0
}
// TODO 处理 message 组件生命周期异常的问题
this.messageChild = null
// TODO 解决头条冒泡的问题
this.clearPropagation = false
this.maskClass.backgroundColor = this.maskBackgroundColor
},
methods: {
setH5Visible() {
// #ifdef H5
// fix by mehaotian 处理 h5 滚动穿透的问题
document.getElementsByTagName('body')[0].style.overflow = 'visible'
// #endif
},
/**
* 公用方法,不显示遮罩层
*/
closeMask() {
this.maskShow = false
},
/**
* 公用方法,遮罩层禁止点击
*/
disableMask() {
this.mkclick = false
},
// TODO nvue 取消冒泡
clear(e) {
// #ifndef APP-NVUE
e.stopPropagation()
// #endif
this.clearPropagation = true
},
open(direction) {
// fix by mehaotian 处理快速打开关闭的情况
if (this.showPopup) {
return
}
let innerType = ['top', 'center', 'bottom', 'left', 'right', 'message', 'dialog', 'share']
if (!(direction && innerType.indexOf(direction) !== -1)) {
direction = this.type
}
if (!this.config[direction]) {
console.error('缺少类型:', direction)
return
}
this[this.config[direction]]()
this.$emit('change', {
show: true,
type: direction
})
},
close(type) {
this.showTrans = false
this.$emit('change', {
show: false,
type: this.type
})
clearTimeout(this.timer)
// // 自定义关闭事件
// this.customOpen && this.customClose()
this.timer = setTimeout(() => {
this.showPopup = false
}, 300)
},
// TODO 处理冒泡事件,头条的冒泡事件有问题 ,先这样兼容
touchstart() {
this.clearPropagation = false
},
onTap() {
if (this.clearPropagation) {
// fix by mehaotian 兼容 nvue
this.clearPropagation = false
return
}
this.$emit('maskClick')
if (!this.mkclick) return
this.close()
},
/**
* 顶部弹出样式处理
*/
top(type) {
this.popupstyle = this.isDesktop ? 'fixforpc-top' : 'top'
this.ani = ['slide-top']
this.transClass = {
position: 'fixed',
left: 0,
right: 0,
backgroundColor: this.bg
}
// TODO 兼容 type 属性 ,后续会废弃
if (type) return
this.showPopup = true
this.showTrans = true
this.$nextTick(() => {
if (this.messageChild && this.type === 'message') {
this.messageChild.timerClose()
}
})
},
/**
* 底部弹出样式处理
*/
bottom(type) {
this.popupstyle = 'bottom'
this.ani = ['slide-bottom']
this.transClass = {
position: 'fixed',
left: 0,
right: 0,
bottom: 0,
paddingBottom: this.safeAreaInsets + 'px',
backgroundColor: this.bg
}
// TODO 兼容 type 属性 ,后续会废弃
if (type) return
this.showPopup = true
this.showTrans = true
},
/**
* 中间弹出样式处理
*/
center(type) {
this.popupstyle = 'center'
this.ani = ['zoom-out', 'fade']
this.transClass = {
position: 'fixed',
/* #ifndef APP-NVUE */
display: 'flex',
flexDirection: 'column',
/* #endif */
bottom: 0,
left: 0,
right: 0,
top: 0,
justifyContent: 'center',
alignItems: 'center'
}
// TODO 兼容 type 属性 ,后续会废弃
if (type) return
this.showPopup = true
this.showTrans = true
},
left(type) {
this.popupstyle = 'left'
this.ani = ['slide-left']
this.transClass = {
position: 'fixed',
left: 0,
bottom: 0,
top: 0,
backgroundColor: this.bg,
/* #ifndef APP-NVUE */
display: 'flex',
flexDirection: 'column'
/* #endif */
}
// TODO 兼容 type 属性 ,后续会废弃
if (type) return
this.showPopup = true
this.showTrans = true
},
right(type) {
this.popupstyle = 'right'
this.ani = ['slide-right']
this.transClass = {
position: 'fixed',
bottom: 0,
right: 0,
top: 0,
backgroundColor: this.bg,
/* #ifndef APP-NVUE */
display: 'flex',
flexDirection: 'column'
/* #endif */
}
// TODO 兼容 type 属性 ,后续会废弃
if (type) return
this.showPopup = true
this.showTrans = true
}
}
}
</script>
<style lang="scss">
.uni-popup {
position: fixed;
/* #ifndef APP-NVUE */
z-index: 99;
/* #endif */
&.top,
&.left,
&.right {
/* #ifdef H5 */
top: var(--window-top);
/* #endif */
/* #ifndef H5 */
top: 0;
/* #endif */
}
.uni-popup__wrapper {
/* #ifndef APP-NVUE */
display: block;
/* #endif */
position: relative;
/* iphonex 等安全区设置,底部安全区适配 */
/* #ifndef APP-NVUE */
// padding-bottom: constant(safe-area-inset-bottom);
// padding-bottom: env(safe-area-inset-bottom);
/* #endif */
&.left,
&.right {
/* #ifdef H5 */
padding-top: var(--window-top);
/* #endif */
/* #ifndef H5 */
padding-top: 0;
/* #endif */
flex: 1;
}
}
}
.fixforpc-z-index {
/* #ifndef APP-NVUE */
z-index: 999;
/* #endif */
}
.fixforpc-top {
top: 0;
}
</style>
{
"id": "uni-popup",
"displayName": "uni-popup 弹出层",
"version": "1.8.4",
"description": " Popup 组件,提供常用的弹层",
"keywords": [
"uni-ui",
"弹出层",
"弹窗",
"popup",
"弹框"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": [
"uni-scss",
"uni-transition"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}
## Popup 弹出层
> **组件名:uni-popup**
> 代码块: `uPopup`
> 关联组件:`uni-transition`
弹出层组件,在应用中弹出一个消息提示窗口、提示框等
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-popup)
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
## 1.0.3(2022-01-21)
- 优化 组件示例
## 1.0.2(2021-11-22)
- 修复 / 符号在 vue 不同版本兼容问题引起的报错问题
## 1.0.1(2021-11-22)
- 修复 vue3中scss语法兼容问题
## 1.0.0(2021-11-18)
- init
@import './styles/index.scss';
{
"id": "uni-scss",
"displayName": "uni-scss 辅助样式",
"version": "1.0.3",
"description": "uni-sass是uni-ui提供的一套全局样式 ,通过一些简单的类名和sass变量,实现简单的页面布局操作,比如颜色、边距、圆角等。",
"keywords": [
"uni-scss",
"uni-ui",
"辅助样式"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"category": [
"JS SDK",
"通用 SDK"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "n",
"联盟": "n"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}
`uni-sass``uni-ui`提供的一套全局样式 ,通过一些简单的类名和`sass`变量,实现简单的页面布局操作,比如颜色、边距、圆角等。
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-sass)
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
\ No newline at end of file
@import './setting/_variables.scss';
@import './setting/_border.scss';
@import './setting/_color.scss';
@import './setting/_space.scss';
@import './setting/_radius.scss';
@import './setting/_text.scss';
@import './setting/_styles.scss';
.uni-border {
border: 1px $uni-border-1 solid;
}
\ No newline at end of file
// TODO 暂时不需要 class ,需要用户使用变量实现 ,如果使用类名其实并不推荐
// @mixin get-styles($k,$c) {
// @if $k == size or $k == weight{
// font-#{$k}:#{$c}
// }@else{
// #{$k}:#{$c}
// }
// }
$uni-ui-color:(
// 主色
primary: $uni-primary,
primary-disable: $uni-primary-disable,
primary-light: $uni-primary-light,
// 辅助色
success: $uni-success,
success-disable: $uni-success-disable,
success-light: $uni-success-light,
warning: $uni-warning,
warning-disable: $uni-warning-disable,
warning-light: $uni-warning-light,
error: $uni-error,
error-disable: $uni-error-disable,
error-light: $uni-error-light,
info: $uni-info,
info-disable: $uni-info-disable,
info-light: $uni-info-light,
// 中性色
main-color: $uni-main-color,
base-color: $uni-base-color,
secondary-color: $uni-secondary-color,
extra-color: $uni-extra-color,
// 背景色
bg-color: $uni-bg-color,
// 边框颜色
border-1: $uni-border-1,
border-2: $uni-border-2,
border-3: $uni-border-3,
border-4: $uni-border-4,
// 黑色
black:$uni-black,
// 白色
white:$uni-white,
// 透明
transparent:$uni-transparent
) !default;
@each $key, $child in $uni-ui-color {
.uni-#{"" + $key} {
color: $child;
}
.uni-#{"" + $key}-bg {
background-color: $child;
}
}
.uni-shadow-sm {
box-shadow: $uni-shadow-sm;
}
.uni-shadow-base {
box-shadow: $uni-shadow-base;
}
.uni-shadow-lg {
box-shadow: $uni-shadow-lg;
}
.uni-mask {
background-color:$uni-mask;
}
@mixin radius($r,$d:null ,$important: false){
$radius-value:map-get($uni-radius, $r) if($important, !important, null);
// Key exists within the $uni-radius variable
@if (map-has-key($uni-radius, $r) and $d){
@if $d == t {
border-top-left-radius:$radius-value;
border-top-right-radius:$radius-value;
}@else if $d == r {
border-top-right-radius:$radius-value;
border-bottom-right-radius:$radius-value;
}@else if $d == b {
border-bottom-left-radius:$radius-value;
border-bottom-right-radius:$radius-value;
}@else if $d == l {
border-top-left-radius:$radius-value;
border-bottom-left-radius:$radius-value;
}@else if $d == tl {
border-top-left-radius:$radius-value;
}@else if $d == tr {
border-top-right-radius:$radius-value;
}@else if $d == br {
border-bottom-right-radius:$radius-value;
}@else if $d == bl {
border-bottom-left-radius:$radius-value;
}
}@else{
border-radius:$radius-value;
}
}
@each $key, $child in $uni-radius {
@if($key){
.uni-radius-#{"" + $key} {
@include radius($key)
}
}@else{
.uni-radius {
@include radius($key)
}
}
}
@each $direction in t, r, b, l,tl, tr, br, bl {
@each $key, $child in $uni-radius {
@if($key){
.uni-radius-#{"" + $direction}-#{"" + $key} {
@include radius($key,$direction,false)
}
}@else{
.uni-radius-#{$direction} {
@include radius($key,$direction,false)
}
}
}
}
@mixin fn($space,$direction,$size,$n) {
@if $n {
#{$space}-#{$direction}: #{$size*$uni-space-root}px
} @else {
#{$space}-#{$direction}: #{-$size*$uni-space-root}px
}
}
@mixin get-styles($direction,$i,$space,$n){
@if $direction == t {
@include fn($space, top,$i,$n);
}
@if $direction == r {
@include fn($space, right,$i,$n);
}
@if $direction == b {
@include fn($space, bottom,$i,$n);
}
@if $direction == l {
@include fn($space, left,$i,$n);
}
@if $direction == x {
@include fn($space, left,$i,$n);
@include fn($space, right,$i,$n);
}
@if $direction == y {
@include fn($space, top,$i,$n);
@include fn($space, bottom,$i,$n);
}
@if $direction == a {
@if $n {
#{$space}:#{$i*$uni-space-root}px;
} @else {
#{$space}:#{-$i*$uni-space-root}px;
}
}
}
@each $orientation in m,p {
$space: margin;
@if $orientation == m {
$space: margin;
} @else {
$space: padding;
}
@for $i from 0 through 16 {
@each $direction in t, r, b, l, x, y, a {
.uni-#{$orientation}#{$direction}-#{$i} {
@include get-styles($direction,$i,$space,true);
}
.uni-#{$orientation}#{$direction}-n#{$i} {
@include get-styles($direction,$i,$space,false);
}
}
}
}
\ No newline at end of file
/* #ifndef APP-NVUE */
$-color-white:#fff;
$-color-black:#000;
@mixin base-style($color) {
color: #fff;
background-color: $color;
border-color: mix($-color-black, $color, 8%);
&:not([hover-class]):active {
background: mix($-color-black, $color, 10%);
border-color: mix($-color-black, $color, 20%);
color: $-color-white;
outline: none;
}
}
@mixin is-color($color) {
@include base-style($color);
&[loading] {
@include base-style($color);
&::before {
margin-right:5px;
}
}
&[disabled] {
&,
&[loading],
&:not([hover-class]):active {
color: $-color-white;
border-color: mix(darken($color,10%), $-color-white);
background-color: mix($color, $-color-white);
}
}
}
@mixin base-plain-style($color) {
color:$color;
background-color: mix($-color-white, $color, 90%);
border-color: mix($-color-white, $color, 70%);
&:not([hover-class]):active {
background: mix($-color-white, $color, 80%);
color: $color;
outline: none;
border-color: mix($-color-white, $color, 50%);
}
}
@mixin is-plain($color){
&[plain] {
@include base-plain-style($color);
&[loading] {
@include base-plain-style($color);
&::before {
margin-right:5px;
}
}
&[disabled] {
&,
&:active {
color: mix($-color-white, $color, 40%);
background-color: mix($-color-white, $color, 90%);
border-color: mix($-color-white, $color, 80%);
}
}
}
}
.uni-btn {
margin: 5px;
color: #393939;
border:1px solid #ccc;
font-size: 16px;
font-weight: 200;
background-color: #F9F9F9;
// TODO 暂时处理边框隐藏一边的问题
overflow: visible;
&::after{
border: none;
}
&:not([type]),&[type=default] {
color: #999;
&[loading] {
background: none;
&::before {
margin-right:5px;
}
}
&[disabled]{
color: mix($-color-white, #999, 60%);
&,
&[loading],
&:active {
color: mix($-color-white, #999, 60%);
background-color: mix($-color-white,$-color-black , 98%);
border-color: mix($-color-white, #999, 85%);
}
}
&[plain] {
color: #999;
background: none;
border-color: $uni-border-1;
&:not([hover-class]):active {
background: none;
color: mix($-color-white, $-color-black, 80%);
border-color: mix($-color-white, $-color-black, 90%);
outline: none;
}
&[disabled]{
&,
&[loading],
&:active {
background: none;
color: mix($-color-white, #999, 60%);
border-color: mix($-color-white, #999, 85%);
}
}
}
}
&:not([hover-class]):active {
color: mix($-color-white, $-color-black, 50%);
}
&[size=mini] {
font-size: 16px;
font-weight: 200;
border-radius: 8px;
}
&.uni-btn-small {
font-size: 14px;
}
&.uni-btn-mini {
font-size: 12px;
}
&.uni-btn-radius {
border-radius: 999px;
}
&[type=primary] {
@include is-color($uni-primary);
@include is-plain($uni-primary)
}
&[type=success] {
@include is-color($uni-success);
@include is-plain($uni-success)
}
&[type=error] {
@include is-color($uni-error);
@include is-plain($uni-error)
}
&[type=warning] {
@include is-color($uni-warning);
@include is-plain($uni-warning)
}
&[type=info] {
@include is-color($uni-info);
@include is-plain($uni-info)
}
}
/* #endif */
@mixin get-styles($k,$c) {
@if $k == size or $k == weight{
font-#{$k}:#{$c}
}@else{
#{$k}:#{$c}
}
}
@each $key, $child in $uni-headings {
/* #ifndef APP-NVUE */
.uni-#{$key} {
@each $k, $c in $child {
@include get-styles($k,$c)
}
}
/* #endif */
/* #ifdef APP-NVUE */
.container .uni-#{$key} {
@each $k, $c in $child {
@include get-styles($k,$c)
}
}
/* #endif */
}
// @use "sass:math";
@import '../tools/functions.scss';
// 间距基础倍数
$uni-space-root: 2 !default;
// 边框半径默认值
$uni-radius-root:5px !default;
$uni-radius: () !default;
// 边框半径断点
$uni-radius: map-deep-merge(
(
0: 0,
// TODO 当前版本暂时不支持 sm 属性
// 'sm': math.div($uni-radius-root, 2),
null: $uni-radius-root,
'lg': $uni-radius-root * 2,
'xl': $uni-radius-root * 6,
'pill': 9999px,
'circle': 50%
),
$uni-radius
);
// 字体家族
$body-font-family: 'Roboto', sans-serif !default;
// 文本
$heading-font-family: $body-font-family !default;
$uni-headings: () !default;
$letterSpacing: -0.01562em;
$uni-headings: map-deep-merge(
(
'h1': (
size: 32px,
weight: 300,
line-height: 50px,
// letter-spacing:-0.01562em
),
'h2': (
size: 28px,
weight: 300,
line-height: 40px,
// letter-spacing: -0.00833em
),
'h3': (
size: 24px,
weight: 400,
line-height: 32px,
// letter-spacing: normal
),
'h4': (
size: 20px,
weight: 400,
line-height: 30px,
// letter-spacing: 0.00735em
),
'h5': (
size: 16px,
weight: 400,
line-height: 24px,
// letter-spacing: normal
),
'h6': (
size: 14px,
weight: 500,
line-height: 18px,
// letter-spacing: 0.0125em
),
'subtitle': (
size: 12px,
weight: 400,
line-height: 20px,
// letter-spacing: 0.00937em
),
'body': (
font-size: 14px,
font-weight: 400,
line-height: 22px,
// letter-spacing: 0.03125em
),
'caption': (
'size': 12px,
'weight': 400,
'line-height': 20px,
// 'letter-spacing': 0.03333em,
// 'text-transform': false
)
),
$uni-headings
);
// 主色
$uni-primary: #2979ff !default;
$uni-primary-disable:lighten($uni-primary,20%) !default;
$uni-primary-light: lighten($uni-primary,25%) !default;
// 辅助色
// 除了主色外的场景色,需要在不同的场景中使用(例如危险色表示危险的操作)。
$uni-success: #18bc37 !default;
$uni-success-disable:lighten($uni-success,20%) !default;
$uni-success-light: lighten($uni-success,25%) !default;
$uni-warning: #f3a73f !default;
$uni-warning-disable:lighten($uni-warning,20%) !default;
$uni-warning-light: lighten($uni-warning,25%) !default;
$uni-error: #e43d33 !default;
$uni-error-disable:lighten($uni-error,20%) !default;
$uni-error-light: lighten($uni-error,25%) !default;
$uni-info: #8f939c !default;
$uni-info-disable:lighten($uni-info,20%) !default;
$uni-info-light: lighten($uni-info,25%) !default;
// 中性色
// 中性色用于文本、背景和边框颜色。通过运用不同的中性色,来表现层次结构。
$uni-main-color: #3a3a3a !default; // 主要文字
$uni-base-color: #6a6a6a !default; // 常规文字
$uni-secondary-color: #909399 !default; // 次要文字
$uni-extra-color: #c7c7c7 !default; // 辅助说明
// 边框颜色
$uni-border-1: #F0F0F0 !default;
$uni-border-2: #EDEDED !default;
$uni-border-3: #DCDCDC !default;
$uni-border-4: #B9B9B9 !default;
// 常规色
$uni-black: #000000 !default;
$uni-white: #ffffff !default;
$uni-transparent: rgba($color: #000000, $alpha: 0) !default;
// 背景色
$uni-bg-color: #f7f7f7 !default;
/* 水平间距 */
$uni-spacing-sm: 8px !default;
$uni-spacing-base: 15px !default;
$uni-spacing-lg: 30px !default;
// 阴影
$uni-shadow-sm:0 0 5px rgba($color: #d8d8d8, $alpha: 0.5) !default;
$uni-shadow-base:0 1px 8px 1px rgba($color: #a5a5a5, $alpha: 0.2) !default;
$uni-shadow-lg:0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5) !default;
// 蒙版
$uni-mask: rgba($color: #000000, $alpha: 0.4) !default;
// 合并 map
@function map-deep-merge($parent-map, $child-map){
$result: $parent-map;
@each $key, $child in $child-map {
$parent-has-key: map-has-key($result, $key);
$parent-value: map-get($result, $key);
$parent-type: type-of($parent-value);
$child-type: type-of($child);
$parent-is-map: $parent-type == map;
$child-is-map: $child-type == map;
@if (not $parent-has-key) or ($parent-type != $child-type) or (not ($parent-is-map and $child-is-map)){
$result: map-merge($result, ( $key: $child ));
}@else {
$result: map-merge($result, ( $key: map-deep-merge($parent-value, $child) ));
}
}
@return $result;
};
// 间距基础倍数
$uni-space-root: 2;
// 边框半径默认值
$uni-radius-root:5px;
// 主色
$uni-primary: #2979ff;
// 辅助色
$uni-success: #4cd964;
// 警告色
$uni-warning: #f0ad4e;
// 错误色
$uni-error: #dd524d;
// 描述色
$uni-info: #909399;
// 中性色
$uni-main-color: #303133;
$uni-base-color: #606266;
$uni-secondary-color: #909399;
$uni-extra-color: #C0C4CC;
// 背景色
$uni-bg-color: #f5f5f5;
// 边框颜色
$uni-border-1: #DCDFE6;
$uni-border-2: #E4E7ED;
$uni-border-3: #EBEEF5;
$uni-border-4: #F2F6FC;
// 常规色
$uni-black: #000000;
$uni-white: #ffffff;
$uni-transparent: rgba($color: #000000, $alpha: 0);
@import './styles/setting/_variables.scss';
// 间距基础倍数
$uni-space-root: 2;
// 边框半径默认值
$uni-radius-root:5px;
// 主色
$uni-primary: #2979ff;
$uni-primary-disable:mix(#fff,$uni-primary,50%);
$uni-primary-light: mix(#fff,$uni-primary,80%);
// 辅助色
// 除了主色外的场景色,需要在不同的场景中使用(例如危险色表示危险的操作)。
$uni-success: #18bc37;
$uni-success-disable:mix(#fff,$uni-success,50%);
$uni-success-light: mix(#fff,$uni-success,80%);
$uni-warning: #f3a73f;
$uni-warning-disable:mix(#fff,$uni-warning,50%);
$uni-warning-light: mix(#fff,$uni-warning,80%);
$uni-error: #e43d33;
$uni-error-disable:mix(#fff,$uni-error,50%);
$uni-error-light: mix(#fff,$uni-error,80%);
$uni-info: #8f939c;
$uni-info-disable:mix(#fff,$uni-info,50%);
$uni-info-light: mix(#fff,$uni-info,80%);
// 中性色
// 中性色用于文本、背景和边框颜色。通过运用不同的中性色,来表现层次结构。
$uni-main-color: #3a3a3a; // 主要文字
$uni-base-color: #6a6a6a; // 常规文字
$uni-secondary-color: #909399; // 次要文字
$uni-extra-color: #c7c7c7; // 辅助说明
// 边框颜色
$uni-border-1: #F0F0F0;
$uni-border-2: #EDEDED;
$uni-border-3: #DCDCDC;
$uni-border-4: #B9B9B9;
// 常规色
$uni-black: #000000;
$uni-white: #ffffff;
$uni-transparent: rgba($color: #000000, $alpha: 0);
// 背景色
$uni-bg-color: #f7f7f7;
/* 水平间距 */
$uni-spacing-sm: 8px;
$uni-spacing-base: 15px;
$uni-spacing-lg: 30px;
// 阴影
$uni-shadow-sm:0 0 5px rgba($color: #d8d8d8, $alpha: 0.5);
$uni-shadow-base:0 1px 8px 1px rgba($color: #a5a5a5, $alpha: 0.2);
$uni-shadow-lg:0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5);
// 蒙版
$uni-mask: rgba($color: #000000, $alpha: 0.4);
## 1.3.2(2023-05-04)
- 修复 NVUE 平台报错的问题
## 1.3.1(2021-11-23)
- 修复 init 方法初始化问题
## 1.3.0(2021-11-19)
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-transition](https://uniapp.dcloud.io/component/uniui/uni-transition)
## 1.2.1(2021-09-27)
- 修复 init 方法不生效的 Bug
## 1.2.0(2021-07-30)
- 组件兼容 vue3,如何创建 vue3 项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.1.1(2021-05-12)
- 新增 示例地址
- 修复 示例项目缺少组件的 Bug
## 1.1.0(2021-04-22)
- 新增 通过方法自定义动画
- 新增 custom-class 非 NVUE 平台支持自定义 class 定制样式
- 优化 动画触发逻辑,使动画更流畅
- 优化 支持单独的动画类型
- 优化 文档示例
## 1.0.2(2021-02-05)
- 调整为 uni_modules 目录规范
// const defaultOption = {
// duration: 300,
// timingFunction: 'linear',
// delay: 0,
// transformOrigin: '50% 50% 0'
// }
// #ifdef APP-NVUE
const nvueAnimation = uni.requireNativePlugin('animation')
// #endif
class MPAnimation {
constructor(options, _this) {
this.options = options
// 在iOS10+QQ小程序平台下,传给原生的对象一定是个普通对象而不是Proxy对象,否则会报parameter should be Object instead of ProxyObject的错误
this.animation = uni.createAnimation({
...options
})
this.currentStepAnimates = {}
this.next = 0
this.$ = _this
}
_nvuePushAnimates(type, args) {
let aniObj = this.currentStepAnimates[this.next]
let styles = {}
if (!aniObj) {
styles = {
styles: {},
config: {}
}
} else {
styles = aniObj
}
if (animateTypes1.includes(type)) {
if (!styles.styles.transform) {
styles.styles.transform = ''
}
let unit = ''
if(type === 'rotate'){
unit = 'deg'
}
styles.styles.transform += `${type}(${args+unit}) `
} else {
styles.styles[type] = `${args}`
}
this.currentStepAnimates[this.next] = styles
}
_animateRun(styles = {}, config = {}) {
let ref = this.$.$refs['ani'].ref
if (!ref) return
return new Promise((resolve, reject) => {
nvueAnimation.transition(ref, {
styles,
...config
}, res => {
resolve()
})
})
}
_nvueNextAnimate(animates, step = 0, fn) {
let obj = animates[step]
if (obj) {
let {
styles,
config
} = obj
this._animateRun(styles, config).then(() => {
step += 1
this._nvueNextAnimate(animates, step, fn)
})
} else {
this.currentStepAnimates = {}
typeof fn === 'function' && fn()
this.isEnd = true
}
}
step(config = {}) {
// #ifndef APP-NVUE
this.animation.step(config)
// #endif
// #ifdef APP-NVUE
this.currentStepAnimates[this.next].config = Object.assign({}, this.options, config)
this.currentStepAnimates[this.next].styles.transformOrigin = this.currentStepAnimates[this.next].config.transformOrigin
this.next++
// #endif
return this
}
run(fn) {
// #ifndef APP-NVUE
this.$.animationData = this.animation.export()
this.$.timer = setTimeout(() => {
typeof fn === 'function' && fn()
}, this.$.durationTime)
// #endif
// #ifdef APP-NVUE
this.isEnd = false
let ref = this.$.$refs['ani'] && this.$.$refs['ani'].ref
if(!ref) return
this._nvueNextAnimate(this.currentStepAnimates, 0, fn)
this.next = 0
// #endif
}
}
const animateTypes1 = ['matrix', 'matrix3d', 'rotate', 'rotate3d', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scale3d',
'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'translate', 'translate3d', 'translateX', 'translateY',
'translateZ'
]
const animateTypes2 = ['opacity', 'backgroundColor']
const animateTypes3 = ['width', 'height', 'left', 'right', 'top', 'bottom']
animateTypes1.concat(animateTypes2, animateTypes3).forEach(type => {
MPAnimation.prototype[type] = function(...args) {
// #ifndef APP-NVUE
this.animation[type](...args)
// #endif
// #ifdef APP-NVUE
this._nvuePushAnimates(type, args)
// #endif
return this
}
})
export function createAnimation(option, _this) {
if(!_this) return
clearTimeout(_this.timer)
return new MPAnimation(option, _this)
}
<template>
<!-- #ifndef APP-NVUE -->
<view v-show="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view>
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<view v-if="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view>
<!-- #endif -->
</template>
<script>
import { createAnimation } from './createAnimation'
/**
* Transition 过渡动画
* @description 简单过渡动画组件
* @tutorial https://ext.dcloud.net.cn/plugin?id=985
* @property {Boolean} show = [false|true] 控制组件显示或隐藏
* @property {Array|String} modeClass = [fade|slide-top|slide-right|slide-bottom|slide-left|zoom-in|zoom-out] 过渡动画类型
* @value fade 渐隐渐出过渡
* @value slide-top 由上至下过渡
* @value slide-right 由右至左过渡
* @value slide-bottom 由下至上过渡
* @value slide-left 由左至右过渡
* @value zoom-in 由小到大过渡
* @value zoom-out 由大到小过渡
* @property {Number} duration 过渡动画持续时间
* @property {Object} styles 组件样式,同 css 样式,注意带’-‘连接符的属性需要使用小驼峰写法如:`backgroundColor:red`
*/
export default {
name: 'uniTransition',
emits:['click','change'],
props: {
show: {
type: Boolean,
default: false
},
modeClass: {
type: [Array, String],
default() {
return 'fade'
}
},
duration: {
type: Number,
default: 300
},
styles: {
type: Object,
default() {
return {}
}
},
customClass:{
type: String,
default: ''
},
onceRender:{
type:Boolean,
default:false
},
},
data() {
return {
isShow: false,
transform: '',
opacity: 1,
animationData: {},
durationTime: 300,
config: {}
}
},
watch: {
show: {
handler(newVal) {
if (newVal) {
this.open()
} else {
// 避免上来就执行 close,导致动画错乱
if (this.isShow) {
this.close()
}
}
},
immediate: true
}
},
computed: {
// 生成样式数据
stylesObject() {
let styles = {
...this.styles,
'transition-duration': this.duration / 1000 + 's'
}
let transform = ''
for (let i in styles) {
let line = this.toLine(i)
transform += line + ':' + styles[i] + ';'
}
return transform
},
// 初始化动画条件
transformStyles() {
return 'transform:' + this.transform + ';' + 'opacity:' + this.opacity + ';' + this.stylesObject
}
},
created() {
// 动画默认配置
this.config = {
duration: this.duration,
timingFunction: 'ease',
transformOrigin: '50% 50%',
delay: 0
}
this.durationTime = this.duration
},
methods: {
/**
* ref 触发 初始化动画
*/
init(obj = {}) {
if (obj.duration) {
this.durationTime = obj.duration
}
this.animation = createAnimation(Object.assign(this.config, obj),this)
},
/**
* 点击组件触发回调
*/
onClick() {
this.$emit('click', {
detail: this.isShow
})
},
/**
* ref 触发 动画分组
* @param {Object} obj
*/
step(obj, config = {}) {
if (!this.animation) return
for (let i in obj) {
try {
if(typeof obj[i] === 'object'){
this.animation[i](...obj[i])
}else{
this.animation[i](obj[i])
}
} catch (e) {
console.error(`方法 ${i} 不存在`)
}
}
this.animation.step(config)
return this
},
/**
* ref 触发 执行动画
*/
run(fn) {
if (!this.animation) return
this.animation.run(fn)
},
// 开始过度动画
open() {
clearTimeout(this.timer)
this.transform = ''
this.isShow = true
let { opacity, transform } = this.styleInit(false)
if (typeof opacity !== 'undefined') {
this.opacity = opacity
}
this.transform = transform
// 确保动态样式已经生效后,执行动画,如果不加 nextTick ,会导致 wx 动画执行异常
this.$nextTick(() => {
// TODO 定时器保证动画完全执行,目前有些问题,后面会取消定时器
this.timer = setTimeout(() => {
this.animation = createAnimation(this.config, this)
this.tranfromInit(false).step()
this.animation.run()
this.$emit('change', {
detail: this.isShow
})
}, 20)
})
},
// 关闭过度动画
close(type) {
if (!this.animation) return
this.tranfromInit(true)
.step()
.run(() => {
this.isShow = false
this.animationData = null
this.animation = null
let { opacity, transform } = this.styleInit(false)
this.opacity = opacity || 1
this.transform = transform
this.$emit('change', {
detail: this.isShow
})
})
},
// 处理动画开始前的默认样式
styleInit(type) {
let styles = {
transform: ''
}
let buildStyle = (type, mode) => {
if (mode === 'fade') {
styles.opacity = this.animationType(type)[mode]
} else {
styles.transform += this.animationType(type)[mode] + ' '
}
}
if (typeof this.modeClass === 'string') {
buildStyle(type, this.modeClass)
} else {
this.modeClass.forEach(mode => {
buildStyle(type, mode)
})
}
return styles
},
// 处理内置组合动画
tranfromInit(type) {
let buildTranfrom = (type, mode) => {
let aniNum = null
if (mode === 'fade') {
aniNum = type ? 0 : 1
} else {
aniNum = type ? '-100%' : '0'
if (mode === 'zoom-in') {
aniNum = type ? 0.8 : 1
}
if (mode === 'zoom-out') {
aniNum = type ? 1.2 : 1
}
if (mode === 'slide-right') {
aniNum = type ? '100%' : '0'
}
if (mode === 'slide-bottom') {
aniNum = type ? '100%' : '0'
}
}
this.animation[this.animationMode()[mode]](aniNum)
}
if (typeof this.modeClass === 'string') {
buildTranfrom(type, this.modeClass)
} else {
this.modeClass.forEach(mode => {
buildTranfrom(type, mode)
})
}
return this.animation
},
animationType(type) {
return {
fade: type ? 1 : 0,
'slide-top': `translateY(${type ? '0' : '-100%'})`,
'slide-right': `translateX(${type ? '0' : '100%'})`,
'slide-bottom': `translateY(${type ? '0' : '100%'})`,
'slide-left': `translateX(${type ? '0' : '-100%'})`,
'zoom-in': `scaleX(${type ? 1 : 0.8}) scaleY(${type ? 1 : 0.8})`,
'zoom-out': `scaleX(${type ? 1 : 1.2}) scaleY(${type ? 1 : 1.2})`
}
},
// 内置动画类型与实际动画对应字典
animationMode() {
return {
fade: 'opacity',
'slide-top': 'translateY',
'slide-right': 'translateX',
'slide-bottom': 'translateY',
'slide-left': 'translateX',
'zoom-in': 'scale',
'zoom-out': 'scale'
}
},
// 驼峰转中横线
toLine(name) {
return name.replace(/([A-Z])/g, '-$1').toLowerCase()
}
}
}
</script>
<style></style>
{
"id": "uni-transition",
"displayName": "uni-transition 过渡动画",
"version": "1.3.2",
"description": "元素的简单过渡动画",
"keywords": [
"uni-ui",
"uniui",
"动画",
"过渡",
"过渡动画"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": ["uni-scss"],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}
\ No newline at end of file
## Transition 过渡动画
> **组件名:uni-transition**
> 代码块: `uTransition`
元素过渡动画
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-transition)
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册