feat(push): migrate APNs provider from @parse/node-apn to apns2 library
- Replace @parse/node-apn with apns2 for improved APNs integration - Update ApnsProvider to use new ApnsClient with modern API - Refactor notification creation and sending logic for better error handling - Add proper error event listeners for device token issues - Update configuration interface to match apns2 requirements - Modify push notification endpoints to allow public access for token registration - Update service methods to handle new response format from apns2 - Add UsersModule dependency to PushNotificationsModule
This commit is contained in:
175
package-lock.json
generated
175
package-lock.json
generated
@@ -16,14 +16,17 @@
|
||||
"@nestjs/platform-express": "^11.0.1",
|
||||
"@nestjs/sequelize": "^11.0.0",
|
||||
"@nestjs/swagger": "^11.1.0",
|
||||
"@parse/node-apn": "^5.0.0",
|
||||
"@types/jsonwebtoken": "^9.0.9",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"apns2": "^12.2.0",
|
||||
"axios": "^1.10.0",
|
||||
"body-parser": "^2.2.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
"cos-nodejs-sdk-v5": "^2.14.7",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.18",
|
||||
"fs": "^0.0.1-security",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"jwks-rsa": "^3.2.0",
|
||||
@@ -1885,6 +1888,15 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@lukeed/ms": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://mirrors.tencent.com/npm/@lukeed/ms/-/ms-2.0.2.tgz",
|
||||
"integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@microsoft/tsdoc": {
|
||||
"version": "0.15.1",
|
||||
"resolved": "https://mirrors.tencent.com/npm/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz",
|
||||
@@ -2586,6 +2598,79 @@
|
||||
"npm": ">=5.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@parse/node-apn": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://mirrors.tencent.com/npm/@parse/node-apn/-/node-apn-5.2.3.tgz",
|
||||
"integrity": "sha512-uBUTTbzk0YyMOcE5qTcNdit5v1BdaECCRSQYbMGU/qY1eHwBaqeWOYd8rwi2Caga3K7IZyQGhpvL4/56H+uvrQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "4.3.3",
|
||||
"jsonwebtoken": "9.0.0",
|
||||
"node-forge": "1.3.1",
|
||||
"verror": "1.10.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/@parse/node-apn/node_modules/core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://mirrors.tencent.com/npm/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@parse/node-apn/node_modules/debug": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://mirrors.tencent.com/npm/debug/-/debug-4.3.3.tgz",
|
||||
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@parse/node-apn/node_modules/jsonwebtoken": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://mirrors.tencent.com/npm/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz",
|
||||
"integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jws": "^3.2.2",
|
||||
"lodash": "^4.17.21",
|
||||
"ms": "^2.1.1",
|
||||
"semver": "^7.3.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
"npm": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@parse/node-apn/node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://mirrors.tencent.com/npm/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node_modules/@parse/node-apn/node_modules/verror": {
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://mirrors.tencent.com/npm/verror/-/verror-1.10.1.tgz",
|
||||
"integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"assert-plus": "^1.0.0",
|
||||
"core-util-is": "1.0.2",
|
||||
"extsprintf": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@pkgr/core": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.0.tgz",
|
||||
@@ -4330,6 +4415,19 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/apns2": {
|
||||
"version": "12.2.0",
|
||||
"resolved": "https://mirrors.tencent.com/npm/apns2/-/apns2-12.2.0.tgz",
|
||||
"integrity": "sha512-HySXBzPDMTX8Vxy/ilU9/XcNndJBlgCc+no2+Hj4BaY7CjkStkszufAI6CRK1yDw8K+6ALH+V+mXuQKZe2zeZA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-jwt": "^6.0.1",
|
||||
"undici": "^7.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/append-field": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
|
||||
@@ -4393,6 +4491,17 @@
|
||||
"safer-buffer": "~2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/asn1.js": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://mirrors.tencent.com/npm/asn1.js/-/asn1.js-5.4.1.tgz",
|
||||
"integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
|
||||
"dependencies": {
|
||||
"bn.js": "^4.0.0",
|
||||
"inherits": "^2.0.1",
|
||||
"minimalistic-assert": "^1.0.0",
|
||||
"safer-buffer": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/assert-plus": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
||||
@@ -4746,6 +4855,11 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bn.js": {
|
||||
"version": "4.12.2",
|
||||
"resolved": "https://mirrors.tencent.com/npm/bn.js/-/bn.js-4.12.2.tgz",
|
||||
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw=="
|
||||
},
|
||||
"node_modules/bodec": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://mirrors.tencent.com/npm/bodec/-/bodec-0.1.0.tgz",
|
||||
@@ -5680,10 +5794,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.13",
|
||||
"resolved": "https://mirrors.tencent.com/npm/dayjs/-/dayjs-1.11.13.tgz",
|
||||
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
|
||||
"dev": true,
|
||||
"version": "1.11.18",
|
||||
"resolved": "https://mirrors.tencent.com/npm/dayjs/-/dayjs-1.11.18.tgz",
|
||||
"integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/debounce-fn": {
|
||||
@@ -6815,6 +6928,21 @@
|
||||
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-jwt": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://mirrors.tencent.com/npm/fast-jwt/-/fast-jwt-6.0.2.tgz",
|
||||
"integrity": "sha512-dTF4bhYnuXhZYQUaxsHKqAyA5y/L/kQc4fUu0wQ0BSA0dMfcNrcv0aqR2YnVi4f7e1OnzDVU7sDsNdzl1O5EVA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@lukeed/ms": "^2.0.2",
|
||||
"asn1.js": "^5.4.1",
|
||||
"ecdsa-sig-formatter": "^1.0.11",
|
||||
"mnemonist": "^0.40.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-levenshtein": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
|
||||
@@ -9536,6 +9664,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/minimalistic-assert": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://mirrors.tencent.com/npm/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
||||
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
@@ -9589,6 +9723,15 @@
|
||||
"mkdirp": "bin/cmd.js"
|
||||
}
|
||||
},
|
||||
"node_modules/mnemonist": {
|
||||
"version": "0.40.3",
|
||||
"resolved": "https://mirrors.tencent.com/npm/mnemonist/-/mnemonist-0.40.3.tgz",
|
||||
"integrity": "sha512-Vjyr90sJ23CKKH/qPAgUKicw/v6pRoamxIEDFOF8uSgFME7DqPRpHgRTejWVjkdGg5dXj0/NyxZHZ9bcjH+2uQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"obliterator": "^2.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/module-details-from-path": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://mirrors.tencent.com/npm/module-details-from-path/-/module-details-from-path-1.0.3.tgz",
|
||||
@@ -9847,6 +9990,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/node-forge": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://mirrors.tencent.com/npm/node-forge/-/node-forge-1.3.1.tgz",
|
||||
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
|
||||
"license": "(BSD-3-Clause OR GPL-2.0)",
|
||||
"engines": {
|
||||
"node": ">= 6.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-int64": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
|
||||
@@ -9936,6 +10088,12 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/obliterator": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://mirrors.tencent.com/npm/obliterator/-/obliterator-2.0.5.tgz",
|
||||
"integrity": "sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/on-finished": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||
@@ -12901,6 +13059,15 @@
|
||||
"through": "^2.3.8"
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://mirrors.tencent.com/npm/undici/-/undici-7.16.0.tgz",
|
||||
"integrity": "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.18.1"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.20.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"@parse/node-apn": "^5.0.0",
|
||||
"@types/jsonwebtoken": "^9.0.9",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"apns2": "^12.2.0",
|
||||
"axios": "^1.10.0",
|
||||
"body-parser": "^2.2.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
@@ -106,4 +107,4 @@
|
||||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
import { Injectable, Logger, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import * as apn from '@parse/node-apn';
|
||||
import { ApnsClient, SilentNotification, Notification, Errors } from 'apns2';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { ApnsConfig, ApnsNotificationOptions } from './interfaces/apns-config.interface';
|
||||
|
||||
interface SendResult {
|
||||
sent: string[];
|
||||
failed: Array<{
|
||||
device: string;
|
||||
error?: Error;
|
||||
status?: string;
|
||||
response?: any;
|
||||
}>;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ApnsProvider implements OnModuleInit, OnModuleDestroy {
|
||||
private readonly logger = new Logger(ApnsProvider.name);
|
||||
private provider: apn.Provider;
|
||||
private multiProvider: apn.MultiProvider;
|
||||
private client: ApnsClient;
|
||||
private config: ApnsConfig;
|
||||
|
||||
constructor(private readonly configService: ConfigService) {
|
||||
@@ -18,7 +26,8 @@ export class ApnsProvider implements OnModuleInit, OnModuleDestroy {
|
||||
|
||||
async onModuleInit() {
|
||||
try {
|
||||
await this.initializeProvider();
|
||||
await this.initializeClient();
|
||||
this.setupErrorHandlers();
|
||||
this.logger.log('APNs Provider initialized successfully');
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to initialize APNs Provider', error);
|
||||
@@ -28,7 +37,7 @@ export class ApnsProvider implements OnModuleInit, OnModuleDestroy {
|
||||
|
||||
async onModuleDestroy() {
|
||||
try {
|
||||
this.shutdown();
|
||||
await this.shutdown();
|
||||
this.logger.log('APNs Provider shutdown successfully');
|
||||
} catch (error) {
|
||||
this.logger.error('Error during APNs Provider shutdown', error);
|
||||
@@ -39,25 +48,24 @@ export class ApnsProvider implements OnModuleInit, OnModuleDestroy {
|
||||
* 构建APNs配置
|
||||
*/
|
||||
private buildConfig(): ApnsConfig {
|
||||
const keyId = this.configService.get<string>('APNS_KEY_ID');
|
||||
const teamId = this.configService.get<string>('APNS_TEAM_ID');
|
||||
const keyId = this.configService.get<string>('APNS_KEY_ID');
|
||||
const keyPath = this.configService.get<string>('APNS_KEY_PATH');
|
||||
const bundleId = this.configService.get<string>('APNS_BUNDLE_ID');
|
||||
const environment = this.configService.get<string>('APNS_ENVIRONMENT', 'sandbox');
|
||||
const clientCount = this.configService.get<number>('APNS_CLIENT_COUNT', 2);
|
||||
|
||||
if (!keyId || !teamId || !keyPath || !bundleId) {
|
||||
if (!teamId || !keyId || !keyPath || !bundleId) {
|
||||
throw new Error('Missing required APNs configuration');
|
||||
}
|
||||
|
||||
let key: string | Buffer;
|
||||
let signingKey: string | Buffer;
|
||||
try {
|
||||
// 尝试读取密钥文件
|
||||
if (fs.existsSync(keyPath)) {
|
||||
key = fs.readFileSync(keyPath);
|
||||
signingKey = fs.readFileSync(keyPath);
|
||||
} else {
|
||||
// 如果是直接的内容而不是文件路径
|
||||
key = keyPath;
|
||||
signingKey = keyPath;
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to read APNs key file: ${keyPath}`, error);
|
||||
@@ -65,49 +73,79 @@ export class ApnsProvider implements OnModuleInit, OnModuleDestroy {
|
||||
}
|
||||
|
||||
return {
|
||||
token: {
|
||||
key,
|
||||
keyId,
|
||||
teamId,
|
||||
},
|
||||
team: teamId,
|
||||
keyId,
|
||||
signingKey,
|
||||
defaultTopic: bundleId,
|
||||
host: environment === 'production' ? 'api.push.apple.com' : 'api.development.push.apple.com',
|
||||
port: 443,
|
||||
production: environment === 'production',
|
||||
clientCount,
|
||||
connectionRetryLimit: this.configService.get<number>('APNS_CONNECTION_RETRY_LIMIT', 3),
|
||||
heartBeat: this.configService.get<number>('APNS_HEARTBEAT', 60000),
|
||||
requestTimeout: this.configService.get<number>('APNS_REQUEST_TIMEOUT', 5000),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化APNs连接
|
||||
* 初始化APNs客户端
|
||||
*/
|
||||
private async initializeProvider(): Promise<void> {
|
||||
private async initializeClient(): Promise<void> {
|
||||
try {
|
||||
// 创建单个Provider
|
||||
this.provider = new apn.Provider(this.config);
|
||||
|
||||
// 创建多Provider连接池
|
||||
this.multiProvider = new apn.MultiProvider(this.config);
|
||||
|
||||
this.logger.log(`APNs Provider initialized with ${this.config.clientCount} clients`);
|
||||
this.logger.log(`Environment: ${this.config.production ? 'Production' : 'Sandbox'}`);
|
||||
this.client = new ApnsClient(this.config);
|
||||
this.logger.log(`APNs Client initialized for ${this.config.production ? 'Production' : 'Sandbox'} environment`);
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to initialize APNs Provider', error);
|
||||
this.logger.error('Failed to initialize APNs Client', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置错误处理器
|
||||
*/
|
||||
private setupErrorHandlers(): void {
|
||||
// 监听特定错误
|
||||
this.client.on(Errors.badDeviceToken, (err) => {
|
||||
this.logger.error(`Bad device token: ${err.deviceToken}`, err.reason);
|
||||
});
|
||||
|
||||
this.client.on(Errors.unregistered, (err) => {
|
||||
this.logger.error(`Device unregistered: ${err.deviceToken}`, err.reason);
|
||||
});
|
||||
|
||||
this.client.on(Errors.topicDisallowed, (err) => {
|
||||
this.logger.error(`Topic disallowed: ${err.deviceToken}`, err.reason);
|
||||
});
|
||||
|
||||
// 监听所有错误
|
||||
this.client.on(Errors.error, (err) => {
|
||||
this.logger.error(`APNs error for device ${err.deviceToken}: ${err.reason}`, err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送单个通知
|
||||
*/
|
||||
async send(notification: apn.Notification, deviceTokens: string[]): Promise<apn.Results> {
|
||||
async send(notification: Notification, deviceTokens: string[]): Promise<SendResult> {
|
||||
const results: SendResult = {
|
||||
sent: [],
|
||||
failed: []
|
||||
};
|
||||
|
||||
try {
|
||||
this.logger.debug(`Sending notification to ${deviceTokens.length} devices`);
|
||||
|
||||
const results = await this.provider.send(notification, deviceTokens);
|
||||
for (const deviceToken of deviceTokens) {
|
||||
try {
|
||||
// 为每个设备令牌创建新的通知实例
|
||||
const deviceNotification = this.createDeviceNotification(notification, deviceToken);
|
||||
await this.client.send(deviceNotification);
|
||||
results.sent.push(deviceToken);
|
||||
} catch (error) {
|
||||
results.failed.push({
|
||||
device: deviceToken,
|
||||
error: error as Error
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.logResults(results);
|
||||
|
||||
return results;
|
||||
} catch (error) {
|
||||
this.logger.error('Error sending notification', error);
|
||||
@@ -118,14 +156,40 @@ export class ApnsProvider implements OnModuleInit, OnModuleDestroy {
|
||||
/**
|
||||
* 批量发送通知
|
||||
*/
|
||||
async sendBatch(notifications: apn.Notification[], deviceTokens: string[]): Promise<apn.Results> {
|
||||
async sendBatch(notifications: Notification[], deviceTokens: string[]): Promise<SendResult> {
|
||||
const results: SendResult = {
|
||||
sent: [],
|
||||
failed: []
|
||||
};
|
||||
|
||||
try {
|
||||
this.logger.debug(`Sending ${notifications.length} notifications to ${deviceTokens.length} devices`);
|
||||
|
||||
const results = await this.multiProvider.send(notifications, deviceTokens);
|
||||
const deviceNotifications: Notification[] = [];
|
||||
for (const notification of notifications) {
|
||||
for (const deviceToken of deviceTokens) {
|
||||
deviceNotifications.push(this.createDeviceNotification(notification, deviceToken));
|
||||
}
|
||||
}
|
||||
|
||||
const sendResults = await this.client.sendMany(deviceNotifications);
|
||||
|
||||
// 处理 sendMany 的结果
|
||||
sendResults.forEach((result, index) => {
|
||||
const deviceIndex = index % deviceTokens.length;
|
||||
const deviceToken = deviceTokens[deviceIndex];
|
||||
|
||||
if (result && typeof result === 'object' && 'error' in result) {
|
||||
results.failed.push({
|
||||
device: deviceToken,
|
||||
error: (result as any).error
|
||||
});
|
||||
} else {
|
||||
results.sent.push(deviceToken);
|
||||
}
|
||||
});
|
||||
|
||||
this.logResults(results);
|
||||
|
||||
return results;
|
||||
} catch (error) {
|
||||
this.logger.error('Error sending batch notifications', error);
|
||||
@@ -136,15 +200,15 @@ export class ApnsProvider implements OnModuleInit, OnModuleDestroy {
|
||||
/**
|
||||
* 管理推送通道
|
||||
*/
|
||||
async manageChannels(notification: apn.Notification, bundleId: string, action: string): Promise<any> {
|
||||
async manageChannels(notification: Notification, bundleId: string, action: string): Promise<any> {
|
||||
try {
|
||||
this.logger.debug(`Managing channels for bundle ${bundleId} with action ${action}`);
|
||||
|
||||
const results = await this.provider.manageChannels(notification, bundleId, action);
|
||||
// apns2 库没有直接的 manageChannels 方法,这里需要实现自定义逻辑
|
||||
// 或者使用原始的 HTTP 请求来管理通道
|
||||
this.logger.warn(`Channel management not directly supported in apns2 library. Action: ${action}`);
|
||||
|
||||
this.logger.log(`Channel management completed: ${JSON.stringify(results)}`);
|
||||
|
||||
return results;
|
||||
return { message: 'Channel management not implemented in apns2 library' };
|
||||
} catch (error) {
|
||||
this.logger.error('Error managing channels', error);
|
||||
throw error;
|
||||
@@ -154,15 +218,15 @@ export class ApnsProvider implements OnModuleInit, OnModuleDestroy {
|
||||
/**
|
||||
* 广播实时活动通知
|
||||
*/
|
||||
async broadcast(notification: apn.Notification, bundleId: string): Promise<any> {
|
||||
async broadcast(notification: Notification, bundleId: string): Promise<any> {
|
||||
try {
|
||||
this.logger.debug(`Broadcasting to bundle ${bundleId}`);
|
||||
|
||||
const results = await this.provider.broadcast(notification, bundleId);
|
||||
// apns2 库没有直接的 broadcast 方法,这里需要实现自定义逻辑
|
||||
// 或者使用原始的 HTTP 请求来广播
|
||||
this.logger.warn(`Broadcast not directly supported in apns2 library. Bundle: ${bundleId}`);
|
||||
|
||||
this.logger.log(`Broadcast completed: ${JSON.stringify(results)}`);
|
||||
|
||||
return results;
|
||||
return { message: 'Broadcast not implemented in apns2 library' };
|
||||
} catch (error) {
|
||||
this.logger.error('Error broadcasting', error);
|
||||
throw error;
|
||||
@@ -172,88 +236,109 @@ export class ApnsProvider implements OnModuleInit, OnModuleDestroy {
|
||||
/**
|
||||
* 创建标准通知
|
||||
*/
|
||||
createNotification(options: {
|
||||
title?: string;
|
||||
body?: string;
|
||||
payload?: any;
|
||||
pushType?: string;
|
||||
priority?: number;
|
||||
expiry?: number;
|
||||
collapseId?: string;
|
||||
topic?: string;
|
||||
sound?: string;
|
||||
badge?: number;
|
||||
mutableContent?: boolean;
|
||||
contentAvailable?: boolean;
|
||||
}): apn.Notification {
|
||||
const notification = new apn.Notification();
|
||||
createNotification(options: ApnsNotificationOptions): Notification {
|
||||
// 构建通知选项
|
||||
const notificationOptions: any = {};
|
||||
|
||||
// 设置基本内容
|
||||
if (options.title) {
|
||||
notification.title = options.title;
|
||||
// 设置 APS 属性
|
||||
const aps: any = {};
|
||||
|
||||
if (options.badge !== undefined) {
|
||||
notificationOptions.badge = options.badge;
|
||||
}
|
||||
|
||||
if (options.body) {
|
||||
notification.body = options.body;
|
||||
}
|
||||
|
||||
// 设置自定义负载
|
||||
if (options.payload) {
|
||||
notification.payload = options.payload;
|
||||
}
|
||||
|
||||
// 设置推送类型
|
||||
if (options.pushType) {
|
||||
notification.pushType = options.pushType;
|
||||
}
|
||||
|
||||
// 设置优先级
|
||||
if (options.priority) {
|
||||
notification.priority = options.priority;
|
||||
}
|
||||
|
||||
// 设置过期时间
|
||||
if (options.expiry) {
|
||||
notification.expiry = options.expiry;
|
||||
}
|
||||
|
||||
// 设置折叠ID
|
||||
if (options.collapseId) {
|
||||
notification.collapseId = options.collapseId;
|
||||
}
|
||||
|
||||
// 设置主题
|
||||
if (options.topic) {
|
||||
notification.topic = options.topic;
|
||||
}
|
||||
|
||||
// 设置声音
|
||||
if (options.sound) {
|
||||
notification.sound = options.sound;
|
||||
notificationOptions.sound = options.sound;
|
||||
}
|
||||
|
||||
// 设置徽章
|
||||
if (options.badge) {
|
||||
notification.badge = options.badge;
|
||||
}
|
||||
|
||||
// 设置可变内容
|
||||
if (options.mutableContent) {
|
||||
notification.mutableContent = 1;
|
||||
}
|
||||
|
||||
// 设置静默推送
|
||||
if (options.contentAvailable) {
|
||||
notification.contentAvailable = 1;
|
||||
notificationOptions.contentAvailable = true;
|
||||
}
|
||||
|
||||
return notification;
|
||||
if (options.mutableContent) {
|
||||
notificationOptions.mutableContent = true;
|
||||
}
|
||||
|
||||
if (options.priority) {
|
||||
notificationOptions.priority = options.priority;
|
||||
}
|
||||
|
||||
if (options.pushType) {
|
||||
notificationOptions.type = options.pushType;
|
||||
}
|
||||
|
||||
// 添加自定义数据
|
||||
if (options.data) {
|
||||
notificationOptions.data = options.data;
|
||||
}
|
||||
|
||||
// 创建通知对象,但不指定设备令牌(将在发送时设置)
|
||||
return new Notification('', notificationOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建基本通知
|
||||
*/
|
||||
createBasicNotification(deviceToken: string, title: string, body?: string, options?: Partial<ApnsNotificationOptions>): Notification {
|
||||
// 构建通知选项
|
||||
const notificationOptions: any = {
|
||||
alert: {
|
||||
title,
|
||||
body: body || ''
|
||||
}
|
||||
};
|
||||
|
||||
if (options?.badge !== undefined) {
|
||||
notificationOptions.badge = options.badge;
|
||||
}
|
||||
|
||||
if (options?.sound) {
|
||||
notificationOptions.sound = options.sound;
|
||||
}
|
||||
|
||||
if (options?.contentAvailable) {
|
||||
notificationOptions.contentAvailable = true;
|
||||
}
|
||||
|
||||
if (options?.mutableContent) {
|
||||
notificationOptions.mutableContent = true;
|
||||
}
|
||||
|
||||
if (options?.priority) {
|
||||
notificationOptions.priority = options.priority;
|
||||
}
|
||||
|
||||
if (options?.pushType) {
|
||||
notificationOptions.type = options.pushType;
|
||||
}
|
||||
|
||||
// 添加自定义数据
|
||||
if (options?.data) {
|
||||
notificationOptions.data = options.data;
|
||||
}
|
||||
|
||||
return new Notification(deviceToken, notificationOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建静默通知
|
||||
*/
|
||||
createSilentNotification(deviceToken: string): SilentNotification {
|
||||
return new SilentNotification(deviceToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为特定设备创建通知实例
|
||||
*/
|
||||
private createDeviceNotification(notification: Notification, deviceToken: string): Notification {
|
||||
// 创建新的通知实例,使用相同的选项但不同的设备令牌
|
||||
return new Notification(deviceToken, notification.options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录推送结果
|
||||
*/
|
||||
private logResults(results: apn.Results): void {
|
||||
private logResults(results: SendResult): void {
|
||||
const { sent, failed } = results;
|
||||
|
||||
this.logger.log(`Push results: ${sent.length} sent, ${failed.length} failed`);
|
||||
@@ -272,14 +357,10 @@ export class ApnsProvider implements OnModuleInit, OnModuleDestroy {
|
||||
/**
|
||||
* 关闭连接
|
||||
*/
|
||||
shutdown(): void {
|
||||
async shutdown(): Promise<void> {
|
||||
try {
|
||||
if (this.provider) {
|
||||
this.provider.shutdown();
|
||||
}
|
||||
|
||||
if (this.multiProvider) {
|
||||
this.multiProvider.shutdown();
|
||||
if (this.client) {
|
||||
await this.client.close();
|
||||
}
|
||||
|
||||
this.logger.log('APNs Provider connections closed');
|
||||
@@ -291,10 +372,9 @@ export class ApnsProvider implements OnModuleInit, OnModuleDestroy {
|
||||
/**
|
||||
* 获取Provider状态
|
||||
*/
|
||||
getStatus(): { connected: boolean; clientCount: number; environment: string } {
|
||||
getStatus(): { connected: boolean; environment: string } {
|
||||
return {
|
||||
connected: !!(this.provider || this.multiProvider),
|
||||
clientCount: this.config.clientCount || 1,
|
||||
connected: !!this.client,
|
||||
environment: this.config.production ? 'production' : 'sandbox',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
export interface ApnsConfig {
|
||||
token: {
|
||||
key: string | Buffer;
|
||||
keyId: string;
|
||||
teamId: string;
|
||||
};
|
||||
production: boolean;
|
||||
clientCount?: number;
|
||||
proxy?: {
|
||||
host: string;
|
||||
port: number;
|
||||
};
|
||||
connectionRetryLimit?: number;
|
||||
heartBeat?: number;
|
||||
requestTimeout?: number;
|
||||
team: string;
|
||||
keyId: string;
|
||||
signingKey: string | Buffer;
|
||||
defaultTopic: string;
|
||||
host?: string;
|
||||
port?: number;
|
||||
production?: boolean;
|
||||
}
|
||||
|
||||
export interface ApnsNotificationOptions {
|
||||
topic: string;
|
||||
topic?: string;
|
||||
id?: string;
|
||||
collapseId?: string;
|
||||
priority?: number;
|
||||
pushType?: string;
|
||||
expiry?: number;
|
||||
badge?: number;
|
||||
sound?: string;
|
||||
contentAvailable?: boolean;
|
||||
mutableContent?: boolean;
|
||||
data?: Record<string, any>;
|
||||
title?: string;
|
||||
body?: string;
|
||||
alert?: any;
|
||||
}
|
||||
@@ -13,21 +13,24 @@ import { Public } from '../common/decorators/public.decorator';
|
||||
|
||||
@ApiTags('推送通知')
|
||||
@Controller('push-notifications')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
|
||||
export class PushNotificationsController {
|
||||
constructor(private readonly pushNotificationsService: PushNotificationsService) { }
|
||||
|
||||
@Post('register-token')
|
||||
@ApiOperation({ summary: '注册设备推送令牌' })
|
||||
@Public()
|
||||
@ApiResponse({ status: 200, description: '注册成功', type: RegisterTokenResponseDto })
|
||||
async registerToken(
|
||||
@CurrentUser() user: AccessTokenPayload,
|
||||
@Body() registerTokenDto: RegisterDeviceTokenDto,
|
||||
): Promise<RegisterTokenResponseDto> {
|
||||
return this.pushNotificationsService.registerToken(user.sub, registerTokenDto);
|
||||
return this.pushNotificationsService.registerToken(registerTokenDto, user.sub);
|
||||
}
|
||||
|
||||
@Put('update-token')
|
||||
@Public()
|
||||
|
||||
@ApiOperation({ summary: '更新设备推送令牌' })
|
||||
@ApiResponse({ status: 200, description: '更新成功', type: UpdateTokenResponseDto })
|
||||
async updateToken(
|
||||
@@ -38,6 +41,7 @@ export class PushNotificationsController {
|
||||
}
|
||||
|
||||
@Delete('unregister-token')
|
||||
@Public()
|
||||
@ApiOperation({ summary: '注销设备推送令牌' })
|
||||
@ApiResponse({ status: 200, description: '注销成功', type: UnregisterTokenResponseDto })
|
||||
async unregisterToken(
|
||||
@@ -49,6 +53,7 @@ export class PushNotificationsController {
|
||||
|
||||
@Post('send')
|
||||
@ApiOperation({ summary: '发送推送通知' })
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiResponse({ status: 200, description: '发送成功', type: PushResponseDto })
|
||||
async sendNotification(
|
||||
@Body() sendNotificationDto: SendPushNotificationDto,
|
||||
@@ -58,6 +63,7 @@ export class PushNotificationsController {
|
||||
|
||||
@Post('send-by-template')
|
||||
@ApiOperation({ summary: '使用模板发送推送' })
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiResponse({ status: 200, description: '发送成功', type: PushResponseDto })
|
||||
async sendNotificationByTemplate(
|
||||
@Body() sendByTemplateDto: SendPushByTemplateDto,
|
||||
@@ -67,6 +73,7 @@ export class PushNotificationsController {
|
||||
|
||||
@Post('send-batch')
|
||||
@ApiOperation({ summary: '批量发送推送' })
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiResponse({ status: 200, description: '发送成功', type: BatchPushResponseDto })
|
||||
async sendBatchNotifications(
|
||||
@Body() sendBatchDto: SendPushNotificationDto,
|
||||
@@ -76,6 +83,7 @@ export class PushNotificationsController {
|
||||
|
||||
@Post('send-silent')
|
||||
@ApiOperation({ summary: '发送静默推送' })
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiResponse({ status: 200, description: '发送成功', type: PushResponseDto })
|
||||
async sendSilentNotification(
|
||||
@Body() body: { userId: string; payload: any },
|
||||
|
||||
@@ -12,11 +12,13 @@ import { PushMessage } from './models/push-message.model';
|
||||
import { PushTemplate } from './models/push-template.model';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { DatabaseModule } from '../database/database.module';
|
||||
import { UsersModule } from '../users/users.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule,
|
||||
DatabaseModule,
|
||||
UsersModule,
|
||||
SequelizeModule.forFeature([
|
||||
UserPushToken,
|
||||
PushMessage,
|
||||
|
||||
@@ -80,7 +80,7 @@ export class PushNotificationsService {
|
||||
const apnsNotification = this.apnsProvider.createNotification({
|
||||
title: notificationData.title,
|
||||
body: notificationData.body,
|
||||
payload: notificationData.payload,
|
||||
data: notificationData.payload,
|
||||
pushType: notificationData.pushType,
|
||||
priority: notificationData.priority,
|
||||
expiry: notificationData.expiry,
|
||||
@@ -239,7 +239,7 @@ export class PushNotificationsService {
|
||||
const apnsNotification = this.apnsProvider.createNotification({
|
||||
title: notificationData.title,
|
||||
body: notificationData.body,
|
||||
payload: notificationData.payload,
|
||||
data: notificationData.payload,
|
||||
pushType: notificationData.pushType,
|
||||
priority: notificationData.priority,
|
||||
expiry: notificationData.expiry,
|
||||
@@ -290,11 +290,12 @@ export class PushNotificationsService {
|
||||
const message = await this.pushMessageService.createMessage(messageData);
|
||||
|
||||
// 查找对应的APNs结果
|
||||
const apnsResult = apnsResults.sent.find(s => s.device === deviceToken) ||
|
||||
const apnsResult = apnsResults.sent.includes(deviceToken) ?
|
||||
{ device: deviceToken, success: true } :
|
||||
apnsResults.failed.find(f => f.device === deviceToken);
|
||||
|
||||
if (apnsResult) {
|
||||
if ('device' in apnsResult && apnsResult.device === deviceToken) {
|
||||
if (apnsResult.device === deviceToken && 'success' in apnsResult && apnsResult.success) {
|
||||
// 成功发送
|
||||
await this.pushMessageService.updateMessageStatus(message.id, PushMessageStatus.SENT, apnsResult);
|
||||
await this.pushTokenService.updateLastUsedTime(deviceToken);
|
||||
@@ -424,9 +425,9 @@ export class PushNotificationsService {
|
||||
/**
|
||||
* 注册设备令牌
|
||||
*/
|
||||
async registerToken(userId: string, tokenData: any): Promise<any> {
|
||||
async registerToken(tokenData: any, userId?: string,): Promise<any> {
|
||||
try {
|
||||
const token = await this.pushTokenService.registerToken(userId, tokenData);
|
||||
const token = await this.pushTokenService.registerToken(tokenData, userId);
|
||||
return {
|
||||
code: ResponseCode.SUCCESS,
|
||||
message: '设备令牌注册成功',
|
||||
|
||||
@@ -18,7 +18,7 @@ export class PushTokenService {
|
||||
/**
|
||||
* 注册设备令牌
|
||||
*/
|
||||
async registerToken(userId: string, tokenData: RegisterDeviceTokenDto): Promise<UserPushToken> {
|
||||
async registerToken(tokenData: RegisterDeviceTokenDto, userId?: string): Promise<UserPushToken> {
|
||||
try {
|
||||
this.logger.log(`Registering push token for user ${userId}`);
|
||||
|
||||
@@ -45,13 +45,6 @@ export class PushTokenService {
|
||||
return existingToken;
|
||||
}
|
||||
|
||||
// 检查用户是否已有其他设备的令牌,可以选择是否停用旧令牌
|
||||
const userTokens = await this.pushTokenModel.findAll({
|
||||
where: {
|
||||
userId,
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
|
||||
// 创建新令牌
|
||||
const newToken = await this.pushTokenModel.create({
|
||||
|
||||
114
yarn.lock
114
yarn.lock
@@ -872,6 +872,11 @@
|
||||
resolved "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz"
|
||||
integrity sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==
|
||||
|
||||
"@lukeed/ms@^2.0.2":
|
||||
version "2.0.2"
|
||||
resolved "https://mirrors.tencent.com/npm/@lukeed/ms/-/ms-2.0.2.tgz"
|
||||
integrity sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==
|
||||
|
||||
"@microsoft/tsdoc@0.15.1":
|
||||
version "0.15.1"
|
||||
resolved "https://mirrors.tencent.com/npm/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz"
|
||||
@@ -1121,6 +1126,16 @@
|
||||
dependencies:
|
||||
consola "^3.2.3"
|
||||
|
||||
"@parse/node-apn@^5.0.0":
|
||||
version "5.2.3"
|
||||
resolved "https://mirrors.tencent.com/npm/@parse/node-apn/-/node-apn-5.2.3.tgz"
|
||||
integrity sha512-uBUTTbzk0YyMOcE5qTcNdit5v1BdaECCRSQYbMGU/qY1eHwBaqeWOYd8rwi2Caga3K7IZyQGhpvL4/56H+uvrQ==
|
||||
dependencies:
|
||||
debug "4.3.3"
|
||||
jsonwebtoken "9.0.0"
|
||||
node-forge "1.3.1"
|
||||
verror "1.10.1"
|
||||
|
||||
"@pkgr/core@^0.2.0":
|
||||
version "0.2.0"
|
||||
resolved "https://registry.npmjs.org/@pkgr/core/-/core-0.2.0.tgz"
|
||||
@@ -2156,6 +2171,14 @@ anymatch@^3.0.3, anymatch@~3.1.2:
|
||||
normalize-path "^3.0.0"
|
||||
picomatch "^2.0.4"
|
||||
|
||||
apns2@^12.2.0:
|
||||
version "12.2.0"
|
||||
resolved "https://mirrors.tencent.com/npm/apns2/-/apns2-12.2.0.tgz"
|
||||
integrity sha512-HySXBzPDMTX8Vxy/ilU9/XcNndJBlgCc+no2+Hj4BaY7CjkStkszufAI6CRK1yDw8K+6ALH+V+mXuQKZe2zeZA==
|
||||
dependencies:
|
||||
fast-jwt "^6.0.1"
|
||||
undici "^7.9.0"
|
||||
|
||||
append-field@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz"
|
||||
@@ -2193,6 +2216,16 @@ asap@^2.0.0:
|
||||
resolved "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz"
|
||||
integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==
|
||||
|
||||
asn1.js@^5.4.1:
|
||||
version "5.4.1"
|
||||
resolved "https://mirrors.tencent.com/npm/asn1.js/-/asn1.js-5.4.1.tgz"
|
||||
integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==
|
||||
dependencies:
|
||||
bn.js "^4.0.0"
|
||||
inherits "^2.0.1"
|
||||
minimalistic-assert "^1.0.0"
|
||||
safer-buffer "^2.1.0"
|
||||
|
||||
asn1@~0.2.3:
|
||||
version "0.2.6"
|
||||
resolved "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz"
|
||||
@@ -2389,6 +2422,11 @@ blessed@0.1.81:
|
||||
resolved "https://mirrors.tencent.com/npm/blessed/-/blessed-0.1.81.tgz"
|
||||
integrity sha512-LoF5gae+hlmfORcG1M5+5XZi4LBmvlXTzwJWzUlPryN/SJdSflZvROM2TwkT0GMpq7oqT48NRd4GS7BiVBc5OQ==
|
||||
|
||||
bn.js@^4.0.0:
|
||||
version "4.12.2"
|
||||
resolved "https://mirrors.tencent.com/npm/bn.js/-/bn.js-4.12.2.tgz"
|
||||
integrity sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==
|
||||
|
||||
bodec@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://mirrors.tencent.com/npm/bodec/-/bodec-0.1.0.tgz"
|
||||
@@ -2952,16 +2990,11 @@ data-uri-to-buffer@^6.0.2:
|
||||
resolved "https://mirrors.tencent.com/npm/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz"
|
||||
integrity sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==
|
||||
|
||||
dayjs@^1.11.18:
|
||||
dayjs@^1.11.18, dayjs@~1.11.13:
|
||||
version "1.11.18"
|
||||
resolved "https://mirrors.tencent.com/npm/dayjs/-/dayjs-1.11.18.tgz#835fa712aac52ab9dec8b1494098774ed7070a11"
|
||||
resolved "https://mirrors.tencent.com/npm/dayjs/-/dayjs-1.11.18.tgz"
|
||||
integrity sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==
|
||||
|
||||
dayjs@~1.11.13:
|
||||
version "1.11.13"
|
||||
resolved "https://mirrors.tencent.com/npm/dayjs/-/dayjs-1.11.13.tgz"
|
||||
integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==
|
||||
|
||||
dayjs@~1.8.24:
|
||||
version "1.8.36"
|
||||
resolved "https://mirrors.tencent.com/npm/dayjs/-/dayjs-1.8.36.tgz"
|
||||
@@ -2981,6 +3014,13 @@ debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, d
|
||||
dependencies:
|
||||
ms "^2.1.3"
|
||||
|
||||
debug@4.3.3:
|
||||
version "4.3.3"
|
||||
resolved "https://mirrors.tencent.com/npm/debug/-/debug-4.3.3.tgz"
|
||||
integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
|
||||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
debug@4.3.6:
|
||||
version "4.3.6"
|
||||
resolved "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz"
|
||||
@@ -3139,7 +3179,7 @@ ecc-jsbn@~0.1.1:
|
||||
jsbn "~0.1.0"
|
||||
safer-buffer "^2.1.0"
|
||||
|
||||
ecdsa-sig-formatter@1.0.11:
|
||||
ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.11:
|
||||
version "1.0.11"
|
||||
resolved "https://mirrors.tencent.com/npm/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz"
|
||||
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
|
||||
@@ -3572,6 +3612,16 @@ fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-sta
|
||||
resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz"
|
||||
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
|
||||
|
||||
fast-jwt@^6.0.1:
|
||||
version "6.0.2"
|
||||
resolved "https://mirrors.tencent.com/npm/fast-jwt/-/fast-jwt-6.0.2.tgz"
|
||||
integrity sha512-dTF4bhYnuXhZYQUaxsHKqAyA5y/L/kQc4fUu0wQ0BSA0dMfcNrcv0aqR2YnVi4f7e1OnzDVU7sDsNdzl1O5EVA==
|
||||
dependencies:
|
||||
"@lukeed/ms" "^2.0.2"
|
||||
asn1.js "^5.4.1"
|
||||
ecdsa-sig-formatter "^1.0.11"
|
||||
mnemonist "^0.40.0"
|
||||
|
||||
fast-levenshtein@^2.0.6:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz"
|
||||
@@ -4217,7 +4267,7 @@ inflight@^1.0.4:
|
||||
once "^1.3.0"
|
||||
wrappy "1"
|
||||
|
||||
inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3:
|
||||
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
@@ -4909,6 +4959,16 @@ jsonfile@^6.0.1:
|
||||
optionalDependencies:
|
||||
graceful-fs "^4.1.6"
|
||||
|
||||
jsonwebtoken@9.0.0:
|
||||
version "9.0.0"
|
||||
resolved "https://mirrors.tencent.com/npm/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz"
|
||||
integrity sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==
|
||||
dependencies:
|
||||
jws "^3.2.2"
|
||||
lodash "^4.17.21"
|
||||
ms "^2.1.1"
|
||||
semver "^7.3.8"
|
||||
|
||||
jsonwebtoken@9.0.2, jsonwebtoken@^9.0.2:
|
||||
version "9.0.2"
|
||||
resolved "https://mirrors.tencent.com/npm/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz"
|
||||
@@ -5295,6 +5355,11 @@ mimic-response@^4.0.0:
|
||||
resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz"
|
||||
integrity sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==
|
||||
|
||||
minimalistic-assert@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://mirrors.tencent.com/npm/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz"
|
||||
integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
|
||||
|
||||
minimatch@^10.0.0:
|
||||
version "10.0.1"
|
||||
resolved "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz"
|
||||
@@ -5345,6 +5410,13 @@ mkdirp@^0.5.4:
|
||||
dependencies:
|
||||
minimist "^1.2.6"
|
||||
|
||||
mnemonist@^0.40.0:
|
||||
version "0.40.3"
|
||||
resolved "https://mirrors.tencent.com/npm/mnemonist/-/mnemonist-0.40.3.tgz"
|
||||
integrity sha512-Vjyr90sJ23CKKH/qPAgUKicw/v6pRoamxIEDFOF8uSgFME7DqPRpHgRTejWVjkdGg5dXj0/NyxZHZ9bcjH+2uQ==
|
||||
dependencies:
|
||||
obliterator "^2.0.4"
|
||||
|
||||
module-details-from-path@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://mirrors.tencent.com/npm/module-details-from-path/-/module-details-from-path-1.0.3.tgz"
|
||||
@@ -5477,6 +5549,11 @@ node-fetch@^2.6.7:
|
||||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
node-forge@1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://mirrors.tencent.com/npm/node-forge/-/node-forge-1.3.1.tgz"
|
||||
integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==
|
||||
|
||||
node-int64@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz"
|
||||
@@ -5524,6 +5601,11 @@ object-inspect@^1.13.3:
|
||||
resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz"
|
||||
integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==
|
||||
|
||||
obliterator@^2.0.4:
|
||||
version "2.0.5"
|
||||
resolved "https://mirrors.tencent.com/npm/obliterator/-/obliterator-2.0.5.tgz"
|
||||
integrity sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==
|
||||
|
||||
on-finished@2.4.1, on-finished@^2.4.1:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz"
|
||||
@@ -7113,6 +7195,11 @@ undici-types@~6.20.0:
|
||||
resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz"
|
||||
integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==
|
||||
|
||||
undici@^7.9.0:
|
||||
version "7.16.0"
|
||||
resolved "https://mirrors.tencent.com/npm/undici/-/undici-7.16.0.tgz"
|
||||
integrity sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==
|
||||
|
||||
universalify@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz"
|
||||
@@ -7196,6 +7283,15 @@ verror@1.10.0:
|
||||
core-util-is "1.0.2"
|
||||
extsprintf "^1.2.0"
|
||||
|
||||
verror@1.10.1:
|
||||
version "1.10.1"
|
||||
resolved "https://mirrors.tencent.com/npm/verror/-/verror-1.10.1.tgz"
|
||||
integrity sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==
|
||||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
core-util-is "1.0.2"
|
||||
extsprintf "^1.2.0"
|
||||
|
||||
vizion@~2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://mirrors.tencent.com/npm/vizion/-/vizion-2.2.1.tgz"
|
||||
|
||||
Reference in New Issue
Block a user