feat: 微信公众号登录插件-未重定向 (#1405)
Reviewed-on: daoyoucloud/tachybase#1405 Reviewed-by: sealday <zhanglin@daoyoucloud.com> Co-authored-by: luliangqiang <2650321653@qq.com> Co-committed-by: luliangqiang <2650321653@qq.com>
This commit is contained in:
parent
7e02b5a71c
commit
9261719589
@ -1,12 +1,21 @@
|
||||
{
|
||||
"name": "@tachybase/plugin-wechat-official-account",
|
||||
"version": "0.21.79",
|
||||
"version": "0.21.73",
|
||||
"keywords": [
|
||||
"Authentication"
|
||||
],
|
||||
"main": "dist/server/index.js",
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^5.3.7",
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"antd": "^5.18.3",
|
||||
"axios": "^1.7.2",
|
||||
"bcrypt": "^5.1.1",
|
||||
"body-parser": "^1.20.2",
|
||||
"crypto": "^1.0.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"koa-bodyparser": "^4.3.0",
|
||||
"qrcode.react": "^3.1.0",
|
||||
"react-router-dom": "^6.11.2",
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
|
@ -0,0 +1,139 @@
|
||||
// --4.1 直接插入缓存
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { SchemaComponent, useAPIClient, useApp } from '@tachybase/client';
|
||||
|
||||
import { CopyOutlined, LoginOutlined } from '@ant-design/icons';
|
||||
import { Button, Input, message, Modal, Spin, Typography } from 'antd';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { useTranslation } from './locale';
|
||||
|
||||
export const AdminSettingsForm = () => {
|
||||
const { t } = useTranslation();
|
||||
const app = useApp();
|
||||
const redirectUrl = 'https://lu.dev.daoyoucloud.com/api/wechat@handleOAuthCallback'; // 设置回调url
|
||||
|
||||
const onCopy = (text) => {
|
||||
navigator.clipboard.writeText(text);
|
||||
message.success(t('Copied'));
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SchemaComponent
|
||||
scope={{ t }}
|
||||
schema={{
|
||||
type: 'object',
|
||||
properties: {
|
||||
public: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
autoSignup: {
|
||||
'x-decorator': 'FormItem',
|
||||
type: 'boolean',
|
||||
title: '{{t("Sign up automatically when the user does not exist")}}',
|
||||
'x-component': 'Checkbox',
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
wechat: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
appId: {
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: '{{t("WeChat AppID")}}',
|
||||
},
|
||||
appSecret: {
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: '{{t("WeChat AppSecret")}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<Typography.Title level={5} style={{ fontSize: '14px' }}>
|
||||
{t('Redirect URL')}:
|
||||
</Typography.Title>
|
||||
<Input value={redirectUrl} disabled addonBefore={<CopyOutlined onClick={() => onCopy(redirectUrl)} />} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const SignInButton = ({ authenticator }) => {
|
||||
const [href, setHref] = useState();
|
||||
const [qrCodeVisible, setQrCodeVisible] = useState(false);
|
||||
const [qrCodeUrl, setQrCodeUrl] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const api = useAPIClient();
|
||||
const location = useLocation();
|
||||
const urlSearchParams = new URLSearchParams(location.search);
|
||||
const redirect = urlSearchParams.get('redirect');
|
||||
|
||||
useEffect(() => {
|
||||
if (href) window.location.href = href;
|
||||
}, [href]);
|
||||
|
||||
useEffect(() => {
|
||||
const authenticatorName = urlSearchParams.get('authenticator');
|
||||
const error = urlSearchParams.get('error');
|
||||
if (authenticatorName === authenticator.name && error) {
|
||||
message.error(t(error));
|
||||
}
|
||||
}, [authenticator.name, t, urlSearchParams]);
|
||||
|
||||
const onClick = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const { data } = await api.request({
|
||||
method: 'post',
|
||||
url: 'https://lu.dev.daoyoucloud.com/api/wechat@generateQrCode', // 生成二维码
|
||||
headers: { 'X-Authenticator': authenticator.name },
|
||||
data: { redirect },
|
||||
});
|
||||
|
||||
console.log('data.data.data.url:', data.data.data.url);
|
||||
|
||||
const url = data.data.data.url;
|
||||
if (url) {
|
||||
setQrCodeUrl(url);
|
||||
setQrCodeVisible(true);
|
||||
} else {
|
||||
console.error('未能生成二维码URL');
|
||||
}
|
||||
} catch (error) {
|
||||
message.error(t('Failed to generate QR code'));
|
||||
console.error('二维码生成失败:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 显示二维码
|
||||
return (
|
||||
<>
|
||||
<Button block icon={<LoginOutlined />} shape="round" onClick={onClick} loading={loading}>
|
||||
{authenticator.title || 'WeChat 公众号登录'}
|
||||
</Button>
|
||||
<Modal
|
||||
title="Scan QR Code to Login"
|
||||
style={{ textAlign: 'center' }}
|
||||
visible={qrCodeVisible}
|
||||
footer={null}
|
||||
onCancel={() => setQrCodeVisible(false)}
|
||||
>
|
||||
{loading ? <Spin /> : <img src={qrCodeUrl} alt="QR Code" style={{ width: '100%' }} />}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,17 +1,13 @@
|
||||
import React from 'react';
|
||||
import { Plugin, useRequest } from '@tachybase/client';
|
||||
import { Plugin } from '@tachybase/client';
|
||||
import PluginAuthClient from '@tachybase/plugin-auth/client';
|
||||
|
||||
import { CustomAuthLayout } from './AuthLayout';
|
||||
import { authType } from '../constants';
|
||||
import { AdminSettingsForm, SignInButton } from './components';
|
||||
|
||||
export class PluginReplacePageClient extends Plugin {
|
||||
async afterAdd() {
|
||||
// await this.app.pm.add()
|
||||
export class PluginWeCahtOfficialAccount extends Plugin {
|
||||
async load() {
|
||||
this.app.pm.get(PluginAuthClient).registerType(authType, { components: { SignInButton, AdminSettingsForm } });
|
||||
}
|
||||
|
||||
async beforeLoad() {}
|
||||
|
||||
// You can get and modify the app instance here
|
||||
async load() {}
|
||||
}
|
||||
|
||||
export default PluginReplacePageClient;
|
||||
export default PluginWeCahtOfficialAccount;
|
||||
|
@ -0,0 +1,7 @@
|
||||
import { useTranslation as useT } from '@tachybase/client';
|
||||
|
||||
import { namespace } from '../constants';
|
||||
|
||||
export function useTranslation() {
|
||||
return useT([namespace, 'client'], { nsMode: 'fallback' });
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
export const authType = '微信公众号';
|
||||
export const namespace = '微信公众号';
|
@ -0,0 +1,39 @@
|
||||
import { Context, Next } from '@tachybase/actions';
|
||||
import { AppSupervisor } from '@tachybase/server';
|
||||
|
||||
export const getAuthUrl = async (ctx: Context, next: Next) => {
|
||||
const { redirect } = ctx.action.params.values;
|
||||
const url = await ctx.auth.getAuthUrl(redirect);
|
||||
ctx.body = { url };
|
||||
await next();
|
||||
};
|
||||
|
||||
export const redirect = async (ctx: Context, next: Next) => {
|
||||
const { code, state } = ctx.request.query;
|
||||
|
||||
const stateString = Array.isArray(state) ? state[0] : state;
|
||||
const search = new URLSearchParams(stateString);
|
||||
|
||||
const authenticator = search.get('name');
|
||||
const appName = search.get('app');
|
||||
const redirect = search.get('redirect') || '/admin';
|
||||
let prefix = process.env.APP_PUBLIC_PATH || '';
|
||||
if (appName && appName !== 'main') {
|
||||
const appSupervisor = AppSupervisor.getInstance();
|
||||
if (appSupervisor?.runningMode !== 'single') {
|
||||
prefix += `apps/${appName}`;
|
||||
}
|
||||
}
|
||||
const auth = await ctx.app.authManager.get(authenticator, ctx);
|
||||
if (prefix.endsWith('/')) {
|
||||
prefix = prefix.slice(0, -1);
|
||||
}
|
||||
try {
|
||||
const { token } = await auth.signIn();
|
||||
ctx.redirect(`${prefix}${redirect}?authenticator=${authenticator}&token=${token}`);
|
||||
} catch (error) {
|
||||
ctx.logger.error('Work auth error', { error });
|
||||
ctx.redirect(`${prefix}/signin?redirect=${redirect}&authenticator=${authenticator}&error=${error.message}`);
|
||||
}
|
||||
await next();
|
||||
};
|
@ -1,22 +1,78 @@
|
||||
// --10、解决数据库注册插入问题
|
||||
import crypto from 'crypto';
|
||||
import { BasicAuth } from '@tachybase/plugin-auth';
|
||||
import { Plugin } from '@tachybase/server';
|
||||
|
||||
import axios from 'axios';
|
||||
import xml2js from 'xml2js';
|
||||
|
||||
import { authType } from '../constants';
|
||||
|
||||
export class PluginReplacePageServer extends Plugin {
|
||||
// 从数据库查询cofig表
|
||||
async config(): Promise<any> {
|
||||
// 从数据库查询config表
|
||||
async config() {
|
||||
const repo = this.app.db.getRepository('config');
|
||||
const config = (await repo.findById(1)) as any;
|
||||
return config.get(); // get()返回具体的data
|
||||
const config = await repo.findById(1);
|
||||
return config.get();
|
||||
}
|
||||
|
||||
// 从数据库查询message表
|
||||
async message(): Promise<any[]> {
|
||||
async message() {
|
||||
const repo = this.app.db.getRepository('message');
|
||||
const messages = (await repo.find()) as any;
|
||||
return messages; // 返回数组Type
|
||||
const messages = await repo.find();
|
||||
return messages;
|
||||
}
|
||||
|
||||
// 从数据库查询menu表
|
||||
async menu() {
|
||||
const repo = this.app.db.getRepository('menu');
|
||||
const menus = await repo.find();
|
||||
|
||||
// 将数据库中的数据构建成菜单结构
|
||||
const menuStructure = { button: [] };
|
||||
|
||||
menus.forEach((menu) => {
|
||||
if (!menu.button) {
|
||||
// 一级菜单
|
||||
if (menuStructure.button.length < 3) {
|
||||
if (Buffer.byteLength(menu.name, 'utf8') <= 16) {
|
||||
menuStructure.button.push({
|
||||
type: menu.type,
|
||||
name: menu.name,
|
||||
key: menu.key || undefined,
|
||||
url: menu.url || undefined,
|
||||
sub_button: [],
|
||||
});
|
||||
} else {
|
||||
console.warn(`一级菜单名称 "${menu.name}" 超过了 16 个字节的限制`);
|
||||
}
|
||||
} else {
|
||||
console.warn('超过了一级菜单的数量限制');
|
||||
}
|
||||
} else {
|
||||
// 二级菜单
|
||||
const parentMenu = menuStructure.button.find((btn) => btn.name === menu.button);
|
||||
if (parentMenu) {
|
||||
if (parentMenu.sub_button.length < 5) {
|
||||
if (Buffer.byteLength(menu.name, 'utf8') <= 40) {
|
||||
parentMenu.sub_button.push({
|
||||
type: menu.type,
|
||||
name: menu.name,
|
||||
key: menu.key || undefined,
|
||||
url: menu.url || undefined,
|
||||
});
|
||||
} else {
|
||||
console.warn(`二级菜单名称 "${menu.name}" 超过了 40 个字节的限制`);
|
||||
}
|
||||
} else {
|
||||
console.warn('超过了二级菜单的数量限制');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log('-------------', JSON.stringify(menuStructure, null, 2));
|
||||
return menuStructure;
|
||||
}
|
||||
|
||||
// 验证Wechat平台的请求签名
|
||||
@ -35,8 +91,12 @@ export class PluginReplacePageServer extends Plugin {
|
||||
const { appid, appsecret } = await this.config(); // 使用config方法 查询config表
|
||||
const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${appsecret}`;
|
||||
|
||||
console.log(`Fetching access token from URL: ${url}`);
|
||||
console.log(`App ID: ${appid}, App Secret: ${appsecret}`);
|
||||
|
||||
try {
|
||||
const response = await axios.get(url);
|
||||
console.log('Access token response:', response.data);
|
||||
return response.data.access_token;
|
||||
} catch (error) {
|
||||
console.error('Error getting access token:', error);
|
||||
@ -44,9 +104,33 @@ export class PluginReplacePageServer extends Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
// 生成OAuth授权URL
|
||||
async generateAuthUrl() {
|
||||
const { appid } = await this.config(); // 获取appid
|
||||
const redirectUri = encodeURIComponent('https://lu.dev.daoyoucloud.com/api/wechat@handleOAuthCallback');
|
||||
const scope = 'snsapi_userinfo'; // 获取用户信息的权限
|
||||
const state = 'STATE'; // 自定义的state参数
|
||||
|
||||
const authUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${redirectUri}&response_type=code&scope=${scope}&state=${state}#wechat_redirect`;
|
||||
return authUrl;
|
||||
}
|
||||
|
||||
// 生成二维码URL
|
||||
async generateQrCode() {
|
||||
const authUrl = await this.generateAuthUrl();
|
||||
if (!authUrl) {
|
||||
console.error('Failed to generate OAuth URL');
|
||||
return null;
|
||||
}
|
||||
|
||||
const qrCodeUrl = `https://api.qrserver.com/v1/create-qr-code/?data=${encodeURIComponent(authUrl)}&size=300x300`;
|
||||
return qrCodeUrl;
|
||||
}
|
||||
|
||||
// 创建菜单
|
||||
async createCustomMenu() {
|
||||
const accessToken = await this.getAccessToken(); // 获取Wechat Token
|
||||
const menu = await this.menu(); // 获取菜单数据
|
||||
|
||||
if (!accessToken) {
|
||||
console.error('Failed to get access token');
|
||||
@ -54,35 +138,6 @@ export class PluginReplacePageServer extends Plugin {
|
||||
}
|
||||
|
||||
const url = `https://api.weixin.qq.com/cgi-bin/menu/create?access_token=${accessToken}`;
|
||||
const menu = {
|
||||
button: [
|
||||
{
|
||||
type: 'view',
|
||||
name: '音乐',
|
||||
url: 'https://music.163.com/', // 确保这个 URL 不超过 256 个字符
|
||||
},
|
||||
{
|
||||
type: 'view',
|
||||
name: '官网',
|
||||
url: 'https://daoyoucloud.com/',
|
||||
},
|
||||
{
|
||||
name: '服务',
|
||||
sub_button: [
|
||||
{
|
||||
type: 'view',
|
||||
name: '正在开发中...',
|
||||
url: 'https://daoyoucloud.com/coming-soon',
|
||||
},
|
||||
{
|
||||
type: 'view',
|
||||
name: '开发者管理',
|
||||
url: 'https://lu.dev.daoyoucloud.com/',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// 若通过accessToken验证,则发送Wechat请求,创建菜单
|
||||
try {
|
||||
@ -93,7 +148,7 @@ export class PluginReplacePageServer extends Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
// Wechat从用户这里获取签名, 若通过则允许后续交互
|
||||
// Wechat从用户这里获取签名,若通过则允许后续交互
|
||||
async get(ctx) {
|
||||
const { query } = ctx.request;
|
||||
if (await this.checkSignature(query)) {
|
||||
@ -114,10 +169,9 @@ export class PluginReplacePageServer extends Plugin {
|
||||
}
|
||||
|
||||
const messages = await this.message(); // 调用当前对象的 message方法
|
||||
// const keywordsList = keywords.map(keyword => keyword.keywords).join(`${keywords}`);
|
||||
const messagesList = messages.map((message, index) => `${index + 1}、 ${message.key}`).join('\n');
|
||||
const messagesList = messages.map((message) => `${message.id}、 ${message.key}`).join('\n');
|
||||
|
||||
let replyContent = `您好,欢迎关注道有云网络科技有限公司!您可以输入下面关键字来获取您要了解的信息!\n(输入序号即可):\n${messagesList}`; // 默认回复内容
|
||||
let replyContent = `您好,欢迎关注道有云网络科技有限公司!您可以输入下面关键字来获取您要了解的信息!\n(输入序号或关键字):\n${messagesList}`;
|
||||
|
||||
// 解析 POST 请求的 XML 格式消息体
|
||||
xml2js.parseString(body, { explicitArray: false }, (err, result) => {
|
||||
@ -131,14 +185,28 @@ export class PluginReplacePageServer extends Plugin {
|
||||
const msg = result.xml;
|
||||
let replyMessage; // 处理用户输入后的回复结果
|
||||
|
||||
if (msg.MsgType === 'text') {
|
||||
if (msg.MsgType === 'event' && msg.Event === 'SCAN') {
|
||||
// 处理扫码事件
|
||||
replyContent = '扫码成功,欢迎您!';
|
||||
replyMessage = `
|
||||
<xml>
|
||||
<ToUserName><![CDATA[${msg.FromUserName}]]></ToUserName>
|
||||
<FromUserName><![CDATA[${msg.ToUserName}]]></FromUserName>
|
||||
<CreateTime>${Math.floor(Date.now() / 1000)}</CreateTime>
|
||||
<MsgType><![CDATA[text]]></MsgType>
|
||||
<Content><![CDATA[${replyContent}]]></Content>
|
||||
</xml>
|
||||
`;
|
||||
} else if (msg.MsgType === 'text') {
|
||||
// 处理文本信息
|
||||
const valueById = messages.find((message) => msg.Content.includes(message.id.toString()));
|
||||
const valueByKey = messages.find((message) => msg.Content.includes(message.key));
|
||||
|
||||
// 匹配用户输入的关键字和message表里的messages字段是否一致,一致则 filter()出一个数组
|
||||
const values = messages.filter((message) => msg.Content.includes(message.id));
|
||||
if (values?.length > 0) {
|
||||
// 问号? 异常处理
|
||||
replyContent = values[0].value;
|
||||
// 异常处理
|
||||
if (valueById) {
|
||||
replyContent = valueById.value;
|
||||
} else if (valueByKey) {
|
||||
replyContent = valueByKey.value;
|
||||
}
|
||||
|
||||
replyMessage = `
|
||||
@ -180,36 +248,213 @@ export class PluginReplacePageServer extends Plugin {
|
||||
});
|
||||
}
|
||||
|
||||
// 加载
|
||||
// 处理OAuth回调
|
||||
async handleOAuthCallback(ctx) {
|
||||
const { code } = ctx.query;
|
||||
console.log('code---:', code);
|
||||
|
||||
if (!code) {
|
||||
ctx.status = 400;
|
||||
ctx.body = 'Authorization code not found';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { appid, appsecret } = await this.config();
|
||||
console.log('appid---:', appid);
|
||||
console.log('appsecret---:', appsecret);
|
||||
// 获取 access_token 和 openid
|
||||
const tokenUrl = `https://api.weixin.qq.com/sns/oauth2/access_token?appid=${appid}&secret=${appsecret}&code=${code}&grant_type=authorization_code`;
|
||||
|
||||
const tokenResponse = await axios.get(tokenUrl);
|
||||
const { access_token, openid } = tokenResponse.data;
|
||||
console.log('access_token---:', access_token, '\n openid---:', openid);
|
||||
|
||||
if (!access_token || !openid) {
|
||||
throw new Error('Failed to get access token or openid');
|
||||
}
|
||||
|
||||
const userInfoUrl = `https://api.weixin.qq.com/sns/userinfo?access_token=${access_token}&openid=${openid}&lang=zh_CN`;
|
||||
// 通过tokenUrl 获取用户message
|
||||
const userInfoResponse = await axios.get(userInfoUrl);
|
||||
|
||||
// 用户信息
|
||||
const userInfo = userInfoResponse.data;
|
||||
console.log('用户信息:', userInfo);
|
||||
|
||||
// 检查数据库中是否存在该用户
|
||||
const userRepo = this.app.db.getRepository('users');
|
||||
const existingUser = await userRepo.findOne({
|
||||
filter: {
|
||||
username: openid,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingUser) {
|
||||
// 如果用户不存在,则插入新用户
|
||||
await userRepo.create({
|
||||
values: {
|
||||
username: openid.substring(0, 6),
|
||||
password: openid,
|
||||
nickname: userInfo.nickname,
|
||||
},
|
||||
});
|
||||
console.log('新用户已创建:', userInfo.nickname);
|
||||
} else {
|
||||
// 如果用户存在,直接登录
|
||||
console.log('用户已存在:', existingUser.nickname);
|
||||
}
|
||||
|
||||
// 这里可以设置登录态或返回登录成功的信息
|
||||
ctx.body = '完成注册,登录成功!';
|
||||
// 返回手机端页面
|
||||
// ctx.redirect('https://lu.dev.daoyoucloud.com/admin/4tr7gnzlwni');
|
||||
} catch (error) {
|
||||
console.error('Error handling OAuth callback:', error);
|
||||
ctx.status = 500;
|
||||
ctx.body = 'Server Error';
|
||||
// ctx.body = '您已存在账户,登录成功!';
|
||||
}
|
||||
}
|
||||
|
||||
// load() 生命周期函数,在插件加载时执行
|
||||
async load() {
|
||||
// load() 生命周期函数,在插件加载时执行
|
||||
// 监听 message 表的 afterCreate 事件
|
||||
this.app.db.on('message.afterCreate', this.handleMessageCreate.bind(this));
|
||||
// 监听 menu 表的 afterUpdate 事件
|
||||
this.app.db.on('menu.afterUpdate', this.handleMenuUpdate.bind(this));
|
||||
|
||||
// 注册认证(前端显示按钮)
|
||||
this.app.authManager.registerTypes(authType, {
|
||||
auth: BasicAuth,
|
||||
});
|
||||
|
||||
// 定义资源动作
|
||||
this.app.resourcer.define({
|
||||
name: 'wechat',
|
||||
actions: {
|
||||
// 测试接口
|
||||
test: async (ctx) => {
|
||||
ctx.withoutDataWrapping = true;
|
||||
ctx.body = 'Hello World!';
|
||||
console.log('Hello World!');
|
||||
},
|
||||
// 创建菜单接口
|
||||
createMenu: async (ctx) => {
|
||||
await this.createCustomMenu();
|
||||
console.log('Custom menu created successfully.');
|
||||
ctx.body = 'success';
|
||||
},
|
||||
// Wechat 消息处理接口/服务器URL配置接口
|
||||
handler: async (ctx) => {
|
||||
ctx.withoutDataWrapping = true;
|
||||
if (ctx.request.method.toLowerCase() === 'get') {
|
||||
//toLowerCase()转换字符串为小写
|
||||
await this.get(ctx);
|
||||
} else {
|
||||
await this.post(ctx);
|
||||
}
|
||||
},
|
||||
// 生成二维码接口
|
||||
generateQrCode: async (ctx) => {
|
||||
const qrCodeUrl = await this.generateQrCode();
|
||||
if (qrCodeUrl) {
|
||||
ctx.body = { data: { url: qrCodeUrl } }; // 确保返回的数据格式正确
|
||||
console.log(`我是生成二维码接口的URL: ${qrCodeUrl}`);
|
||||
} else {
|
||||
ctx.status = 500;
|
||||
ctx.body = 'Failed to generate QR code';
|
||||
}
|
||||
},
|
||||
// 处理OAuth回调接口
|
||||
handleOAuthCallback: async (ctx) => {
|
||||
await this.handleOAuthCallback(ctx);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
this.app.acl.allow('wechat', '*', 'public'); // 实体名称,允许所有资源,公共级别访问
|
||||
this.app.acl.allow('wechat', '*', 'public');
|
||||
}
|
||||
|
||||
// 处理 message 表的 afterCreate 事件
|
||||
async handleMessageCreate(model, options) {
|
||||
const { id, key, value } = model.get();
|
||||
console.log(`New message created: ${key} - ${value}`);
|
||||
|
||||
// 获取所有关注公众号的用户 ID
|
||||
const userIds = await this.getAllFollowers();
|
||||
console.log('Follower IDs:', userIds);
|
||||
|
||||
// 推送消息content
|
||||
await this.pushMessageToFollowers(userIds, `目前系统提示已经更新,大家可以使用新的关键字 "${key}" 来获取信息!`);
|
||||
}
|
||||
|
||||
// 处理 menu 表的 afterUpdate 事件
|
||||
async handleMenuUpdate(model, options) {
|
||||
const { id, name } = model.get();
|
||||
console.log(`Menu item updated: ${name} (ID: ${id})`);
|
||||
|
||||
// 获取所有关注公众号的用户 ID
|
||||
const userIds = await this.getAllFollowers();
|
||||
console.log('Follower IDs:', userIds);
|
||||
|
||||
// 推送消息content
|
||||
await this.pushMessageToFollowers(userIds, '用户你好,菜单项已经更新');
|
||||
}
|
||||
|
||||
// 获取用户列表
|
||||
async getAllFollowers() {
|
||||
const accessToken = await this.getAccessToken();
|
||||
if (!accessToken) {
|
||||
console.error('Failed to get access token');
|
||||
return [];
|
||||
}
|
||||
|
||||
const url = `https://api.weixin.qq.com/cgi-bin/user/get?access_token=${accessToken}`;
|
||||
try {
|
||||
const response = await axios.get(url);
|
||||
if (response.data.errcode) {
|
||||
console.error(`Error getting followers: ${response.data.errmsg}`);
|
||||
return [];
|
||||
}
|
||||
console.log('返回用户ID数组----');
|
||||
return response.data.data.openid; // 返回关注者的OpenID数组
|
||||
} catch (error) {
|
||||
console.error('Error getting followers:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// 给用户发送消息
|
||||
async pushMessageToFollowers(userIds, message) {
|
||||
const accessToken = await this.getAccessToken();
|
||||
if (!accessToken) {
|
||||
console.error('Failed to get access token');
|
||||
return;
|
||||
}
|
||||
|
||||
const url = `https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=${accessToken}`;
|
||||
|
||||
for (const userId of userIds) {
|
||||
const data = {
|
||||
touser: userId,
|
||||
msgtype: 'text',
|
||||
text: {
|
||||
content: message,
|
||||
},
|
||||
};
|
||||
|
||||
// 调用Wechat API对用户发送消息
|
||||
try {
|
||||
const response = await axios.post(url, data);
|
||||
if (response.data.errcode) {
|
||||
console.error(`Error pushing message to user ${userId}: ${response.data.errmsg}`);
|
||||
} else {
|
||||
console.log(`Message pushed to user ${userId} successfully.`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error pushing message to user ${userId}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4045,6 +4045,9 @@ importers:
|
||||
|
||||
packages/plugins/@tachybase/plugin-wechat-official-account:
|
||||
dependencies:
|
||||
'@ant-design/icons':
|
||||
specifier: ^5.3.7
|
||||
version: 5.3.7(react-dom@18.3.1)(react@18.3.1)
|
||||
'@tachybase/client':
|
||||
specifier: workspace:*
|
||||
version: link:../../../core/client
|
||||
@ -4063,12 +4066,27 @@ importers:
|
||||
antd:
|
||||
specifier: 5.19.1
|
||||
version: 5.19.1(react-dom@18.3.1)(react@18.3.1)
|
||||
axios:
|
||||
specifier: ^1.7.2
|
||||
version: 1.7.2
|
||||
bcrypt:
|
||||
specifier: ^5.1.1
|
||||
version: 5.1.1
|
||||
body-parser:
|
||||
specifier: ^1.20.2
|
||||
version: 1.20.2
|
||||
crypto:
|
||||
specifier: ^1.0.1
|
||||
version: 1.0.1
|
||||
jsonwebtoken:
|
||||
specifier: ^8.5.1
|
||||
version: 8.5.1
|
||||
koa-bodyparser:
|
||||
specifier: ^4.3.0
|
||||
version: 4.4.1
|
||||
qrcode.react:
|
||||
specifier: ^3.1.0
|
||||
version: 3.1.0(react@18.3.1)
|
||||
react-router-dom:
|
||||
specifier: ^6.11.2
|
||||
version: 6.25.1(react-dom@18.3.1)(react@18.3.1)
|
||||
@ -14770,7 +14788,6 @@ packages:
|
||||
/are-we-there-yet@2.0.0:
|
||||
resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==}
|
||||
engines: {node: '>=10'}
|
||||
deprecated: This package is no longer supported.
|
||||
dependencies:
|
||||
delegates: 1.0.0
|
||||
readable-stream: 3.6.2
|
||||
@ -15303,6 +15320,18 @@ packages:
|
||||
tweetnacl: 0.14.5
|
||||
dev: true
|
||||
|
||||
/bcrypt@5.1.1:
|
||||
resolution: {integrity: sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
'@mapbox/node-pre-gyp': 1.0.11
|
||||
node-addon-api: 5.1.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/bessel@1.0.2:
|
||||
resolution: {integrity: sha512-Al3nHGQGqDYqqinXhQzmwmcRToe/3WyBv4N8aZc5Pef8xw2neZlR9VPi84Sa23JtgWcucu18HxVZrnI0fn2etw==}
|
||||
engines: {node: '>=0.8'}
|
||||
@ -15795,7 +15824,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
/call-bind@1.0.7:
|
||||
resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==}
|
||||
resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==, tarball: https://registry.npmmirror.com/call-bind/-/call-bind-1.0.7.tgz}
|
||||
engines: {node: '>= 0.4'}
|
||||
dependencies:
|
||||
es-define-property: 1.0.0
|
||||
@ -16214,7 +16243,7 @@ packages:
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
/co-body@6.1.0:
|
||||
resolution: {integrity: sha512-m7pOT6CdLN7FuXUcpuz/8lfQ/L77x8SchHCF4G0RBTJO20Wzmhn5Sp4/5WsKy8OSpifBSUrmg83qEqaDHdyFuQ==}
|
||||
resolution: {integrity: sha512-m7pOT6CdLN7FuXUcpuz/8lfQ/L77x8SchHCF4G0RBTJO20Wzmhn5Sp4/5WsKy8OSpifBSUrmg83qEqaDHdyFuQ==, tarball: https://registry.npmmirror.com/co-body/-/co-body-6.1.0.tgz}
|
||||
dependencies:
|
||||
inflation: 2.1.0
|
||||
qs: 6.12.3
|
||||
@ -16747,7 +16776,7 @@ packages:
|
||||
toggle-selection: 1.0.6
|
||||
|
||||
/copy-to@2.0.1:
|
||||
resolution: {integrity: sha512-3DdaFaU/Zf1AnpLiFDeNCD4TOWe3Zl2RZaTzUvWiIk5ERzcCodOE20Vqq4fzCbNoHURFHT4/us/Lfq+S2zyY4w==}
|
||||
resolution: {integrity: sha512-3DdaFaU/Zf1AnpLiFDeNCD4TOWe3Zl2RZaTzUvWiIk5ERzcCodOE20Vqq4fzCbNoHURFHT4/us/Lfq+S2zyY4w==, tarball: https://registry.npmmirror.com/copy-to/-/copy-to-2.0.1.tgz}
|
||||
|
||||
/core-js-compat@3.34.0:
|
||||
resolution: {integrity: sha512-4ZIyeNbW/Cn1wkMMDy+mvrRUxrwFNjKwbhCfQpDd+eLgYipDqp8oGFGtLmhh18EDPKA0g3VUBYOxQGGwvWLVpA==}
|
||||
@ -16987,6 +17016,11 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/crypto@1.0.1:
|
||||
resolution: {integrity: sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==}
|
||||
deprecated: This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in.
|
||||
dev: false
|
||||
|
||||
/css-blank-pseudo@3.0.3(postcss@8.4.39):
|
||||
resolution: {integrity: sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==}
|
||||
engines: {node: ^12 || ^14 || >=16}
|
||||
@ -17743,7 +17777,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
/define-data-property@1.1.4:
|
||||
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
|
||||
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==, tarball: https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz}
|
||||
engines: {node: '>= 0.4'}
|
||||
dependencies:
|
||||
es-define-property: 1.0.0
|
||||
@ -18356,13 +18390,13 @@ packages:
|
||||
which-typed-array: 1.1.15
|
||||
|
||||
/es-define-property@1.0.0:
|
||||
resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==}
|
||||
resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==, tarball: https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.0.tgz}
|
||||
engines: {node: '>= 0.4'}
|
||||
dependencies:
|
||||
get-intrinsic: 1.2.4
|
||||
|
||||
/es-errors@1.3.0:
|
||||
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
|
||||
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==, tarball: https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
/es-get-iterator@1.1.3:
|
||||
@ -19762,7 +19796,7 @@ packages:
|
||||
dev: true
|
||||
|
||||
/function-bind@1.1.2:
|
||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==, tarball: https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz}
|
||||
|
||||
/function.prototype.name@1.1.6:
|
||||
resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==}
|
||||
@ -19779,7 +19813,6 @@ packages:
|
||||
/gauge@3.0.2:
|
||||
resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==}
|
||||
engines: {node: '>=10'}
|
||||
deprecated: This package is no longer supported.
|
||||
dependencies:
|
||||
aproba: 2.0.0
|
||||
color-support: 1.1.3
|
||||
@ -19829,7 +19862,7 @@ packages:
|
||||
resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==}
|
||||
|
||||
/get-intrinsic@1.2.4:
|
||||
resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
|
||||
resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==, tarball: https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz}
|
||||
engines: {node: '>= 0.4'}
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
@ -20132,7 +20165,7 @@ packages:
|
||||
resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==}
|
||||
|
||||
/gopd@1.0.1:
|
||||
resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
|
||||
resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==, tarball: https://registry.npmmirror.com/gopd/-/gopd-1.0.1.tgz}
|
||||
dependencies:
|
||||
get-intrinsic: 1.2.4
|
||||
|
||||
@ -20229,16 +20262,16 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
|
||||
/has-property-descriptors@1.0.2:
|
||||
resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
|
||||
resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==, tarball: https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz}
|
||||
dependencies:
|
||||
es-define-property: 1.0.0
|
||||
|
||||
/has-proto@1.0.3:
|
||||
resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==}
|
||||
resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==, tarball: https://registry.npmmirror.com/has-proto/-/has-proto-1.0.3.tgz}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
/has-symbols@1.0.3:
|
||||
resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
|
||||
resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==, tarball: https://registry.npmmirror.com/has-symbols/-/has-symbols-1.0.3.tgz}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
/has-tostringtag@1.0.2:
|
||||
@ -20288,7 +20321,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
/hasown@2.0.2:
|
||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==, tarball: https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz}
|
||||
engines: {node: '>= 0.4'}
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
@ -20688,7 +20721,7 @@ packages:
|
||||
optional: true
|
||||
|
||||
/inflation@2.1.0:
|
||||
resolution: {integrity: sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==}
|
||||
resolution: {integrity: sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==, tarball: https://registry.npmmirror.com/inflation/-/inflation-2.1.0.tgz}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
/inflection@1.13.4:
|
||||
@ -22672,7 +22705,7 @@ packages:
|
||||
dev: true
|
||||
|
||||
/media-typer@0.3.0:
|
||||
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
|
||||
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==, tarball: https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
/mem@8.1.1:
|
||||
@ -23372,6 +23405,10 @@ packages:
|
||||
resolution: {integrity: sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==}
|
||||
dev: false
|
||||
|
||||
/node-addon-api@5.1.0:
|
||||
resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==}
|
||||
dev: false
|
||||
|
||||
/node-domexception@1.0.0:
|
||||
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
|
||||
engines: {node: '>=10.5.0'}
|
||||
@ -23609,7 +23646,6 @@ packages:
|
||||
|
||||
/npmlog@5.0.1:
|
||||
resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==}
|
||||
deprecated: This package is no longer supported.
|
||||
dependencies:
|
||||
are-we-there-yet: 2.0.0
|
||||
console-control-strings: 1.1.0
|
||||
@ -23658,14 +23694,14 @@ packages:
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
/object-inspect@1.12.3:
|
||||
resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==}
|
||||
resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==, tarball: https://registry.npmmirror.com/object-inspect/-/object-inspect-1.12.3.tgz}
|
||||
|
||||
/object-inspect@1.13.1:
|
||||
resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==}
|
||||
dev: false
|
||||
|
||||
/object-inspect@1.13.2:
|
||||
resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==}
|
||||
resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==, tarball: https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.2.tgz}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
/object-is@1.1.5:
|
||||
@ -25427,7 +25463,6 @@ packages:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
dev: true
|
||||
|
||||
/qrcode@1.5.3:
|
||||
resolution: {integrity: sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==}
|
||||
@ -27564,7 +27599,7 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
|
||||
/safer-buffer@2.1.2:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==, tarball: https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz}
|
||||
|
||||
/sanitize-html@2.10.0:
|
||||
resolution: {integrity: sha512-JqdovUd81dG4k87vZt6uA6YhDfWkUGruUu/aPmXLxXi45gZExnt9Bnw/qeQU8oGf82vPyaE0vO4aH0PbobB9JQ==}
|
||||
@ -27831,7 +27866,7 @@ packages:
|
||||
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
|
||||
|
||||
/set-function-length@1.2.2:
|
||||
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
|
||||
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==, tarball: https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz}
|
||||
engines: {node: '>= 0.4'}
|
||||
dependencies:
|
||||
define-data-property: 1.1.4
|
||||
@ -27865,7 +27900,7 @@ packages:
|
||||
resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==}
|
||||
|
||||
/setprototypeof@1.2.0:
|
||||
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
|
||||
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==, tarball: https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz}
|
||||
|
||||
/sha.js@2.4.11:
|
||||
resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==}
|
||||
@ -27914,7 +27949,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
/side-channel@1.0.6:
|
||||
resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==}
|
||||
resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==, tarball: https://registry.npmmirror.com/side-channel/-/side-channel-1.0.6.tgz}
|
||||
engines: {node: '>= 0.4'}
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
@ -29210,7 +29245,7 @@ packages:
|
||||
resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==}
|
||||
|
||||
/toidentifier@1.0.1:
|
||||
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
|
||||
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==, tarball: https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz}
|
||||
engines: {node: '>=0.6'}
|
||||
|
||||
/toposort-class@1.0.1:
|
||||
@ -29601,7 +29636,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
/type-is@1.6.18:
|
||||
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
||||
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==, tarball: https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz}
|
||||
engines: {node: '>= 0.6'}
|
||||
dependencies:
|
||||
media-typer: 0.3.0
|
||||
|
Loading…
Reference in New Issue
Block a user