修正购买流程的bug

This commit is contained in:
zhl 2023-06-02 19:54:21 +08:00
parent 476da08a8f
commit 0688261153
9 changed files with 168 additions and 67 deletions

View File

@ -14,8 +14,11 @@ EMAIL_VERIFY_URL="https://wallet.cebggame.com"
ALCHEMY_APPID="f83Is2y7L425rxl8" ALCHEMY_APPID="f83Is2y7L425rxl8"
ALCHEMY_APP_SECRET="4Yn8RkxDXN71Q3p0" ALCHEMY_APP_SECRET="4Yn8RkxDXN71Q3p0"
ALCHEMY_API_BASE="https://openapi-test.alchemypay.org" # ALCHEMY_API_BASE="https://openapi-test.alchemypay.org"
ALCHEMY_PAGE_BASE="https://ramptest.alchemypay.org" # ALCHEMY_PAGE_BASE="https://ramptest.alchemypay.org"
ALCHEMY_API_BASE="http://127.0.0.1:3009"
ALCHEMY_PAGE_BASE="http://127.0.0.1:3009/pay_page"
ALCHEMY_PAY_CB_URL="https://wallet.cebggame.com" ALCHEMY_PAY_CB_URL="https://wallet.cebggame.com"
CHAIN_CLIENT_URL=http://127.0.0.1:3006 CHAIN_CLIENT_URL=http://127.0.0.1:3006
@ -29,10 +32,12 @@ AVAILABLE_TOKENS=cec|ceg
ARBITRUM_CHAIN_ID=421163 ARBITRUM_CHAIN_ID=421163
ARBITRUM_CEC_ADDRESS='0xaa34B79A0Ab433eaC900fB3CB9f191F5Cd27501D' ARBITRUM_CEC_ADDRESS='0xaa34B79A0Ab433eaC900fB3CB9f191F5Cd27501D'
ARBITRUM_CEG_ADDRESS='0xaa34B79A0Ab433eaC900fB3CB9f191F5Cd27501D' ARBITRUM_CEG_ADDRESS='0xaa34B79A0Ab433eaC900fB3CB9f191F5Cd27501D'
ARBITRUM_WALLET='' ARBITRUM_WALLET='0x50A8e60041A206AcaA5F844a1104896224be6F39'
BSC_CHAIN_ID=97 BSC_CHAIN_ID=97
BSC_CEC_ADDRESS='0xaa34B79A0Ab433eaC900fB3CB9f191F5Cd27501D' BSC_CEC_ADDRESS='0xaa34B79A0Ab433eaC900fB3CB9f191F5Cd27501D'
BSC_CEG_ADDRESS='0xaa34B79A0Ab433eaC900fB3CB9f191F5Cd27501D' BSC_CEG_ADDRESS='0xaa34B79A0Ab433eaC900fB3CB9f191F5Cd27501D'
BSC_WALLET='' BSC_WALLET='0x50A8e60041A206AcaA5F844a1104896224be6F39'
# 链端转账回调地址 # 链端转账回调地址
PAY_TRANSFER_CB_URL='' PAY_TRANSFER_CB_URL='http://127.0.0.1:3007/api/internal/update_task'
# 链端回调hash的ket
HASH_SALT='iG4Rpsa)6U31$H#^T85$^^3'

View File

@ -36,9 +36,9 @@ let chainCfgs = {}
for (let network of networks) { for (let network of networks) {
let data = { chainId: 0, wallet: '', tokens: {} } let data = { chainId: 0, wallet: '', tokens: {} }
data.chainId = parseInt(process.env[`${network.toUpperCase()}_CHAIN_ID`]) data.chainId = parseInt(process.env[`${network.toUpperCase()}_CHAIN_ID`])
data.wallet = process.env[`WALLET_${network.toUpperCase()}`] data.wallet = process.env[`${network.toUpperCase()}_WALLET`]
for (let sub of tokenList) { for (let sub of tokenList) {
data.tokens[sub] = process.env[`ADDRESS_${sub.toUpperCase()}_${network.toUpperCase()}`] data.tokens[sub] = process.env[`${network.toUpperCase()}_${sub.toUpperCase()}_ADDRESS`]
} }
chainCfgs[network] = data chainCfgs[network] = data
} }

View File

@ -2,7 +2,7 @@ import logger from 'logger/logger'
import BaseController from 'common/base.controller' import BaseController from 'common/base.controller'
import { ZError } from 'common/ZError' import { ZError } from 'common/ZError'
import { router } from 'decorators/router' import { router } from 'decorators/router'
import { createPageSign, queryFiat, queryPrice, refreshToken } from 'service/alchemy.svr' import { createOrder, createPageSign, queryFiat, queryPrice, refreshToken } from 'service/alchemy.svr'
import { generateKVStr } from 'utils/net.util' import { generateKVStr } from 'utils/net.util'
import { PayRecord, PayStatus } from 'modules/PayRecord' import { PayRecord, PayStatus } from 'modules/PayRecord'
import { PriceSvr } from 'service/price.svr' import { PriceSvr } from 'service/price.svr'
@ -10,7 +10,59 @@ import { PriceSvr } from 'service/price.svr'
const CALL_BACK_URL = `${process.env.ALCHEMY_PAY_CB_URL}/pay/out/alchemy/buycb` const CALL_BACK_URL = `${process.env.ALCHEMY_PAY_CB_URL}/pay/out/alchemy/buycb`
class AlchemyController extends BaseController { class AlchemyController extends BaseController {
@router('post /pay/alchemy/buy') @router('post /pay/alchemy/buy')
async beginPay(req, res) { async beginApiPay(req, res) {
const user = req.user
const { network, crypto, address, fiat, fiatAmount, country } = req.params
if (fiat || fiatAmount || country) {
if (!fiat || !fiatAmount || !country) {
throw new ZError(11, 'fiat, fiatAmount and country must be provided')
}
}
if (network || crypto) {
if (!network || !crypto) {
throw new ZError(12, 'network and crypto must be provided')
}
}
const tokenResult = await refreshToken(user.emailReal || user.email)
console.log(tokenResult)
if (!tokenResult.success || tokenResult.returnCode !== '0000') {
logger.info(`fetch pay token error::code: ${tokenResult.returnCode} msg: ${tokenResult.returnMsg}`)
throw new ZError(10, 'fetch pay token error')
}
const { id, email, accessToken } = tokenResult.data
let record = new PayRecord({ account: user.id, address, network, crypto })
if (fiat) record.fiat = fiat
if (fiatAmount) record.fiatAmount = fiatAmount
if (country) record.country = country
await record.save()
let payData: any = {
side: 'BUY',
merchantOrderNo: record.id,
amount: record.fiatAmount,
fiatCurrency: record.fiat,
cryptoCurrency: record.crypto,
depositType: '2',
address: address,
network: record.network,
payWayCode: '10001',
alpha2: record.country,
callbackUrl: CALL_BACK_URL,
merchantName: 'CEBG',
}
logger.info(`create order data::${JSON.stringify(payData)}`)
let payRes = await createOrder(accessToken, payData)
logger.info(`create order result::${JSON.stringify(payRes)}`)
record.outData = payRes.data
if (payRes.success) {
record.outOrderId = payRes.data.orderNo
} else {
record.status = PayStatus.FAIL
}
record.save()
return { url: payRes.data.payUrl }
}
@router('post /pay/alchemy/buypage')
async beginPagePay(req, res) {
const user = req.user const user = req.user
const { network, crypto, address, fiat, fiatAmount, country } = req.params const { network, crypto, address, fiat, fiatAmount, country } = req.params
if (fiat || fiatAmount || country) { if (fiat || fiatAmount || country) {

View File

@ -5,6 +5,7 @@ import { role, router } from 'decorators/router'
import { checkPayResultSign, checkSimpleSign } from 'service/alchemy.svr' import { checkPayResultSign, checkSimpleSign } from 'service/alchemy.svr'
import { PayRecord, PayStatus } from 'modules/PayRecord' import { PayRecord, PayStatus } from 'modules/PayRecord'
import { TransferQueue } from 'queue/transfer.queue' import { TransferQueue } from 'queue/transfer.queue'
import { TransferRecord } from 'modules/TransferRecord'
let errorRes = function (msg: string) { let errorRes = function (msg: string) {
return { return {
@ -27,12 +28,17 @@ class AlchemyOutController extends BaseController {
logger.info(`alchemy callback merchantOrderNo not found`) logger.info(`alchemy callback merchantOrderNo not found`)
throw new ZError(11, 'alchemy callback merchantOrderNo not found') throw new ZError(11, 'alchemy callback merchantOrderNo not found')
} }
let record = await PayRecord.findById(merchantOrderNo) let record = await PayRecord.findById(merchantOrderNo)
if (!record) { if (!record) {
logger.info(`alchemy callback record not found`) logger.info(`alchemy callback record not found`)
throw new ZError(12, 'alchemy callback record not found') throw new ZError(12, 'alchemy callback record not found')
} }
if (record.status !== PayStatus.PENDING && record.status !== PayStatus.TRANSFERING) { if (
record.status !== PayStatus.PENDING &&
record.status !== PayStatus.TRANSFERING &&
record.status !== PayStatus.TRANSFERED
) {
logger.info(`alchemy callback record status error`) logger.info(`alchemy callback record status error`)
throw new ZError(13, 'alchemy callback record status error') throw new ZError(13, 'alchemy callback record status error')
} }
@ -42,13 +48,18 @@ class AlchemyOutController extends BaseController {
await record.save() await record.save()
throw new ZError(14, 'alchemy callback sign error') throw new ZError(14, 'alchemy callback sign error')
} }
let transferRecord = await TransferRecord.findByRecordId(record.id)
if (transferRecord) {
transferRecord.status = 9
await transferRecord.save()
}
record.outOrderId = orderNo record.outOrderId = orderNo
record.network = network record.network = network
record.crypto = crypto record.crypto = crypto
record.outData = req.params record.outData = req.params
record.status = status == 'PAY_SUCCESS' ? PayStatus.SUCCESS : PayStatus.FAIL record.status = status == 'PAY_SUCCESS' ? PayStatus.SUCCESS : PayStatus.FAIL
await record.save() await record.save()
logger.info(`alchemy callback success, pay finished`)
return {} return {}
} }
@ -60,14 +71,15 @@ class AlchemyOutController extends BaseController {
@router('get /pay/out/alchemy/queryprice') @router('get /pay/out/alchemy/queryprice')
async queryToken(req, res) { async queryToken(req, res) {
const { crypto } = req.params const { crypto } = req.params
const { appId, timestamp, sign } = req.headers let { appId, appid, timestamp, sign } = req.headers
if (!crypto) { if (!crypto) {
return errorRes('params mismatch') return errorRes('params mismatch')
} }
appId = appId || appid
if (!appId || !timestamp || !sign) { if (!appId || !timestamp || !sign) {
return errorRes('headers mismatch') return errorRes('headers mismatch')
} }
if (!checkSimpleSign(req.headers)) { if (!checkSimpleSign(req.headers, { crypto })) {
return errorRes('sign error') return errorRes('sign error')
} }
let result = { let result = {
@ -90,20 +102,22 @@ class AlchemyOutController extends BaseController {
/** /**
* *
* TODO:: * TODO::test
*/ */
@role(ROLE_ANON) @role(ROLE_ANON)
@router('post /pay/out/alchemy/distribute') @router('post /pay/out/alchemy/distribute')
async distributeToken(req, res) { async distributeToken(req, res) {
const { orderNo, crypto, network, address, cryptoAmount, cryptoPrice, usdtAmount } = req.params const { orderNo, crypto, network, address, cryptoAmount, cryptoPrice, usdtAmount } = req.params
const { appId, timestamp, sign } = req.headers let { appId, appid, timestamp, sign } = req.headers
if (!orderNo || !crypto || !network || !address || !cryptoAmount || !cryptoPrice || !usdtAmount) { if (!orderNo || !crypto || !network || !address || !cryptoAmount || !cryptoPrice || !usdtAmount) {
return errorRes('params mismatch') return errorRes('params mismatch')
} }
appId = appId || appid
if (!appId || !timestamp || !sign) { if (!appId || !timestamp || !sign) {
return errorRes('headers mismatch') return errorRes('headers mismatch')
} }
if (!checkSimpleSign(req.headers)) { let signData = { orderNo, crypto, network, address, cryptoAmount, cryptoPrice, usdtAmount }
if (!checkSimpleSign(req.headers, signData)) {
return errorRes('sign error') return errorRes('sign error')
} }
@ -117,6 +131,7 @@ class AlchemyOutController extends BaseController {
record.cryptoAmount = cryptoAmount record.cryptoAmount = cryptoAmount
record.cryptoPrice = cryptoPrice record.cryptoPrice = cryptoPrice
record.usdtAdmount = usdtAmount record.usdtAdmount = usdtAmount
record.status = PayStatus.TRANSFERING
await record.save() await record.save()
new TransferQueue().addTask(record) new TransferQueue().addTask(record)
let result = { let result = {
@ -126,6 +141,6 @@ class AlchemyOutController extends BaseController {
returnCode: '0000', // false: 9999 returnCode: '0000', // false: 9999
returnMsg: 'in amet', returnMsg: 'in amet',
} }
res.send(result) return result
} }
} }

View File

@ -47,7 +47,7 @@ export default class InternalController extends BaseController {
if (!sign) { if (!sign) {
throw new ZError(10, 'sign not found') throw new ZError(10, 'sign not found')
} }
let hash = calcHash({ id, result, successCount, errorCount, hashList }) let hash = calcHash({ id, result, successCount, errorCount, gas, gasPrice, hashList })
console.log(hash, sign) console.log(hash, sign)
if (sign !== hash) { if (sign !== hash) {
throw new ZError(11, 'sign not match') throw new ZError(11, 'sign not match')
@ -71,7 +71,9 @@ export default class InternalController extends BaseController {
record.hashList = hashList record.hashList = hashList
task.txHash = hashList[0] task.txHash = hashList[0]
task.status = PayStatus.TRANSFERED task.status = PayStatus.TRANSFERED
setImmediate(notify.apply(this, [task])) setImmediate(async () => {
await notify(task, record)
})
} else { } else {
record.status = 10 record.status = 10
task.status = PayStatus.TRANSFER_FAIL task.status = PayStatus.TRANSFER_FAIL

View File

@ -78,8 +78,8 @@ export class PayRecordClass extends BaseModule {
@prop() @prop()
public txHash?: string public txHash?: string
public static async findByRecordId(this: ReturnModelType<typeof PayRecordClass>, outOrderid: string) { public static async findByRecordId(this: ReturnModelType<typeof PayRecordClass>, outOrderId: string) {
return this.findOne({ outOrderid }).exec() return this.findOne({ outOrderId }).exec()
} }
} }

View File

@ -40,7 +40,7 @@ export class TransferQueue {
assert(chainCfg, `chain config not found: ${task.network}`) assert(chainCfg, `chain config not found: ${task.network}`)
let chainId = chainCfg.chainId let chainId = chainCfg.chainId
let wallet = chainCfg.wallet let wallet = chainCfg.wallet
let address = chainCfg.tokens[task.crypto] let address = chainCfg.tokens[task.crypto.toLowerCase()]
assert(address, `token address not found: ${task.crypto}`) assert(address, `token address not found: ${task.crypto}`)
let record = await TransferRecord.insertOrUpdate( let record = await TransferRecord.insertOrUpdate(
{ recordId: task.id }, { recordId: task.id },

View File

@ -1,13 +1,31 @@
import axios from 'axios' import axios from 'axios'
import { hmacsha256, sha1 } from 'utils/security.util' import { hmacsha256, sha1 } from 'utils/security.util'
import crypto from 'crypto' import crypto from 'crypto'
import { generateKVStr } from 'utils/net.util'
export function createSimpleSign() { export function createSimpleSign(data: any) {
let timestamp = Date.now() let timestamp = Date.now()
let appid = process.env.ALCHEMY_APPID let appid = process.env.ALCHEMY_APPID
let secret = process.env.ALCHEMY_APP_SECRET let secret = process.env.ALCHEMY_APP_SECRET
// let sign = sha1(appid + secret + timestamp) let signData = { appid, timestamp }
let sign = hmacsha256(appid + timestamp, secret) signData = Object.assign(signData, data)
let signStr = Object.keys(signData)
.sort()
.map(key => `${key}=${signData[key]}`)
.join('&')
let sign = hmacsha256(signStr, secret)
return {
appid,
timestamp,
sign,
}
}
export function createSha1Sign() {
let timestamp = Date.now()
let appid = process.env.ALCHEMY_APPID
let secret = process.env.ALCHEMY_APP_SECRET
let sign = sha1(appid + secret + timestamp)
return { return {
appid, appid,
timestamp, timestamp,
@ -26,11 +44,18 @@ export function checkPayResultSign(data: any) {
return sign === signature return sign === signature
} }
export function checkSimpleSign(data: any) { export function checkSimpleSign(headers: any, data: any) {
// alchemy 很不严谨, 有时候是 appid, 有时候是 appId // alchemy 很不严谨, 有时候是 appid, 有时候是 appId
const { appid, appId, timestamp, sign } = data const { appid, appId, timestamp, sign } = headers
let appIdToCheck = appId || appid let appIdToCheck = appId || appid
const expectedSign = sha1(appIdToCheck + process.env.ALCHEMY_APP_SECRET + timestamp) let signData = { appid: appIdToCheck, timestamp }
signData = Object.assign(signData, data)
let signStr = Object.keys(signData)
.sort()
.map(key => `${key}=${signData[key]}`)
.join('&')
const expectedSign = hmacsha256(signStr, process.env.ALCHEMY_APP_SECRET)
// const expectedSign = sha1(appIdToCheck + process.env.ALCHEMY_APP_SECRET + timestamp)
return sign === expectedSign return sign === expectedSign
} }
@ -66,11 +91,11 @@ export function createPageSign(plainText: string) {
*/ */
export async function refreshToken(email: string) { export async function refreshToken(email: string) {
const data = JSON.stringify({ email }) const data = JSON.stringify({ email })
const { appid, timestamp, sign } = createSimpleSign() const { appid, timestamp, sign } = createSimpleSign({ email })
const host = process.env.ALCHEMY_API_BASE const host = process.env.ALCHEMY_API_BASE
const config = { const config = {
method: 'post', method: 'post',
url: `${host}/merchant/getToken`, url: `${host}/open/api/v3/merchant/getToken`,
headers: { headers: {
appId: appid, appId: appid,
timestamp: timestamp, timestamp: timestamp,
@ -87,11 +112,11 @@ export async function refreshToken(email: string) {
* https://alchemycn.readme.io/docs/创建订单 * https://alchemycn.readme.io/docs/创建订单
*/ */
export async function createOrder(token: string, data: any) { export async function createOrder(token: string, data: any) {
const { appid, timestamp, sign } = createSimpleSign() const { appid, timestamp, sign } = createSimpleSign(data)
const host = process.env.ALCHEMY_API_BASE const host = process.env.ALCHEMY_API_BASE
const config = { const config = {
method: 'post', method: 'post',
url: `${host}/merchant/trade/create`, url: `${host}/open/api/v3/merchant/trade/create`,
headers: { headers: {
'access-token': token, 'access-token': token,
appId: appid, appId: appid,
@ -112,7 +137,7 @@ export async function createOrder(token: string, data: any) {
* TODO:: test * TODO:: test
*/ */
export async function updateOrderStatus(data: any) { export async function updateOrderStatus(data: any) {
const { appid, timestamp, sign } = createSimpleSign() const { appid, timestamp, sign } = createSha1Sign()
const url = process.env.ALCHEMY_API_BASE + '/webhooks/treasure' const url = process.env.ALCHEMY_API_BASE + '/webhooks/treasure'
const config = { const config = {
method: 'post', method: 'post',
@ -133,7 +158,6 @@ export async function updateOrderStatus(data: any) {
* https://alchemycn.readme.io/docs/查询数字货币币价 * https://alchemycn.readme.io/docs/查询数字货币币价
*/ */
export async function queryPrice(data: any) { export async function queryPrice(data: any) {
const { appid, timestamp, sign } = createSimpleSign()
let dataOrign = { let dataOrign = {
crypto: 'ETH', crypto: 'ETH',
network: 'ETH', network: 'ETH',
@ -143,9 +167,10 @@ export async function queryPrice(data: any) {
side: 'BUY', side: 'BUY',
} }
dataOrign = { ...dataOrign, ...data } dataOrign = { ...dataOrign, ...data }
const { appid, timestamp, sign } = createSimpleSign(dataOrign)
const config = { const config = {
method: 'post', method: 'post',
url: process.env.ALCHEMY_API_BASE + '/merchant/order/quote', url: process.env.ALCHEMY_API_BASE + '/open/api/v3/merchant/order/quote',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
appId: appid, appId: appid,
@ -164,20 +189,21 @@ export async function queryPrice(data: any) {
* @returns * @returns
*/ */
export async function queryFiat() { export async function queryFiat() {
const { appid, timestamp, sign } = createSimpleSign()
let dataOrign = { let dataOrign = {
type: 'BUY', type: 'BUY',
} }
const { appid, timestamp, sign } = createSimpleSign(dataOrign)
let url = process.env.ALCHEMY_API_BASE + '/open/api/v3/merchant/fiat/list'
url = generateKVStr({ data: dataOrign, encode: true, uri: url })
const config = { const config = {
method: 'GET', method: 'GET',
url: process.env.ALCHEMY_API_BASE + '/merchant/fiat/list', url,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
appId: appid, appId: appid,
sign, sign,
timestamp, timestamp,
}, },
data: dataOrign,
} }
let response = await axios(config) let response = await axios(config)
return response.data return response.data

View File

@ -5,7 +5,7 @@
* @param splitChar , & * @param splitChar , &
* @param equalChar = * @param equalChar =
*/ */
export function generateKeyValStr(data: {}, ignoreNull = true, splitChar: string = '&', equalChar = '=') { export function generateKeyValStr(data: Record<string, any>, ignoreNull = true, splitChar = '&', equalChar = '=') {
const keys = Object.keys(data) const keys = Object.keys(data)
keys.sort() keys.sort()
let result = '' let result = ''
@ -15,7 +15,7 @@ export function generateKeyValStr(data: {}, ignoreNull = true, splitChar: string
return return
} }
if (i++ > 0) result += splitChar if (i++ > 0) result += splitChar
result += `${key}${equalChar}${data[key]}` result += `${key}${equalChar}${data[key]}`
} }
return result return result
} }
@ -47,15 +47,15 @@ export function keyValToObject(str: string, splitChar: string = '&', equalChar =
export function isTrue(obj) { export function isTrue(obj) {
return ( return (
obj === 'true' || obj === 'true' ||
obj === 'TRUE' || obj === 'TRUE' ||
obj === 'True' || obj === 'True' ||
obj === 'on' || obj === 'on' ||
obj === 'ON' || obj === 'ON' ||
obj === true || obj === true ||
obj === 1 || obj === 1 ||
obj === '1' || obj === '1' ||
obj === 'YES' || obj === 'YES' ||
obj === 'yes' obj === 'yes'
) )
} }
@ -84,7 +84,7 @@ export function string10to62(number: string | number) {
qutient = (qutient - mod) / radix qutient = (qutient - mod) / radix
arr.unshift(chars[mod]) arr.unshift(chars[mod])
} while (qutient) } while (qutient)
return arr.join('') return arr.join('')
} }
/** /**
@ -105,32 +105,33 @@ export function string62to10(numberCode: string) {
return originNumber return originNumber
} }
const reNormalUUID = /^[0-9a-fA-F-]{36}$/; const reNormalUUID = /^[0-9a-fA-F-]{36}$/
const reLongUUID = /^[0-9a-fA-F]{32}$/; const reLongUUID = /^[0-9a-fA-F]{32}$/
const reShortUUID = /^[0-9a-zA-Z+/]{22,23}$/; const reShortUUID = /^[0-9a-zA-Z+/]{22,23}$/
const n = /-/g; const n = /-/g
export function compressUuid(e:string, t: boolean = false) { export function compressUuid(e: string, t: boolean = false) {
if (reNormalUUID.test(e)) { if (reNormalUUID.test(e)) {
e = e.replace(n, ''); e = e.replace(n, '')
} else if (!reLongUUID.test(e)) { } else if (!reLongUUID.test(e)) {
return e; return e
} }
var r = !0 === t ? 2 : 5; var r = !0 === t ? 2 : 5
return compressHex(e, r) return compressHex(e, r)
} }
const CHARS_BASE64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; const CHARS_BASE64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
export function compressHex(e: string, r: number) { export function compressHex(e: string, r: number) {
var i, n = e.length; var i,
i = void 0 !== r ? r : n % 3; n = e.length
for (var s = e.slice(0, i), o = []; i < n;) { i = void 0 !== r ? r : n % 3
for (var s = e.slice(0, i), o = []; i < n; ) {
var u = parseInt(e[i], 16), var u = parseInt(e[i], 16),
a = parseInt(e[i + 1], 16), a = parseInt(e[i + 1], 16),
c = parseInt(e[i + 2], 16); c = parseInt(e[i + 2], 16)
o.push(CHARS_BASE64[u << 2 | a >> 2]); o.push(CHARS_BASE64[(u << 2) | (a >> 2)])
o.push(CHARS_BASE64[(3 & a) << 4 | c]); o.push(CHARS_BASE64[((3 & a) << 4) | c])
i += 3; i += 3
} }
return s + o.join('') return s + o.join('')
} }