From 69adf77a939698f95142a95ca81da13e33f5d536 Mon Sep 17 00:00:00 2001 From: CounterFire2023 <136581895+CounterFire2023@users.noreply.github.com> Date: Thu, 18 Jan 2024 13:08:00 +0800 Subject: [PATCH] move some method to communal project --- .gitmodules | 3 + package.json | 3 +- packages/zutils | 1 + src/api.server.ts | 5 +- src/chain/BlockChain.ts | 4 +- src/chain/ERC721Reactor.ts | 4 +- src/chain/HttpRetryProvider.ts | 4 +- src/chain/WalletReactor.ts | 2 +- src/chain/chain.api.ts | 19 +- src/clock/Schedule.ts | 2 +- src/common/AsyncQueue.ts | 107 ---------- src/common/ZError.ts | 13 -- src/common/base.controller.ts | 7 - src/controllers/chain.controllers.ts | 4 +- src/controllers/task.controllers.ts | 6 +- src/controllers/token.controllers.ts | 4 +- src/decorators/nojson.ts | 2 +- src/decorators/router.ts | 142 ------------- src/decorators/singleton.ts | 29 --- src/events.ts | 4 +- src/models/Base.ts | 2 +- src/models/CheckIn.ts | 2 +- src/monitor.ts | 4 +- src/queue/chain.queue.ts | 3 +- src/queue/confirm.queue.ts | 3 +- src/redis/RedisClient.ts | 306 --------------------------- src/schedule/blocknum.schedule.ts | 2 +- src/scriptions.ts | 4 +- src/service/block.sync.service.ts | 7 +- src/service/event.batch.service.ts | 16 +- src/service/event.sync.service.ts | 4 +- src/service/explore.service.ts | 6 +- src/service/info.service.ts | 4 +- src/service/mail.service.ts | 2 +- src/service/price.service.ts | 2 +- src/utils/block.util.ts | 6 +- src/utils/contract.util.ts | 8 +- src/utils/date.util.ts | 39 ---- src/utils/event.util.ts | 45 ---- src/utils/net.util.ts | 138 ------------ src/utils/number.util.ts | 198 ----------------- src/utils/promise.util.ts | 138 ------------ src/utils/security.util.ts | 74 ------- src/utils/string.util.ts | 123 ----------- src/utils/wallet.util.ts | 125 ----------- yarn.lock | 9 + 46 files changed, 73 insertions(+), 1562 deletions(-) create mode 100644 .gitmodules create mode 160000 packages/zutils delete mode 100644 src/common/AsyncQueue.ts delete mode 100644 src/common/ZError.ts delete mode 100644 src/common/base.controller.ts delete mode 100644 src/decorators/router.ts delete mode 100644 src/decorators/singleton.ts delete mode 100644 src/redis/RedisClient.ts delete mode 100644 src/utils/date.util.ts delete mode 100644 src/utils/event.util.ts delete mode 100644 src/utils/net.util.ts delete mode 100644 src/utils/number.util.ts delete mode 100644 src/utils/promise.util.ts delete mode 100644 src/utils/security.util.ts delete mode 100644 src/utils/string.util.ts delete mode 100644 src/utils/wallet.util.ts diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..7915798 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "packages/zutils"] + path = packages/zutils + url = git@git.kingsome.cn:zhanghongliang/zutils.git diff --git a/package.json b/package.json index ddae9af..2f29b02 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,8 @@ "nodemailer": "^6.9.1", "redis": "^3.1.2", "tracer": "^1.1.6", - "web3": "^1.7.4" + "web3": "^1.7.4", + "zutils": "link:packages/zutils" }, "devDependencies": { "@types/dotenv": "^8.2.0", diff --git a/packages/zutils b/packages/zutils new file mode 160000 index 0000000..d1fccba --- /dev/null +++ b/packages/zutils @@ -0,0 +1 @@ +Subproject commit d1fccbac2657731057ab788c89fba8b2f50da78f diff --git a/src/api.server.ts b/src/api.server.ts index 2f9fcc6..ca8634b 100644 --- a/src/api.server.ts +++ b/src/api.server.ts @@ -4,11 +4,10 @@ import * as dotenv from 'dotenv' const envFile = process.env.NODE_ENV && process.env.NODE_ENV === 'production' ? `.env.production` : '.env.development' dotenv.config({ path: envFile }) import { IncomingMessage, Server, ServerResponse } from 'http' -import { RouterMap } from 'decorators/router' import { mongoose } from '@typegoose/typegoose' import logger from 'logger/logger' -import { RedisClient } from 'redis/RedisClient' import { PriceSvr } from 'service/price.service' +import { RouterMap, ZRedisClient } from 'zutils' const zReqParserPlugin = require('plugins/zReqParser') @@ -99,7 +98,7 @@ export class ApiServer { logger.log(`DB Connection Error: ${err.message}`) } let opts = { url: process.env.REDIS } - new RedisClient(opts) + new ZRedisClient(opts) logger.log('REDIS Connected') } private initSchedules() { diff --git a/src/chain/BlockChain.ts b/src/chain/BlockChain.ts index 598cabd..a9cabaa 100644 --- a/src/chain/BlockChain.ts +++ b/src/chain/BlockChain.ts @@ -1,4 +1,4 @@ -import { singleton } from 'decorators/singleton' +import { singleton } from 'zutils' import { TaskType } from 'models/RequestTask' import { ConfirmQueue } from 'queue/confirm.queue' import Web3 from 'web3' @@ -8,7 +8,7 @@ import { ERC721Reactor } from './ERC721Reactor' import { HttpRetryProvider } from './HttpRetryProvider' import { WalletReactor } from './WalletReactor' import { DistributorReactor } from './DistributorReactor' -import { fromTokenMinimalUnit, safeNumberToBN, toBN } from 'utils/number.util' +import { fromTokenMinimalUnit, safeNumberToBN } from 'zutils/utils/number.util' import { AllChains } from './allchain' import assert from 'assert' import { IPriceData } from 'structs/PriceData' diff --git a/src/chain/ERC721Reactor.ts b/src/chain/ERC721Reactor.ts index 3c6a845..b49052c 100644 --- a/src/chain/ERC721Reactor.ts +++ b/src/chain/ERC721Reactor.ts @@ -1,5 +1,5 @@ -import { timeoutFetch } from 'utils/net.util' -import { getFormattedIpfsUrl } from 'utils/wallet.util' +import { timeoutFetch } from 'zutils/utils/net.util' +import { getFormattedIpfsUrl } from 'zutils/utils/wallet.util' import Web3 from 'web3' import { Contract } from 'web3-eth-contract' import { Account } from 'web3-core' diff --git a/src/chain/HttpRetryProvider.ts b/src/chain/HttpRetryProvider.ts index 8a06753..89de212 100644 --- a/src/chain/HttpRetryProvider.ts +++ b/src/chain/HttpRetryProvider.ts @@ -1,8 +1,8 @@ import axios from 'axios' import deepmerge from 'deepmerge' import logger from 'logger/logger' -import { generateHeader } from 'utils/net.util' -import { retry } from 'utils/promise.util' +import { generateHeader } from 'zutils/utils/net.util' +import { retry } from 'zutils/utils/promise.util' const defaultOptions = { retry: { diff --git a/src/chain/WalletReactor.ts b/src/chain/WalletReactor.ts index fdc97c3..ba47fb0 100644 --- a/src/chain/WalletReactor.ts +++ b/src/chain/WalletReactor.ts @@ -2,7 +2,7 @@ import { Contract } from 'web3-eth-contract' import Web3 from 'web3' import { Account } from 'web3-core' import { ZERO_BYTES32 } from 'common/Constants' -import { generateRandomBytes32 } from 'utils/wallet.util' +import { generateRandomBytes32 } from 'zutils/utils/wallet.util' const abi = require('../../config/abis/BEMultiSigWallet.json').abi /** diff --git a/src/chain/chain.api.ts b/src/chain/chain.api.ts index 750a5dc..c0e5784 100644 --- a/src/chain/chain.api.ts +++ b/src/chain/chain.api.ts @@ -1,22 +1,11 @@ -import fetch, { Response, RequestInit } from 'node-fetch' -import { retry } from 'utils/promise.util' +import { timeoutFetch } from 'zutils/utils/net.util' +import { retry } from 'zutils/utils/promise.util' const DEFAULT_TIMEOUT = 30000 -const AbortController = globalThis.AbortController -const request = async (url: string, options: RequestInit) => { +const request = async (url: string, options: any) => { options.timeout = options.timeout || DEFAULT_TIMEOUT - const controller = new AbortController() - let res = Promise.race([ - await fetch(url, { ...options, signal: controller.signal }), - new Promise((_, reject) => - setTimeout(() => { - controller.abort() - reject(new Error('timeout')) - }, options.timeout), - ), - ]) - return res + return timeoutFetch(url, options, options.timeout) } const requestChain = async (rpc: string, method: string, params: any) => { diff --git a/src/clock/Schedule.ts b/src/clock/Schedule.ts index 5b9466f..97b7625 100644 --- a/src/clock/Schedule.ts +++ b/src/clock/Schedule.ts @@ -1,4 +1,4 @@ -import { singleton } from '../decorators/singleton' +import { singleton } from 'zutils' import Clock from './ClockTimer' import { Delayed } from './Delayed' diff --git a/src/common/AsyncQueue.ts b/src/common/AsyncQueue.ts deleted file mode 100644 index 792c27e..0000000 --- a/src/common/AsyncQueue.ts +++ /dev/null @@ -1,107 +0,0 @@ -type Callback = () => Promise - -export type AsyncQueue = { - push: (task: Callback) => Promise - flush: () => Promise - size: number -} - -/** - * Ensures that each callback pushed onto the queue is executed in series. - * Such a quetie 😻 - * @param opts.dedupeConcurrent If dedupeConcurrent is `true` it ensures that if multiple - * tasks are pushed onto the queue while there is an active task, only the - * last one will be executed, once the active task has completed. - * e.g. in the below example, only 0 and 3 will be executed. - * ``` - * const queue = createAsyncQueue({ dedupeConcurrent: true }) - * queue.push(async () => console.log(0)) // returns 0 - * queue.push(async () => console.log(1)) // returns 3 - * queue.push(async () => console.log(2)) // returns 3 - * queue.push(async () => console.log(3)) // returns 3 - * ``` - * */ -export function createAsyncQueue(opts = { dedupeConcurrent: false }): AsyncQueue { - const { dedupeConcurrent } = opts - let queue: Callback[] = [] - let running: Promise | undefined - let nextPromise = new DeferredPromise() - const push = (task: Callback) => { - let taskPromise = new DeferredPromise() - if (dedupeConcurrent) { - queue = [] - if (nextPromise.started) nextPromise = new DeferredPromise() - taskPromise = nextPromise - } - queue.push(() => { - taskPromise.started = true - task().then(taskPromise.resolve).catch(taskPromise.reject) - return taskPromise.promise - }) - if (!running) running = start() - return taskPromise.promise - } - const start = async () => { - while (queue.length) { - const task = queue.shift()! - await task().catch(() => {}) - } - running = undefined - } - return { - push, - flush: () => running || Promise.resolve(), - get size() { - return queue.length - }, - } -} - -export const createAsyncQueues = (opts = { dedupeConcurrent: false }) => { - const queues: { [queueId: string]: AsyncQueue } = {} - const push = (queueId: string, task: Callback) => { - if (!queues[queueId]) queues[queueId] = createAsyncQueue(opts) - return queues[queueId].push(task) - } - const flush = (queueId: string) => { - if (!queues[queueId]) queues[queueId] = createAsyncQueue(opts) - return queues[queueId].flush() - } - return { push, flush } -} - -class DeferredPromise { - started = false - resolve: (x: T | PromiseLike) => void = () => {} - reject: (x: E) => void = () => {} - promise: Promise - - constructor() { - this.promise = new Promise((res, rej) => { - this.resolve = res - this.reject = rej - }) - } -} - -// function main() { -// const queue = createAsyncQueue() -// queue.push(async () => { -// console.log(0) -// }) // returns 0 -// queue.push(async () => { -// console.log(1) - -// return new Promise((resolve, reject) => { -// setTimeout(() => { -// console.log('12') -// resolve() -// }, 1000) -// }) -// }) // returns 3 -// queue.push(async () => console.log(2)) // returns 3 -// queue.push(async () => console.log(3)) // returns 3 -// console.log('hi') -// } - -// main() diff --git a/src/common/ZError.ts b/src/common/ZError.ts deleted file mode 100644 index ffe0623..0000000 --- a/src/common/ZError.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { FastifyError } from 'fastify' - -export class ZError implements FastifyError { - code: string - statusCode?: number - message: string - name: string - - constructor(statusCode: number, message: string) { - this.statusCode = statusCode - this.message = message - } -} diff --git a/src/common/base.controller.ts b/src/common/base.controller.ts deleted file mode 100644 index 2fda6fe..0000000 --- a/src/common/base.controller.ts +++ /dev/null @@ -1,7 +0,0 @@ -import fastify = require('fastify') - -export const ROLE_ANON = 'anon' -class BaseController { - aotoRoute(req: fastify.FastifyRequest, res) {} -} -export default BaseController diff --git a/src/controllers/chain.controllers.ts b/src/controllers/chain.controllers.ts index 003cbaf..68ae3a1 100644 --- a/src/controllers/chain.controllers.ts +++ b/src/controllers/chain.controllers.ts @@ -1,11 +1,9 @@ -import BaseController from 'common/base.controller' -import { role, router } from 'decorators/router' import { ChainTask, ChainTaskClass } from 'models/ChainTask' import { RequestTask } from 'models/RequestTask' import { ChainQueue } from 'queue/chain.queue' import { DocumentType } from '@typegoose/typegoose' import { BlockChain } from 'chain/BlockChain' -import { ZError } from 'common/ZError' +import { BaseController, role, router, ZError } from 'zutils' class ChainController extends BaseController { @role('anon') diff --git a/src/controllers/task.controllers.ts b/src/controllers/task.controllers.ts index 95347aa..d092d96 100644 --- a/src/controllers/task.controllers.ts +++ b/src/controllers/task.controllers.ts @@ -1,9 +1,7 @@ -import { ZError } from 'common/ZError' -import BaseController from 'common/base.controller' -import { role, router } from 'decorators/router' import { CheckIn } from 'models/CheckIn' import { NftHolder } from 'models/NftHolder' -import { getMonthBegin, getNDayAgo } from 'utils/date.util' +import { BaseController, role, router, ZError } from 'zutils' +import { getMonthBegin, getNDayAgo } from 'zutils/utils/date.util' class TaskController extends BaseController { @role('anon') diff --git a/src/controllers/token.controllers.ts b/src/controllers/token.controllers.ts index 94e0665..98dd42b 100644 --- a/src/controllers/token.controllers.ts +++ b/src/controllers/token.controllers.ts @@ -1,7 +1,5 @@ import { BlockChain } from 'chain/BlockChain' -import { ZError } from 'common/ZError' -import BaseController from 'common/base.controller' -import { role, router } from 'decorators/router' +import { BaseController, role, router, ZError } from 'zutils' class TokenController extends BaseController { @role('anon') diff --git a/src/decorators/nojson.ts b/src/decorators/nojson.ts index 90f4ba2..6a0e96f 100644 --- a/src/decorators/nojson.ts +++ b/src/decorators/nojson.ts @@ -1,5 +1,5 @@ import 'reflect-metadata' -import { singleton } from './singleton' +import { singleton } from 'zutils' @singleton export class NoJsonClass { diff --git a/src/decorators/router.ts b/src/decorators/router.ts deleted file mode 100644 index 366aa6f..0000000 --- a/src/decorators/router.ts +++ /dev/null @@ -1,142 +0,0 @@ -import BaseController from '../common/base.controller' - -export class RouterData { - target?: any - method?: string - path?: string - fun?: Function -} - -export class RouterMap { - static decoratedRouters: Map< - Function, - { - roles?: string[] - permissions?: string[][] - data?: RouterData[] - depts?: string[] - } - > = new Map() -} - -export function router(route?: string) { - return (target: BaseController, name: string, value: PropertyDescriptor) => { - if (!route) { - const controller = target.constructor.name - const controllerName = controller.toLowerCase().replace('.controller', '') - route = 'all ' + ['', controllerName, name].join('/') - } - const split = route.split(' ') - if (split.length > 2) { - throw new Error('路由中只允许一个空格') - } - const [method, path] = split - // @ts-ignore - const key = target[name] - let routerData = new RouterData() - routerData.target = target - routerData.method = method - routerData.path = path - // @ts-ignore - routerData.fun = target[name] - - if (RouterMap.decoratedRouters.has(key)) { - let objCurrent = RouterMap.decoratedRouters.get(key) - if (!objCurrent.data) { - objCurrent.data = [routerData] - } else { - objCurrent.data.push(routerData) - } - // @ts-ignore - RouterMap.decoratedRouters.set(target[name], objCurrent) - } else { - let routerObj = { - data: [routerData], - } - // @ts-ignore - RouterMap.decoratedRouters.set(target[name], routerObj) - } - } -} - -export function role(roles?: string | string[]) { - return (target: BaseController, name: string, value: PropertyDescriptor) => { - let roleList: string[] = [] - if (roles) { - if (Array.isArray(roles)) { - roleList = roles - } else { - roleList = [roles] - } - } - // @ts-ignore - const key = target[name] - let roleObj = { roles: roleList } - if (RouterMap.decoratedRouters.has(key)) { - let objCurrent = RouterMap.decoratedRouters.get(key) - Object.assign(objCurrent, roleObj) - // @ts-ignore - RouterMap.decoratedRouters.set(target[name], objCurrent) - } else { - // @ts-ignore - RouterMap.decoratedRouters.set(target[name], roleObj) - } - } -} - -export function permission(permissions?: string | string[]) { - return (target: BaseController, name: string, value: PropertyDescriptor) => { - let permissionList: string[][] = [[]] - if (permissions) { - if (Array.isArray(permissions)) { - let arr = [] - for (let sub of permissions) { - arr.push(sub.split(':')) - } - permissionList = arr - } else { - permissionList = [permissions.split(':')] - } - } - // @ts-ignore - const key = target[name] - let permissionObj = { permissions: permissionList } - if (RouterMap.decoratedRouters.has(key)) { - let objCurrent = RouterMap.decoratedRouters.get(key) - Object.assign(objCurrent, permissionObj) - // @ts-ignore - RouterMap.decoratedRouters.set(target[name], objCurrent) - } else { - // @ts-ignore - RouterMap.decoratedRouters.set(target[name], permissionObj) - } - } -} - -/** - * 有dept修饰器的, 需要验证部门id是否存在 - */ -export function dept(depts?: string | string[]) { - return (target: BaseController, name: string, value: PropertyDescriptor) => { - let deptList: string[] = [] - if (depts) { - if (Array.isArray(depts)) { - deptList = depts - } else { - deptList = [depts] - } - } - // @ts-ignore - const key = target[name] - let deptObj = { depts: deptList } - if (RouterMap.decoratedRouters.has(key)) { - let objCurrent = RouterMap.decoratedRouters.get(key) - Object.assign(objCurrent, deptObj) - // @ts-ignore - RouterMap.decoratedRouters.set(target[name], objCurrent) - } else { - // @ts-ignore - RouterMap.decoratedRouters.set(target[name], deptObj) - } - } -} diff --git a/src/decorators/singleton.ts b/src/decorators/singleton.ts deleted file mode 100644 index c43327d..0000000 --- a/src/decorators/singleton.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * 单例化一个class - * 使用方法: - * @singleton - * class Test {} - * new Test() === new Test() // returns `true` - * 也可以不使用 decorator - * const TestSingleton = singleton(Test) - * new TestSingleton() === new TestSingleton() //returns 'true' - */ - -export const SINGLETON_KEY = Symbol() - -export type Singleton any> = T & { - [SINGLETON_KEY]: T extends new (...args: any[]) => infer I ? I : never -} -export const singleton = any>(classTarget: T) => - new Proxy(classTarget, { - construct(target: Singleton, argumentsList, newTarget) { - // Skip proxy for children - if (target.prototype !== newTarget.prototype) { - return Reflect.construct(target, argumentsList, newTarget) - } - if (!target[SINGLETON_KEY]) { - target[SINGLETON_KEY] = Reflect.construct(target, argumentsList, newTarget) - } - return target[SINGLETON_KEY] - }, - }) diff --git a/src/events.ts b/src/events.ts index eee70c8..00da7b2 100644 --- a/src/events.ts +++ b/src/events.ts @@ -1,6 +1,5 @@ import * as dotenv from 'dotenv' import logger from 'logger/logger' -import { RedisClient } from 'redis/RedisClient' const envFile = process.env.NODE_ENV && process.env.NODE_ENV === 'production' ? `.env.production` : '.env.development' dotenv.config({ path: envFile }) @@ -10,6 +9,7 @@ import 'common/Extend' import { AllChains, IChain } from 'chain/allchain' import { EventBatchSvr } from 'service/event.batch.service' import { IEventCfg } from 'interface/IEventCfg' +import { ZRedisClient } from 'zutils' let svrs: any[] = [] let lock = false @@ -55,7 +55,7 @@ async function parseAllEvents() { ;(async () => { let opts = { url: process.env.REDIS } - new RedisClient(opts) + new ZRedisClient(opts) logger.info('REDIS Connected') await initEventSvrs() setInterval(function () { diff --git a/src/models/Base.ts b/src/models/Base.ts index 71aee4d..9fec851 100644 --- a/src/models/Base.ts +++ b/src/models/Base.ts @@ -6,7 +6,7 @@ import { plugin, ReturnModelType } from '@typegoose/typegoose' import findOrCreate from 'mongoose-findorcreate' import { Connection } from 'mongoose' import { ObjectId } from 'bson' -import { isTrue } from '../utils/string.util' +import { isTrue } from 'zutils/utils/string.util' import { AnyParamConstructor } from '@typegoose/typegoose/lib/types' const jsonExcludeKeys = ['updatedAt', '__v'] diff --git a/src/models/CheckIn.ts b/src/models/CheckIn.ts index 2d547c1..0f12c52 100644 --- a/src/models/CheckIn.ts +++ b/src/models/CheckIn.ts @@ -1,7 +1,7 @@ import { getModelForClass, index, modelOptions, prop } from '@typegoose/typegoose' import { dbconn } from 'decorators/dbconn' import { BaseModule } from './Base' -import { formatDate, yesterday } from 'utils/date.util' +import { formatDate, yesterday } from 'zutils/utils/date.util' import { logger } from '@typegoose/typegoose/lib/logSettings' @dbconn() diff --git a/src/monitor.ts b/src/monitor.ts index d1c114e..53773f5 100644 --- a/src/monitor.ts +++ b/src/monitor.ts @@ -1,6 +1,5 @@ import * as dotenv from 'dotenv' import logger from 'logger/logger' -import { RedisClient } from 'redis/RedisClient' const envFile = process.env.NODE_ENV && process.env.NODE_ENV === 'production' ? `.env.production` : '.env.development' dotenv.config({ path: envFile }) import { EventSyncSvr } from 'service/event.sync.service' @@ -8,6 +7,7 @@ import { NftTransferEvent } from 'models/NftTransferEvent' import { FtTransferEvent } from 'models/FtTransferEvent' import 'common/Extend' +import { ZRedisClient } from 'zutils' let svrs: any[] = [] let lock = false @@ -52,7 +52,7 @@ async function parseAllEvents() { async function main() { let opts = { url: process.env.REDIS } - new RedisClient(opts) + new ZRedisClient(opts) logger.info('REDIS Connected') await initEventSvrs() setInterval(function () { diff --git a/src/queue/chain.queue.ts b/src/queue/chain.queue.ts index cd79f99..f822f3a 100644 --- a/src/queue/chain.queue.ts +++ b/src/queue/chain.queue.ts @@ -1,5 +1,4 @@ -import { AsyncQueue, createAsyncQueue } from 'common/AsyncQueue' -import { singleton } from 'decorators/singleton' +import { AsyncQueue, createAsyncQueue, singleton } from 'zutils' import { DocumentType } from '@typegoose/typegoose' import { ReqTaskStatus, RequestTaskClass } from 'models/RequestTask' import { BlockChain } from 'chain/BlockChain' diff --git a/src/queue/confirm.queue.ts b/src/queue/confirm.queue.ts index 40eab9a..5ae39c1 100644 --- a/src/queue/confirm.queue.ts +++ b/src/queue/confirm.queue.ts @@ -1,5 +1,4 @@ -import { AsyncQueue, createAsyncQueue } from 'common/AsyncQueue' -import { singleton } from 'decorators/singleton' +import { AsyncQueue, createAsyncQueue, singleton } from 'zutils' import { DocumentType } from '@typegoose/typegoose' import { ReqTaskStatus, RequestTaskClass } from 'models/RequestTask' import Web3 from 'web3' diff --git a/src/redis/RedisClient.ts b/src/redis/RedisClient.ts deleted file mode 100644 index 2eee25c..0000000 --- a/src/redis/RedisClient.ts +++ /dev/null @@ -1,306 +0,0 @@ -import { resolveCname } from 'dns' -import redis from 'redis' -import { promisify } from 'util' -import { singleton } from '../decorators/singleton' - -type Callback = (...args: any[]) => void - -@singleton -export class RedisClient { - public pub: redis.RedisClient - public sub: redis.RedisClient - - protected subscribeAsync: any - protected unsubscribeAsync: any - protected publishAsync: any - - protected subscriptions: { [channel: string]: Callback[] } = {} - - protected smembersAsync: any - protected sismemberAsync: any - protected hgetAsync: any - protected hlenAsync: any - protected pubsubAsync: any - protected incrAsync: any - protected decrAsync: any - - constructor(opts?: redis.ClientOpts) { - this.sub = redis.createClient(opts) - this.pub = redis.createClient(opts) - - // no listener limit - this.sub.setMaxListeners(0) - - // create promisified pub/sub methods. - this.subscribeAsync = promisify(this.sub.subscribe).bind(this.sub) - this.unsubscribeAsync = promisify(this.sub.unsubscribe).bind(this.sub) - - this.publishAsync = promisify(this.pub.publish).bind(this.pub) - - // create promisified redis methods. - this.smembersAsync = promisify(this.pub.smembers).bind(this.pub) - this.sismemberAsync = promisify(this.pub.sismember).bind(this.pub) - this.hlenAsync = promisify(this.pub.hlen).bind(this.pub) - this.hgetAsync = promisify(this.pub.hget).bind(this.pub) - this.pubsubAsync = promisify(this.pub.pubsub).bind(this.pub) - this.decrAsync = promisify(this.pub.decr).bind(this.pub) - this.incrAsync = promisify(this.pub.incr).bind(this.pub) - } - - public async subscribe(topic: string, callback: Callback) { - if (!this.subscriptions[topic]) { - this.subscriptions[topic] = [] - } - - this.subscriptions[topic].push(callback) - - if (this.sub.listeners('message').length === 0) { - this.sub.addListener('message', this.handleSubscription) - } - - await this.subscribeAsync(topic) - - return this - } - - public async unsubscribe(topic: string, callback?: Callback) { - if (callback) { - const index = this.subscriptions[topic].indexOf(callback) - this.subscriptions[topic].splice(index, 1) - } else { - this.subscriptions[topic] = [] - } - - if (this.subscriptions[topic].length === 0) { - await this.unsubscribeAsync(topic) - } - - return this - } - - public async publish(topic: string, data: any) { - if (data === undefined) { - data = false - } - - await this.publishAsync(topic, JSON.stringify(data)) - } - - public async exists(roomId: string): Promise { - return (await this.pubsubAsync('channels', roomId)).length > 0 - } - - public async setex(key: string, value: string, seconds: number) { - return new Promise(resolve => this.pub.setex(key, seconds, value, resolve)) - } - - public async expire(key: string, seconds: number) { - return new Promise(resolve => this.pub.expire(key, seconds, resolve)) - } - - public async get(key: string): Promise { - return new Promise((resolve, reject) => { - this.pub.get(key, (err, data: string | null) => { - if (err) { - return reject(err) - } - resolve(data) - }) - }) - } - - public async set(key: string, val: string) { - return new Promise(resolve => { - this.pub.set(key, val, () => { - resolve && resolve('') - }) - }) - } - - public async del(roomId: string) { - return new Promise(resolve => { - this.pub.del(roomId, resolve) - }) - } - - public async sadd(key: string, value: any) { - return new Promise(resolve => { - this.pub.sadd(key, value, resolve) - }) - } - - public async smembers(key: string): Promise { - return await this.smembersAsync(key) - } - - public async sismember(key: string, field: string): Promise { - return await this.sismemberAsync(key, field) - } - - public async srem(key: string, value: any) { - return new Promise(resolve => { - this.pub.srem(key, value, resolve) - }) - } - - public async scard(key: string) { - return new Promise((resolve, reject) => { - this.pub.scard(key, (err, data) => { - if (err) { - return reject(err) - } - resolve(data) - }) - }) - } - public async srandmember(key: string) { - return new Promise((resolve, reject) => { - this.pub.srandmember(key, (err, data) => { - if (err) { - return reject(err) - } - resolve(data) - }) - }) - } - - public async sinter(...keys: string[]) { - return new Promise((resolve, reject) => { - this.pub.sinter(...keys, (err, data) => { - if (err) { - return reject(err) - } - resolve(data) - }) - }) - } - - public async zadd(key: string, value: any, member: string) { - return new Promise(resolve => { - this.pub.zadd(key, value, member, resolve) - }) - } - public async zrangebyscore(key: string, min: number, max: number) { - return new Promise((resolve, reject) => { - this.pub.zrangebyscore(key, min, max, 'withscores', (err, data) => { - if (err) { - return reject(err) - } - resolve(data) - }) - }) - } - - public async zcard(key: string) { - return new Promise((resolve, reject) => { - this.pub.zcard(key, (err, data) => { - if (err) { - return reject(err) - } - resolve(data) - }) - }) - } - - public async zcount(key: string, min: number, max: number) { - return new Promise((resolve, reject) => { - this.pub.zcount(key, min, max, (err, data) => { - if (err) { - return reject(err) - } - resolve(data) - }) - }) - } - - public async zrevrank(key: string, member: string) { - return new Promise((resolve, reject) => { - this.pub.zrevrank(key, member, (err, data) => { - if (err) { - return reject(err) - } - resolve(data) - }) - }) - } - - public async zscore(key: string, member: string) { - return new Promise((resolve, reject) => { - this.pub.zscore(key, member, (err, data) => { - if (err) { - return reject(err) - } - resolve(data) - }) - }) - } - - public async zrevrange(key: string, start: number, end: number) { - return new Promise((resolve, reject) => { - this.pub.zrevrange(key, start, end, 'withscores', (err, data) => { - if (err) { - return reject(err) - } - resolve(data) - }) - }) - } - - public async hset(key: string, field: string, value: string) { - return new Promise(resolve => { - this.pub.hset(key, field, value, resolve) - }) - } - - public async hincrby(key: string, field: string, value: number) { - return new Promise(resolve => { - this.pub.hincrby(key, field, value, resolve) - }) - } - - public async hget(key: string, field: string) { - return await this.hgetAsync(key, field) - } - - public async hgetall(key: string) { - return new Promise<{ [key: string]: string }>((resolve, reject) => { - this.pub.hgetall(key, (err, values) => { - if (err) { - return reject(err) - } - resolve(values) - }) - }) - } - - public async hdel(key: string, field: string) { - return new Promise((resolve, reject) => { - this.pub.hdel(key, field, (err, ok) => { - if (err) { - return reject(err) - } - resolve(ok) - }) - }) - } - - public async hlen(key: string): Promise { - return await this.hlenAsync(key) - } - - public async incr(key: string): Promise { - return await this.incrAsync(key) - } - - public async decr(key: string): Promise { - return await this.decrAsync(key) - } - - protected handleSubscription = (channel: string, message: string) => { - if (this.subscriptions[channel]) { - for (let i = 0, l = this.subscriptions[channel].length; i < l; i++) { - this.subscriptions[channel][i](JSON.parse(message)) - } - } - } -} diff --git a/src/schedule/blocknum.schedule.ts b/src/schedule/blocknum.schedule.ts index c3f38ef..82642ae 100644 --- a/src/schedule/blocknum.schedule.ts +++ b/src/schedule/blocknum.schedule.ts @@ -1,5 +1,5 @@ import { BlockChain } from 'chain/BlockChain' -import { singleton } from 'decorators/singleton' +import { singleton } from 'zutils' import logger from 'logger/logger' import * as schedule from 'node-schedule' diff --git a/src/scriptions.ts b/src/scriptions.ts index 74ad871..781dd53 100644 --- a/src/scriptions.ts +++ b/src/scriptions.ts @@ -1,6 +1,5 @@ import * as dotenv from 'dotenv' import logger from 'logger/logger' -import { RedisClient } from 'redis/RedisClient' const envFile = process.env.NODE_ENV && process.env.NODE_ENV === 'production' ? `.env.production` : '.env.development' dotenv.config({ path: envFile }) const scriptions = require('../config/scription_list.json') @@ -11,6 +10,7 @@ import { BlockSyncSvr } from 'service/block.sync.service' import { IScriptionCfg } from 'interface/IScriptionCfg' import { buildScriptionFilters } from 'utils/block.util' import { CheckIn } from 'models/CheckIn' +import { ZRedisClient } from 'zutils' let svrs: any[] = [] let lock = false @@ -69,7 +69,7 @@ async function parseAllEvents() { ;(async () => { let opts = { url: process.env.REDIS } - new RedisClient(opts) + new ZRedisClient(opts) logger.info('REDIS Connected') await initEventSvrs() setInterval(function () { diff --git a/src/service/block.sync.service.ts b/src/service/block.sync.service.ts index 125c92e..582d4d0 100644 --- a/src/service/block.sync.service.ts +++ b/src/service/block.sync.service.ts @@ -2,9 +2,10 @@ import { IChain } from 'chain/allchain' import { retryEthBlockNumber } from 'chain/chain.api' import { IScriptionCfg } from 'interface/IScriptionCfg' import logger from 'logger/logger' -import { RedisClient } from 'redis/RedisClient' + import { getPastBlocksIter } from 'utils/block.util' -import { formatDate } from 'utils/date.util' +import { ZRedisClient } from 'zutils' +import { formatDate } from 'zutils/utils/date.util' export class BlockSyncSvr { chainCfg: IChain @@ -28,7 +29,7 @@ export class BlockSyncSvr { async execute() { let currentBlock = await retryEthBlockNumber(this.rpc) - let blockStr = await new RedisClient().get(this.redisKey) + let blockStr = await new ZRedisClient().get(this.redisKey) if (blockStr) { this.fromBlock = Math.max(parseInt(blockStr), this.fromBlock) } diff --git a/src/service/event.batch.service.ts b/src/service/event.batch.service.ts index 9aeee58..3763112 100644 --- a/src/service/event.batch.service.ts +++ b/src/service/event.batch.service.ts @@ -3,13 +3,13 @@ import { batchEthLogs, ethGetLogs, retryEthBlockNumber } from 'chain/chain.api' import logger from 'logger/logger' import { GeneralEvent } from 'models/GeneralEvent' -import { RedisClient } from 'redis/RedisClient' -import { decodeEvent, getTopics } from 'utils/event.util' +import { decodeEvent, getTopics } from 'zutils/utils/chain.util' import { NftHolder } from 'models/NftHolder' import { TokenHolder } from 'models/TokenHolder' import { NftStake } from 'models/NftStake' import { IEventCfg } from 'interface/IEventCfg' +import { ZRedisClient } from 'zutils' let eventProcessers = { NftHolder: NftHolder, @@ -31,7 +31,7 @@ export class EventBatchSvr { for (let cfg of this.eventCfgs) { this.fromBlock = Math.min(this.fromBlock, cfg.fromBlock) if (!cfg.topic) { - cfg.topic = getTopics(cfg) + cfg.topic = getTopics(cfg.abi) } this.processer.set((cfg.address + cfg.topic).toLowerCase(), cfg) } @@ -42,7 +42,7 @@ export class EventBatchSvr { async execute() { let currentBlock = await retryEthBlockNumber(this.rpc) let toBlock = parseInt(currentBlock.result, 16) - let blockStr = await new RedisClient().get(this.redisKey) + let blockStr = await new ZRedisClient().get(this.redisKey) if (blockStr) { this.fromBlock = Math.max(parseInt(blockStr), this.fromBlock) } @@ -105,9 +105,9 @@ export class EventBatchSvr { for (let cfg of this.eventCfgs) { cfg.fromBlock = nextBlock const redisKey = this.buildRedisKey(cfg) - await new RedisClient().set(redisKey, cfg.fromBlock + '') + await new ZRedisClient().set(redisKey, cfg.fromBlock + '') } - await new RedisClient().set(this.redisKey, nextBlock + '') + await new ZRedisClient().set(this.redisKey, nextBlock + '') } buildRedisKey(cfg: IEventCfg) { @@ -116,7 +116,7 @@ export class EventBatchSvr { async fixBlockNumber(cfg: IEventCfg) { const redisKey = this.buildRedisKey(cfg) - let blockStr = await new RedisClient().get(redisKey) + let blockStr = await new ZRedisClient().get(redisKey) if (blockStr) { cfg.fromBlock = Math.max(parseInt(blockStr), cfg.fromBlock) } @@ -153,7 +153,7 @@ export class EventBatchSvr { } event.chain = this.chainCfg.id + '' event.event = cfg.event - let result = decodeEvent(cfg, event) + let result = decodeEvent(cfg.abi, event) // cfg.fromBlock = Math.max (parseInt(event.blockNumber, 16) + 1, cfg.fromBlock) event.decodedData = result const record = await GeneralEvent.saveEvent(event) diff --git a/src/service/event.sync.service.ts b/src/service/event.sync.service.ts index 6d7f052..6896b43 100644 --- a/src/service/event.sync.service.ts +++ b/src/service/event.sync.service.ts @@ -2,10 +2,10 @@ import assert from 'assert' import { AllChains } from 'chain/allchain' import { HttpRetryProvider } from 'chain/HttpRetryProvider' import logger from 'logger/logger' -import { RedisClient } from 'redis/RedisClient' import { clearTimeCache, getPastEventsIter, processEvents } from 'utils/contract.util' import Web3 from 'web3' +import { ZRedisClient } from 'zutils' export class EventSyncSvr { web3: Web3 @@ -47,7 +47,7 @@ export class EventSyncSvr { async parseEvents() { let currentBlock = await this.web3.eth.getBlockNumber() - let blockStr = await new RedisClient().get(this.blockKey) + let blockStr = await new ZRedisClient().get(this.blockKey) if (blockStr) { this.fromBlock = Math.max(parseInt(blockStr), this.fromBlock) } diff --git a/src/service/explore.service.ts b/src/service/explore.service.ts index dbbd9ad..45f14e2 100644 --- a/src/service/explore.service.ts +++ b/src/service/explore.service.ts @@ -1,6 +1,6 @@ import axios from 'axios' import logger from 'logger/logger' -import { RedisClient } from 'redis/RedisClient' +import { ZRedisClient } from 'zutils' export function queryNftTxCount(address: string) { let data = { @@ -67,7 +67,7 @@ export class ExploreNftTxSvr { async parseEvents() { logger.info(`query nft tx list:: address: ${this.address}`) - let blockStr = await new RedisClient().get(this.blockKey) + let blockStr = await new ZRedisClient().get(this.blockKey) if (blockStr) { this.lastBlock = Math.max(parseInt(blockStr), this.lastBlock) } @@ -116,6 +116,6 @@ export class ExploreNftTxSvr { } page += 1 } - new RedisClient().set(this.blockKey, maxBlock + '') + new ZRedisClient().set(this.blockKey, maxBlock + '') } } diff --git a/src/service/info.service.ts b/src/service/info.service.ts index 4cfdca4..6d2e0d6 100644 --- a/src/service/info.service.ts +++ b/src/service/info.service.ts @@ -1,7 +1,7 @@ import axios from 'axios' -import { singleton } from 'decorators/singleton' +import { singleton } from 'zutils' import logger from 'logger/logger' -import { hmacSha256 } from 'utils/security.util' +import { hmacSha256 } from 'zutils/utils/security.util' const REPORT_TASK_URI = '/api/internal/update_task' diff --git a/src/service/mail.service.ts b/src/service/mail.service.ts index 639bb94..d79ca3e 100644 --- a/src/service/mail.service.ts +++ b/src/service/mail.service.ts @@ -1,4 +1,4 @@ -import { singleton } from 'decorators/singleton' +import { singleton } from 'zutils' import logger from 'logger/logger' import { createTransport, Transporter } from 'nodemailer' import Mail from 'nodemailer/lib/mailer' diff --git a/src/service/price.service.ts b/src/service/price.service.ts index 59211eb..32cb3fe 100644 --- a/src/service/price.service.ts +++ b/src/service/price.service.ts @@ -1,5 +1,5 @@ import { BlockChain } from 'chain/BlockChain' -import { singleton } from 'decorators/singleton' +import { singleton } from 'zutils' import * as schedule from 'node-schedule' import logger from 'logger/logger' import axios from 'axios' diff --git a/src/utils/block.util.ts b/src/utils/block.util.ts index 2aecea6..6d28f09 100644 --- a/src/utils/block.util.ts +++ b/src/utils/block.util.ts @@ -1,8 +1,8 @@ import { batchEthBlocks } from 'chain/chain.api' import { IScriptionCfg } from 'interface/IScriptionCfg' import logger from 'logger/logger' -import { RedisClient } from 'redis/RedisClient' -import { utf8ToHex } from './string.util' +import { ZRedisClient } from 'zutils' +import { utf8ToHex } from 'zutils/utils/string.util' const MAX_BATCH_AMOUNT = +process.env.MAX_BLOCK_BATCH_AMOUNT const REQUEST_INTERVAL = 0.5 * 1000 @@ -80,7 +80,7 @@ export async function getPastBlocks({ if (retryCount > 0) { blocks.sort((a, b) => parseInt(a.number) - parseInt(b.number)) } - await new RedisClient().set(redisKey, blockNumber + amount + '') + await new ZRedisClient().set(redisKey, blockNumber + amount + '') await new Promise(resolve => setTimeout(resolve, REQUEST_INTERVAL)) } catch (e) { logger.log(e.message || e) diff --git a/src/utils/contract.util.ts b/src/utils/contract.util.ts index caa3751..12af5c7 100644 --- a/src/utils/contract.util.ts +++ b/src/utils/contract.util.ts @@ -1,7 +1,7 @@ import logger from 'logger/logger' -import { toBN } from './number.util' +import { toBN } from 'zutils/utils/number.util' import { BN } from 'ethereumjs-util' -import { RedisClient } from 'redis/RedisClient' +import { ZRedisClient } from 'zutils' const ONE = toBN(1) const TWO = toBN(2) @@ -192,10 +192,10 @@ export function* getPastEventsIter({ yield getPastEvents({ contract, event, fromBlock: from, toBlock: to, options }) from = to.add(ONE) to = to.add(queryRange) - yield new RedisClient().set(redisKey, from + '') + yield new ZRedisClient().set(redisKey, from + '') } yield getPastEvents({ contract, event, fromBlock: from, toBlock: toBlockBN, options }) - yield new RedisClient().set(redisKey, toBlockBN.add(ONE) + '') + yield new ZRedisClient().set(redisKey, toBlockBN.add(ONE) + '') } export async function processEvents(web3, iterator, processedEvent) { diff --git a/src/utils/date.util.ts b/src/utils/date.util.ts deleted file mode 100644 index bf89187..0000000 --- a/src/utils/date.util.ts +++ /dev/null @@ -1,39 +0,0 @@ -// format the date to the format we want -export const formatDate = (date: Date): string => { - const year = date.getFullYear() - const month = (date.getMonth() + 1 + '').padStart(2, '0') - const day = (date.getDate() + '').padStart(2, '0') - return `${year}${month}${day}` -} - -// get begin of one day -export const getDayBegin = (date: Date): Date => { - const year = date.getFullYear() - const month = date.getMonth() - const day = date.getDate() - return new Date(year, month, day) -} - -// get begin of n day ago -export const getNDayAgo = (n: number, begin: boolean): Date => { - const date = new Date(Date.now() - n * 24 * 60 * 60 * 1000) - if (begin) { - return getDayBegin(date) - } else { - return date - } -} - -// get begin of this month -export const getMonthBegin = (date: Date): Date => { - const year = date.getFullYear() - const month = date.getMonth() - return new Date(year, month, 1) -} - -// get formated datestring of yesterday -export const yesterday = () => { - const date = new Date() - date.setDate(date.getDate() - 1) - return date -} diff --git a/src/utils/event.util.ts b/src/utils/event.util.ts deleted file mode 100644 index c769b08..0000000 --- a/src/utils/event.util.ts +++ /dev/null @@ -1,45 +0,0 @@ -//@ts-ignore -import { keccak256, _jsonInterfaceMethodToString, AbiInput } from 'web3-utils' -import web3abi from 'web3-eth-abi' -import { IEventCfg } from 'interface/IEventCfg' - -export const getTopics = (cfg: IEventCfg) => { - // let abi = cfg.abi - // let topic = `${abi.name}(` - // for (let item of abi.inputs) { - // topic += item.type + ',' - // } - // topic = topic.slice(0, -1) - // topic += ')' - return keccak256(_jsonInterfaceMethodToString(cfg.abi)) -} - -export const decodeEvent = (cfg: IEventCfg, eventData: { data: string; topics: string[] }) => { - const abiInputs = cfg.abi.inputs - let result = web3abi.decodeLog(abiInputs, eventData.data, eventData.topics.slice(1)) - let decodedData: any = {} - for (let i = 0; i < abiInputs.length; i++) { - const input: AbiInput = abiInputs[i] - if (input.type === 'tuple[]') { - // @ts-ignore - decodedData[input.name] = result[i].map(item => { - let itemData = {} - for (let j = 0; j < input.components.length; j++) { - const component = input.components[j] - itemData[component.name] = item[j] - } - return itemData - }) - } else if (input.type === 'tuple') { - let itemData = {} - for (let j = 0; j < input.components.length; j++) { - const component = input.components[j] - itemData[component.name] = result[i][j] - } - decodedData[input.name] = itemData - } else { - decodedData[input.name] = result[i] - } - } - return decodedData -} diff --git a/src/utils/net.util.ts b/src/utils/net.util.ts deleted file mode 100644 index 2697f91..0000000 --- a/src/utils/net.util.ts +++ /dev/null @@ -1,138 +0,0 @@ -const TIMEOUT_ERROR = new Error('timeout') - -const hexRe = /^[0-9A-Fa-f]+$/gu - -/** - * Execute fetch and verify that the response was successful. - * - * @param request - Request information. - * @param options - Fetch options. - * @returns The fetch response. - */ -export async function successfulFetch(request: string, options?: RequestInit) { - const response = await fetch(request, options) - if (!response.ok) { - throw new Error(`Fetch failed with status '${response.status}' for request '${request}'`) - } - return response -} - -/** - * Execute fetch and return object response. - * - * @param request - The request information. - * @param options - The fetch options. - * @returns The fetch response JSON data. - */ -export async function handleFetch(request: string, options?: RequestInit) { - const response = await successfulFetch(request, options) - const object = await response.json() - return object -} - -/** - * Execute fetch and return object response, log if known error thrown, otherwise rethrow error. - * - * @param request - the request options object - * @param request.url - The request url to query. - * @param request.options - The fetch options. - * @param request.timeout - Timeout to fail request - * @param request.errorCodesToCatch - array of error codes for errors we want to catch in a particular context - * @returns The fetch response JSON data or undefined (if error occurs). - */ -export async function fetchWithErrorHandling({ - url, - options, - timeout, - errorCodesToCatch, -}: { - url: string - options?: RequestInit - timeout?: number - errorCodesToCatch?: number[] -}) { - let result - try { - if (timeout) { - result = Promise.race([ - await handleFetch(url, options), - new Promise((_, reject) => - setTimeout(() => { - reject(TIMEOUT_ERROR) - }, timeout), - ), - ]) - } else { - result = await handleFetch(url, options) - } - } catch (e) { - logOrRethrowError(e, errorCodesToCatch) - } - return result -} - -/** - * Fetch that fails after timeout. - * - * @param url - Url to fetch. - * @param options - Options to send with the request. - * @param timeout - Timeout to fail request. - * @returns Promise resolving the request. - */ -export async function timeoutFetch(url: string, options?: RequestInit, timeout = 500): Promise { - return Promise.race([ - successfulFetch(url, options), - new Promise((_, reject) => - setTimeout(() => { - reject(TIMEOUT_ERROR) - }, timeout), - ), - ]) -} - -/** - * Utility method to log if error is a common fetch error and otherwise rethrow it. - * - * @param error - Caught error that we should either rethrow or log to console - * @param codesToCatch - array of error codes for errors we want to catch and log in a particular context - */ -function logOrRethrowError(error: any, codesToCatch: number[] = []) { - if (!error) { - return - } - - const includesErrorCodeToCatch = codesToCatch.some(code => - error.message.includes(`Fetch failed with status '${code}'`), - ) - - if ( - error instanceof Error && - (includesErrorCodeToCatch || error.message.includes('Failed to fetch') || error === TIMEOUT_ERROR) - ) { - console.error(error) - } else { - throw error - } -} - -export function generateHeader() { - let random = function (start, end) { - return (Math.random() * (end - start) + start) | 0 - } - let getIp = function () { - return `${random(1, 254)}.${random(1, 254)}.${random(1, 254)}.${random(1, 254)}` - } - let time = Date.now() - let useragent = `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_0_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${ - (70 + Math.random() * 10) | 0 - }.0.4324.${(Math.random() * 100) | 0} Safari/537.36` - const ip = getIp() - return { - 'Refresh-Token': (time -= 5000), - 'Cache-Control': 'no-cache', - 'User-Agent': useragent, - 'X-Forwarded-For': ip, - 'X-Real-IP': ip, - 'Content-Type': 'application/json', - } -} diff --git a/src/utils/number.util.ts b/src/utils/number.util.ts deleted file mode 100644 index b967a44..0000000 --- a/src/utils/number.util.ts +++ /dev/null @@ -1,198 +0,0 @@ -import Web3 from 'web3' -import { BN } from 'ethereumjs-util' - -/** - * Converts some token minimal unit to render format string, showing 5 decimals - * - * @param {Number|String|BN} tokenValue - Token value to convert - * @param {Number} decimals - Token decimals to convert - * @param {Number} decimalsToShow - Decimals to 5 - * @returns {String} - Number of token minimal unit, in render format - * If value is less than 5 precision decimals will show '< 0.00001' - */ -export function renderFromTokenMinimalUnit(tokenValue, decimals, decimalsToShow = 5) { - const minimalUnit = fromTokenMinimalUnit(tokenValue || 0, decimals) - const minimalUnitNumber = parseFloat(minimalUnit) - let renderMinimalUnit - if (minimalUnitNumber < 0.00001 && minimalUnitNumber > 0) { - renderMinimalUnit = '< 0.00001' - } else { - const base = Math.pow(10, decimalsToShow) - renderMinimalUnit = (Math.round(minimalUnitNumber * base) / base).toString() - } - return renderMinimalUnit -} -/** - * Converts token minimal unit to readable string value - * - * @param {number|string|Object} minimalInput - Token minimal unit to convert - * @param {string} decimals - Token decimals to convert - * @returns {string} - String containing the new number - */ -export function fromTokenMinimalUnit(minimalInput, decimals) { - minimalInput = addHexPrefix(Number(minimalInput).toString(16)) - let minimal = safeNumberToBN(minimalInput) - const negative = minimal.lt(new BN(0)) - const base = Web3.utils.toBN(Math.pow(10, decimals).toString()) - - if (negative) { - minimal = minimal.mul(new BN(-1)) - } - let fraction = minimal.mod(base).toString(10) - while (fraction.length < decimals) { - fraction = '0' + fraction - } - fraction = fraction.match(/^([0-9]*[1-9]|0)(0*)/)[1] - const whole = minimal.div(base).toString(10) - let value = '' + whole + (fraction === '0' ? '' : '.' + fraction) - if (negative) { - value = '-' + value - } - return value -} - -/** - * Converts wei to render format string, showing 5 decimals - * - * @param {Number|String|BN} value - Wei to convert - * @param {Number} decimalsToShow - Decimals to 5 - * @returns {String} - Number of token minimal unit, in render format - * If value is less than 5 precision decimals will show '< 0.00001' - */ -export function renderFromWei(value, decimalsToShow = 5) { - let renderWei = '0' - // avoid undefined - if (value) { - const wei = Web3.utils.fromWei(value) - const weiNumber = parseFloat(wei) - if (weiNumber < 0.00001 && weiNumber > 0) { - renderWei = '< 0.00001' - } else { - const base = Math.pow(10, decimalsToShow) - renderWei = (Math.round(weiNumber * base) / base).toString() - } - } - return renderWei -} - -/** - * Converts token BN value to hex string number to be sent - * - * @param {Object} value - BN instance to convert - * @param {number} decimals - Decimals to be considered on the conversion - * @returns {string} - String of the hex token value - */ -export function calcTokenValueToSend(value, decimals) { - return value ? (value * Math.pow(10, decimals)).toString(16) : 0 -} - -/** - * Determines if a string is a valid decimal - * - * @param {string} value - String to check - * @returns {boolean} - True if the string is a valid decimal - */ -export function isDecimal(value) { - return Number.isFinite(parseFloat(value)) && !Number.isNaN(parseFloat(value)) && !isNaN(+value) -} - -/** - * Creates a BN object from a string - * - * @param {string} value - Some numeric value represented as a string - * @returns {Object} - BN instance - */ -export function toBN(value) { - return Web3.utils.toBN(value) -} - -/** - * Prefixes a hex string with '0x' or '-0x' and returns it. Idempotent. - * - * @param {string} str - The string to prefix. - * @returns {string} The prefixed string. - */ -export const addHexPrefix = (str: string) => { - if (typeof str !== 'string' || str.match(/^-?0x/u)) { - return str - } - - if (str.match(/^-?0X/u)) { - return str.replace('0X', '0x') - } - - if (str.startsWith('-')) { - return str.replace('-', '-0x') - } - - return `0x${str}` -} - -/** - * Wraps 'numberToBN' method to avoid potential undefined and decimal values - * - * @param {number|string} value - number - * @returns {Object} - The converted value as BN instance - */ -export function safeNumberToBN(value: number | string) { - const safeValue = fastSplit(value.toString()) || '0' - return numberToBN(safeValue) -} - -/** - * Performs a fast string split and returns the first item of the string based on the divider provided - * - * @param {number|string} value - number/string to be splitted - * @param {string} divider - string value to use to split the string (default '.') - * @returns {string} - the selected splitted element - */ - -export function fastSplit(value, divider = '.') { - value += '' - const [from, to] = [value.indexOf(divider), 0] - return value.substring(from, to) || value -} - -export function stripHexPrefix(str: string) { - if (typeof str !== 'string') { - return str - } - - return str.slice(0, 2) === '0x' ? str.slice(2) : str -} - -export function numberToBN(arg) { - if (typeof arg === 'string' || typeof arg === 'number') { - var multiplier = Web3.utils.toBN(1); // eslint-disable-line - var formattedString = String(arg).toLowerCase().trim() - var isHexPrefixed = formattedString.substr(0, 2) === '0x' || formattedString.substr(0, 3) === '-0x' - var stringArg = stripHexPrefix(formattedString); // eslint-disable-line - if (stringArg.substr(0, 1) === '-') { - stringArg = stripHexPrefix(stringArg.slice(1)) - multiplier = Web3.utils.toBN(-1) - } - stringArg = stringArg === '' ? '0' : stringArg - - if ( - (!stringArg.match(/^-?[0-9]+$/) && stringArg.match(/^[0-9A-Fa-f]+$/)) || - stringArg.match(/^[a-fA-F]+$/) || - (isHexPrefixed === true && stringArg.match(/^[0-9A-Fa-f]+$/)) - ) { - return Web3.utils.toBN(stringArg).mul(multiplier) - } - - if ((stringArg.match(/^-?[0-9]+$/) || stringArg === '') && isHexPrefixed === false) { - return Web3.utils.toBN(stringArg).mul(multiplier) - } - } else if (typeof arg === 'object' && arg.toString && !arg.pop && !arg.push) { - if (arg.toString(10).match(/^-?[0-9]+$/) && (arg.mul || arg.dividedToIntegerBy)) { - return Web3.utils.toBN(arg.toString(10)) - } - } - - throw new Error( - '[number-to-bn] while converting number ' + - JSON.stringify(arg) + - ' to BN.js instance, error: invalid number value. Value must be an integer, hex string, BN or BigNumber instance. Note, decimals are not supported.', - ) -} diff --git a/src/utils/promise.util.ts b/src/utils/promise.util.ts deleted file mode 100644 index 80512f9..0000000 --- a/src/utils/promise.util.ts +++ /dev/null @@ -1,138 +0,0 @@ -type RetryOptions = { - maxRetries: number - whitelistErrors: Error[] -} -/** - * 使用: - * retry(() => fetch("https://example.com"), { maxRetries: 3, whitelistErrors: [] }) - * .then((response) => console.log(response)) - * .catch((error) => console.error(error)); - * @param promiseFn - * @param options - * @returns - */ -export function retry(promiseFn: () => Promise, options: RetryOptions): Promise { - let retries = 0 - let defaultOptions = { - maxRetries: 3, - whitelistErrors: [], - } - Object.assign(defaultOptions, options) - const { maxRetries, whitelistErrors } = options - - const retryPromise = async (): Promise => { - try { - return await promiseFn() - } catch (err) { - if ( - retries < maxRetries && - whitelistErrors.some(whitelistedError => err instanceof whitelistedError.constructor) - ) { - retries++ - return retryPromise() - } - throw err - } - } - - return retryPromise() -} -/** - * 构建一个promise, 在 - * usage: - * function delay(ms: number): Promise { - const deferred = new Deferred(); - - setTimeout(() => { - deferred.resolve(); - }, ms); - - return deferred.promise; - } - - console.log("start"); - - delay(1000).then(() => { - console.log("after 1 second"); - }); - - console.log("end"); - */ -export class Deferred { - private _resolve!: (value: T | PromiseLike) => void - private _reject!: (reason?: any) => void - - public readonly promise: Promise - - constructor() { - this.promise = new Promise((resolve, reject) => { - this._resolve = resolve - this._reject = reject - }) - } - - public resolve(value: T | PromiseLike): void { - this._resolve(value) - } - - public reject(reason?: any): void { - this._reject(reason) - } - - public then( - onfulfilled?: ((value: T) => TResult1 | PromiseLike) | null | undefined, - onrejected?: ((reason: any) => TResult2 | PromiseLike) | null | undefined, - ): Promise { - return this.promise.then(onfulfilled, onrejected) - } - - public catch( - onrejected?: ((reason: any) => TResult | PromiseLike) | null | undefined, - ): Promise { - return this.promise.catch(onrejected) - } -} - -/** - * 简单限流的 Promise 队列 - * usage: - const q = new PromiseQueue(); - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].forEach((v) => { - q.add( - () => - new Promise((resolve) => { - setTimeout(() => { - console.log(v); - resolve(); - }, 1000); - }) - ); -}); - */ -export class PromiseQueue { - private readonly concurrency: number - private _current: number = 0 - private _list: (() => Promise)[] = [] - - constructor({ concurrency = 2 }: { concurrency: number }) { - this.concurrency = concurrency - } - - add(promiseFn: () => Promise) { - this._list.push(promiseFn) - this.loadNext() - } - - loadNext() { - if (this._list.length === 0 || this.concurrency === this._current) return - this._current++ - const fn = this._list.shift()! - const promise = fn.call(this) - promise.then(this.onLoaded.bind(this)).catch(this.onLoaded.bind(this)) - } - - onLoaded() { - this._current-- - this.loadNext() - } -} diff --git a/src/utils/security.util.ts b/src/utils/security.util.ts deleted file mode 100644 index e799be4..0000000 --- a/src/utils/security.util.ts +++ /dev/null @@ -1,74 +0,0 @@ -import crypto from 'crypto' - -export function hmac(input, key, out) { - return out - ? crypto.createHmac('sha1', key).update(input).digest(out) - : crypto.createHmac('sha1', key).update(input).digest('hex') -} - -export function genRandomString(length) { - return crypto - .randomBytes(Math.ceil(length / 2)) - .toString('hex') - .slice(0, length) -} - -export function sha512(password, salt) { - let hash = crypto.createHmac('sha512', salt) - hash.update(password) - let value = hash.digest('hex') - return { - salt: salt, - passwordHash: value, - } -} - -export function sha1(str) { - const md5sum = crypto.createHash('sha1') - md5sum.update(str) - str = md5sum.digest('hex') - return str -} - -export function hmacSha256(text: string, secret: string) { - const mac = crypto.createHmac('sha256', secret) - const data = mac.update(text).digest('hex').toLowerCase() - console.log(`HmacSHA256 rawContent is [${text}], key is [${secret}], hash result is [${data}]`) - return data -} - -export function md5(str) { - const md5sum = crypto.createHash('md5') - md5sum.update(str) - str = md5sum.digest('hex') - return str -} - -export function createSign(secretKey, paramStr, timestamp) { - paramStr = `${paramStr}:${timestamp}:${secretKey}` - return sha1(paramStr) -} - -export function checkSign({ - secretKey, - data, - sign, - signKeys, -}: { - secretKey: string - data: {} - sign: string - signKeys: string[] -}) { - signKeys.sort() - let signStr = '' - for (let key of signKeys) { - if (signStr.length > 0) { - signStr += '&' - } - signStr += `${key}=${data[key]}` - } - console.log(signStr) - let sign1 = hmacSha256(signStr, secretKey) - return sign1 === sign -} diff --git a/src/utils/string.util.ts b/src/utils/string.util.ts deleted file mode 100644 index 422f203..0000000 --- a/src/utils/string.util.ts +++ /dev/null @@ -1,123 +0,0 @@ -/** - * 根据key升序生成 key1=val1&key2=val2的字符串 - * @param {object} data 需要处理的对象 - * @param {boolean} ignoreNull 是否过滤空值(空格或者null值不参与拼接) - * @param splitChar 连接的字符, 默认是& - * @param equalChar = - */ -export function generateKeyValStr(data: {}, ignoreNull = true, splitChar: string = '&', equalChar = '=') { - const keys = Object.keys(data) - keys.sort() - let result = '' - let i = 0 - for (let key of keys) { - if (ignoreNull && !data[key]) { - return - } - if (i++ > 0) result += splitChar - result += `${key}${equalChar}${data[key]}` - } - return result -} - -/** - * 将key1=val&key2=val的字符串组装成对象 - * @param str key1=val&key2=val的字符串 - * @param splitChar 连接的字符, 默认是& - * @param equalChar = - */ -export function keyValToObject(str: string, splitChar: string = '&', equalChar = '='): {} { - let result = {} - if (!str) { - return result - } - let arrs = str.split(splitChar) - for (let sub of arrs) { - let subArr = sub.split(equalChar) - result[subArr[0]] = subArr[1] - } - return result -} - -/** - * 判断传入的值是否为true - * @param {Object} obj 传入值为'true','TRUE',1,'1','on','ON','YES','yes'时,返回true,其他值均返回false - * @return {boolean} - */ -export function isTrue(obj) { - return ( - obj === 'true' || - obj === 'TRUE' || - obj === 'True' || - obj === 'on' || - obj === 'ON' || - obj === true || - obj === 1 || - obj === '1' || - obj === 'YES' || - obj === 'yes' - ) -} - -/** - * 验证ObjectId格式是否正确 - * @param {string} id - * @return {boolean} - */ -export function isObjectId(id: string): boolean { - //mongoose.Types.ObjectId.isValid(id) - return /^[a-fA-F0-9]{24}$/.test(id) -} - -/** - * 10进制 -> 62进制 - * @param {string | number} number - * @return {string} - */ -export function string10to62(number: string | number) { - const chars = '0123456789abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ'.split('') - const radix = chars.length - let qutient = +number - const arr = [] - do { - const mod = qutient % radix - qutient = (qutient - mod) / radix - arr.unshift(chars[mod]) - } while (qutient) - return arr.join('') -} - -/** - * 62进制 -> 10 进制 - * @param {string} numberCode - * @return {number} - */ -export function string62to10(numberCode: string) { - const chars = '0123456789abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ' - const radix = chars.length - numberCode = numberCode + '' - const len = numberCode.length - let i = 0 - let originNumber = 0 - while (i < len) { - originNumber += Math.pow(radix, i++) * (chars.indexOf(numberCode.charAt(len - i)) || 0) - } - return originNumber -} - -export function hexToUtf8(hexString) { - // Remove any leading "0x" prefix and split into pairs of characters - let _hexString = hexString.replace(/^0x/, '') - let buffer = Buffer.from(_hexString, 'hex') - return buffer.toString('utf8') -} - -export function utf8ToHex(utf8String) { - // Create a Buffer object from the UTF-8 string - const buffer = Buffer.from(utf8String, 'utf8') - - // Convert the Buffer object to a hex string - const hexString = buffer.toString('hex') - - return hexString -} diff --git a/src/utils/wallet.util.ts b/src/utils/wallet.util.ts deleted file mode 100644 index b30cf5a..0000000 --- a/src/utils/wallet.util.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { renderFromTokenMinimalUnit } from './number.util' -import { asciiToHex } from 'web3-utils' - -/** - * Removes IPFS protocol prefix from input string. - * - * @param ipfsUrl - An IPFS url (e.g. ipfs://{content id}) - * @returns IPFS content identifier and (possibly) path in a string - * @throws Will throw if the url passed is not IPFS. - */ -export function removeIpfsProtocolPrefix(ipfsUrl: string) { - if (ipfsUrl.startsWith('ipfs://ipfs/')) { - return ipfsUrl.replace('ipfs://ipfs/', '') - } else if (ipfsUrl.startsWith('ipfs://')) { - return ipfsUrl.replace('ipfs://', '') - } - // this method should not be used with non-ipfs urls (i.e. startsWith('ipfs://') === true) - throw new Error('this method should not be used with non ipfs urls') -} - -/** - * Extracts content identifier and path from an input string. - * - * @param ipfsUrl - An IPFS URL minus the IPFS protocol prefix - * @returns IFPS content identifier (cid) and sub path as string. - * @throws Will throw if the url passed is not ipfs. - */ -export function getIpfsCIDv1AndPath(ipfsUrl: string): { - cid: string - path?: string -} { - const url = removeIpfsProtocolPrefix(ipfsUrl) - - // check if there is a path - // (CID is everything preceding first forward slash, path is everything after) - const index = url.indexOf('/') - const cid = index !== -1 ? url.substring(0, index) : url - const path = index !== -1 ? url.substring(index) : undefined - //TODO: - // We want to ensure that the CID is v1 (https://docs.ipfs.io/concepts/content-addressing/#identifier-formats) - // because most cid v0s appear to be incompatible with IPFS subdomains - // return { - // cid: CID.parse(cid).toV1().toString(), - // path, - // }; - return { - cid, - path, - } -} - -/** - * Adds URL protocol prefix to input URL string if missing. - * - * @param urlString - An IPFS URL. - * @returns A URL with a https:// prepended. - */ -export function addUrlProtocolPrefix(urlString: string): string { - if (!urlString.match(/(^http:\/\/)|(^https:\/\/)/u)) { - return `https://${urlString}` - } - return urlString -} - -/** - * Formats URL correctly for use retrieving assets hosted on IPFS. - * - * @param ipfsGateway - The users preferred IPFS gateway (full URL or just host). - * @param ipfsUrl - The IFPS URL pointed at the asset. - * @param subdomainSupported - Boolean indicating whether the URL should be formatted with subdomains or not. - * @returns A formatted URL, with the user's preferred IPFS gateway and format (subdomain or not), pointing to an asset hosted on IPFS. - */ -export function getFormattedIpfsUrl(ipfsGateway: string, ipfsUrl: string, subdomainSupported: boolean): string { - const { host, protocol, origin } = new URL(addUrlProtocolPrefix(ipfsGateway)) - if (subdomainSupported) { - const { cid, path } = getIpfsCIDv1AndPath(ipfsUrl) - return `${protocol}//${cid}.ipfs.${host}${path || ''}` - } - const cidAndPath = removeIpfsProtocolPrefix(ipfsUrl) - return `${origin}/ipfs/${cidAndPath}` -} - -/** - * Returns whether the given code corresponds to a smart contract. - * - * @param code - The potential smart contract code. - * @returns Whether the code was smart contract code or not. - */ -export function isSmartContractCode(code: string) { - /* istanbul ignore if */ - if (!code) { - return false - } - // Geth will return '0x', and ganache-core v2.2.1 will return '0x0' - const smartContractCode = code !== '0x' && code !== '0x0' - return smartContractCode -} - -export function formatAddress(address: string) { - if (address.length >= 10) { - return address.substring(0, 6) + '...' + address.substring(address.length - 4) - } else if (address.length > 0 && address.length < 10) { - return address - } else { - return '' - } -} - -export function formatMoney(balance: number | string, symbol: string) { - if (balance === '-') { - return `- ${symbol}` - } - let money = renderFromTokenMinimalUnit(balance, 18, 4) - return `${money} ${symbol}` -} - -/** - * 生成随机的bytes32的字符串 - * @returns - */ -export function generateRandomBytes32() { - const v1 = (Math.random() * 9000000 + 1000000) | 0 - const v2 = (Math.random() * 900000 + 100000) | 0 - return asciiToHex(v1 + '' + v2) -} diff --git a/yarn.lock b/yarn.lock index 616acba..dbc408a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1349,6 +1349,11 @@ crypto-browserify@3.12.0: randombytes "^2.0.0" randomfill "^1.0.3" +crypto-js@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631" + integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q== + d@1, d@^1.0.1: version "1.0.1" resolved "https://registry.npmmirror.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" @@ -4706,3 +4711,7 @@ yn@3.1.1: version "3.1.1" resolved "https://registry.npmmirror.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +"zutils@link:packages/zutils": + version "0.0.0" + uid ""