deduplicate task parsing and masking helpers
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
import { getAgisoShopConfig } from '../platforms/agiso/shop-config-service.js'
|
||||
import { safeParseJson } from './admin-query-utils.js'
|
||||
import { normalizeAdminRole } from './admin-auth-service.js'
|
||||
import {
|
||||
parseTaskContext as parseTaskContextValue,
|
||||
parseTaskState as parseTaskStateValue,
|
||||
} from '../../utils/task-json.js'
|
||||
|
||||
import type { AdminViewerSessionInput } from '../../types/admin-read-inputs.js'
|
||||
import type { OrderItemRow, TaskRow } from '../../types/repository-rows.js'
|
||||
@@ -227,39 +231,11 @@ export function resolveDisplayShopName(provider: unknown, shopId: unknown, shopN
|
||||
}
|
||||
|
||||
export function parseTaskContext(task: TaskLike | null | undefined): JsonRecord {
|
||||
const value = task?.context_json
|
||||
|
||||
if (!value) {
|
||||
return {}
|
||||
}
|
||||
|
||||
if (typeof value === 'object') {
|
||||
return value
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(String(value || '{}'))
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
return parseTaskContextValue(task)
|
||||
}
|
||||
|
||||
export function parseTaskState(task: TaskLike | null | undefined): JsonRecord {
|
||||
const value = task?.state_json
|
||||
|
||||
if (!value) {
|
||||
return {}
|
||||
}
|
||||
|
||||
if (typeof value === 'object') {
|
||||
return value
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(String(value || '{}'))
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
return parseTaskStateValue(task)
|
||||
}
|
||||
|
||||
export function resolveOrderItemTitle(item: Partial<OrderItemRow> | null | undefined): string {
|
||||
|
||||
@@ -1,29 +1,16 @@
|
||||
import {
|
||||
maskPhone as maskPhoneValue,
|
||||
maskSecret as maskSecretValue,
|
||||
} from "../../../utils/masking.js";
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
export function maskSecret(value: unknown) {
|
||||
const normalized = String(value || "").trim();
|
||||
if (!normalized) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (normalized.length <= 10) {
|
||||
return `${normalized.slice(0, 2)}****${normalized.slice(-2)}`;
|
||||
}
|
||||
|
||||
return `${normalized.slice(0, 6)}****${normalized.slice(-6)}`;
|
||||
return maskSecretValue(value);
|
||||
}
|
||||
|
||||
export function maskPhone(value: unknown) {
|
||||
const normalized = String(value || "").trim();
|
||||
if (!normalized) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (normalized.length < 7) {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
return `${normalized.slice(0, 3)}****${normalized.slice(-4)}`;
|
||||
return maskPhoneValue(value, { maskShort: false });
|
||||
}
|
||||
|
||||
export function mapAdminKuaishouEticketShopItem(item: JsonObject = {}) {
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
import { normalizeProductName } from '../../order/product-match-service.js'
|
||||
import { getCloudtentaclesSourceConfig } from '../../platforms/cloudtentacles/source-config-service.js'
|
||||
import { getCloudtentaclesSessionState } from '../../platforms/cloudtentacles/session-state-service.js'
|
||||
import { resolveCloudtentaclesConfig } from '../../platforms/cloudtentacles/shared.js'
|
||||
import {
|
||||
appointCloudtentaclesVirtualNumber,
|
||||
backCloudtentaclesVirtualNumber,
|
||||
fetchCloudtentaclesVirtualNumberCode,
|
||||
generateCloudtentaclesLoginCode,
|
||||
getCloudtentaclesBindUrl,
|
||||
verifyCloudtentaclesLoginCode,
|
||||
} from '../../platforms/cloudtentacles/virtual-number-service.js'
|
||||
import { createHttpError } from '../../../utils/http.js'
|
||||
import type { HttpErrorLike } from '../../../utils/http.js'
|
||||
import {
|
||||
resolvePersistedCloudtentaclesContext as resolveFulfillmentPersistedCloudtentaclesContext,
|
||||
} from '../../fulfillment/kuaishou-cloud/cloudtentacles-context.js'
|
||||
|
||||
export const KUAISHOU_CLOUD_FIXED_VN_KEY = '1'
|
||||
|
||||
export type JsonObject = Record<string, any>
|
||||
export {
|
||||
KUAISHOU_CLOUD_FIXED_VN_KEY,
|
||||
isKuaishouCloudTask,
|
||||
normalizeKuaishouCloudFlow,
|
||||
normalizeKuaishouCloudRoleInfo,
|
||||
resolveKuaishouCloudBindUrlExpiresAt,
|
||||
type JsonObject,
|
||||
} from '../../fulfillment/kuaishou-cloud/domain.js'
|
||||
export {
|
||||
prepareKuaishouCloudBindResourceWithFallback,
|
||||
resolveKuaishouCloudBindingResources,
|
||||
resolveKuaishouCloudVnKeyCandidates,
|
||||
} from '../../fulfillment/kuaishou-cloud/binding-resources.js'
|
||||
|
||||
export type CloudtentaclesContext = {
|
||||
baseUrl: string
|
||||
@@ -31,17 +33,17 @@ export type CloudSkuLikeItem = {
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type KuaishouCloudFlow = JsonObject & {
|
||||
export type KuaishouCloudFlow = Record<string, any> & {
|
||||
configId: string
|
||||
internalSkuCode: string
|
||||
internalSkuName: string
|
||||
ticket: JsonObject
|
||||
binding: JsonObject
|
||||
role: JsonObject
|
||||
purchase: JsonObject
|
||||
dispatch: JsonObject
|
||||
returnNumber: JsonObject
|
||||
consume: JsonObject
|
||||
ticket: Record<string, any>
|
||||
binding: Record<string, any>
|
||||
role: Record<string, any>
|
||||
purchase: Record<string, any>
|
||||
dispatch: Record<string, any>
|
||||
returnNumber: Record<string, any>
|
||||
consume: Record<string, any>
|
||||
}
|
||||
|
||||
export type KuaishouCloudBindingResources = {
|
||||
@@ -60,332 +62,23 @@ export type PreparedKuaishouCloudBindResource = {
|
||||
bindUrl: string
|
||||
}
|
||||
|
||||
export function isKuaishouCloudTask(task: unknown): boolean {
|
||||
const record: JsonObject = task && typeof task === 'object' ? task : {}
|
||||
return String(record.executor_key || '').trim() === 'kuaishou_ct_assisted'
|
||||
}
|
||||
|
||||
export function resolvePersistedCloudtentaclesContext(): CloudtentaclesContext {
|
||||
const source = getCloudtentaclesSourceConfig()
|
||||
const session = getCloudtentaclesSessionState()
|
||||
const token = String(session.token || '').trim()
|
||||
|
||||
if (!token) {
|
||||
throw createHttpError('当前 cloudtentacles 没有可用 token,请先到平台配置完成登录校验', {
|
||||
statusCode: 409,
|
||||
errorCode: 'admin_task_kuaishou_cloud_missing_cloud_token',
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
baseUrl: String(session.baseUrl || source.baseUrl || '').trim() || 'https://123.207.217.176',
|
||||
token,
|
||||
deviceId: String(session.deviceId || source.deviceId || '-').trim() || '-',
|
||||
deviceType: Number(session.deviceType ?? source.deviceType ?? 0),
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeKuaishouCloudFlow(value: unknown): KuaishouCloudFlow {
|
||||
const source: JsonObject = value && typeof value === 'object' ? value : {}
|
||||
const binding = source.binding && typeof source.binding === 'object' ? source.binding : {}
|
||||
const role = source.role && typeof source.role === 'object' ? source.role : {}
|
||||
const purchase = source.purchase && typeof source.purchase === 'object' ? source.purchase : {}
|
||||
const dispatch = source.dispatch && typeof source.dispatch === 'object' ? source.dispatch : {}
|
||||
const returnNumber = source.returnNumber && typeof source.returnNumber === 'object' ? source.returnNumber : {}
|
||||
const consume = source.consume && typeof source.consume === 'object' ? source.consume : {}
|
||||
const ticket = source.ticket && typeof source.ticket === 'object' ? source.ticket : {}
|
||||
const bindPreparedAt = binding.bindPreparedAt || null
|
||||
|
||||
return {
|
||||
...source,
|
||||
configId: String(source.configId || '').trim(),
|
||||
internalSkuCode: String(source.internalSkuCode || '').trim(),
|
||||
internalSkuName: String(source.internalSkuName || '').trim(),
|
||||
ticket: {
|
||||
code: String(ticket.code || '').trim(),
|
||||
status: String(ticket.status || 'pending').trim() || 'pending',
|
||||
capturedAt: ticket.capturedAt || null,
|
||||
capturedBy: ticket.capturedBy || null,
|
||||
verifiedAt: ticket.verifiedAt || null,
|
||||
oid: String(ticket.oid || '').trim(),
|
||||
formToken: String(ticket.formToken || '').trim(),
|
||||
leftCount: Number(ticket.leftCount || 0) || 0,
|
||||
goodsTitle: String(ticket.goodsTitle || '').trim(),
|
||||
},
|
||||
binding: {
|
||||
prepareStatus: String(binding.prepareStatus || 'pending').trim() || 'pending',
|
||||
cloudSourceKey: String(binding.cloudSourceKey || 'default').trim() || 'default',
|
||||
skuId: Number(binding.skuId || 0) || 0,
|
||||
skuName: String(binding.skuName || '').trim(),
|
||||
vnKey: String(binding.vnKey || '').trim(),
|
||||
vnId: Number(binding.vnId || 0) || 0,
|
||||
vnPhone: String(binding.vnPhone || '').trim(),
|
||||
bindUrl: String(binding.bindUrl || '').trim(),
|
||||
bindPreparedAt,
|
||||
bindExpiresAt: binding.bindExpiresAt || resolveKuaishouCloudBindUrlExpiresAt(bindPreparedAt),
|
||||
bindProbeAt: binding.bindProbeAt || null,
|
||||
bindProbeStatus: String(binding.bindProbeStatus || '').trim(),
|
||||
bindProbeMessage: String(binding.bindProbeMessage || '').trim(),
|
||||
roleName: String(binding.roleName || '').trim(),
|
||||
roleId: String(binding.roleId || '').trim(),
|
||||
},
|
||||
role: {
|
||||
status: String(role.status || 'pending').trim() || 'pending',
|
||||
name: String(role.name || '').trim(),
|
||||
rid: String(role.rid || '').trim(),
|
||||
refreshedAt: role.refreshedAt || null,
|
||||
errorMessage: String(role.errorMessage || '').trim(),
|
||||
rawInfo: role.rawInfo && typeof role.rawInfo === 'object' ? role.rawInfo : null,
|
||||
},
|
||||
purchase: {
|
||||
autoBuyEnabled: purchase.autoBuyEnabled !== false,
|
||||
minAssetReserve: Number(purchase.minAssetReserve || 0) || 0,
|
||||
usedKnapsack: purchase.usedKnapsack === true,
|
||||
purchaseTriggered: purchase.purchaseTriggered === true,
|
||||
assetBefore: Number(purchase.assetBefore || 0) || 0,
|
||||
assetAfter: Number(purchase.assetAfter || 0) || 0,
|
||||
purchaseAt: purchase.purchaseAt || null,
|
||||
},
|
||||
dispatch: {
|
||||
status: String(dispatch.status || 'pending').trim() || 'pending',
|
||||
dispatchAt: dispatch.dispatchAt || null,
|
||||
dispatchBy: dispatch.dispatchBy || null,
|
||||
sendType: Number(dispatch.sendType || 0) || 0,
|
||||
note: String(dispatch.note || '').trim(),
|
||||
},
|
||||
returnNumber: {
|
||||
status: String(returnNumber.status || 'pending').trim() || 'pending',
|
||||
returnedAt: returnNumber.returnedAt || null,
|
||||
returnedBy: returnNumber.returnedBy || null,
|
||||
autoReturnEnabled: returnNumber.autoReturnEnabled === true,
|
||||
},
|
||||
consume: {
|
||||
status: String(consume.status || 'pending').trim() || 'pending',
|
||||
shopId: String(consume.shopId || '').trim(),
|
||||
shopName: String(consume.shopName || '').trim(),
|
||||
autoConsumeEnabled: consume.autoConsumeEnabled === true,
|
||||
consumedAt: consume.consumedAt || null,
|
||||
errorMessage: String(consume.errorMessage || '').trim(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeKuaishouCloudRoleInfo(value: unknown): { name: string, rid: string, rawInfo: JsonObject | null } {
|
||||
const rawInfo: JsonObject | null = value && typeof value === 'object' ? value : null
|
||||
const nestedBindInfo = rawInfo?.sBindInfo && typeof rawInfo.sBindInfo === 'object' ? rawInfo.sBindInfo : null
|
||||
const source = nestedBindInfo || rawInfo
|
||||
|
||||
return {
|
||||
name: String(source?.name || source?.roleName || source?.nickname || source?.sRoleName || '').trim(),
|
||||
rid: String(source?.rid || source?.roleId || source?.uid || source?.sRoleId || source?.sUserId || '').trim(),
|
||||
rawInfo,
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveKuaishouCloudBindUrlExpiresAt(preparedAt: unknown): string | null {
|
||||
const preparedTime = Date.parse(String(preparedAt || ''))
|
||||
if (!Number.isFinite(preparedTime)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const config = resolveCloudtentaclesConfig()
|
||||
const ttlSeconds = Number(config.bindUrlTtlSeconds || 600)
|
||||
return new Date(preparedTime + Math.max(1, ttlSeconds) * 1000).toISOString()
|
||||
}
|
||||
|
||||
export function resolveKuaishouCloudBindingResources(
|
||||
flow: KuaishouCloudFlow,
|
||||
{ skuItems = [], knapsackItems = [] }: { skuItems?: unknown[], knapsackItems?: unknown[] } = {},
|
||||
): KuaishouCloudBindingResources {
|
||||
const normalizedSkuItems = Array.isArray(skuItems) ? skuItems.filter(isCloudSkuLikeItem) : []
|
||||
const normalizedKnapsackItems = Array.isArray(knapsackItems) ? knapsackItems.filter(isCloudSkuLikeItem) : []
|
||||
const currentSkuId = Number(flow?.binding?.skuId || 0) || 0
|
||||
const currentSkuName = String(flow?.binding?.skuName || '').trim()
|
||||
const nameCandidates = collectKuaishouCloudNameCandidates(flow)
|
||||
|
||||
const skuItemById = currentSkuId > 0
|
||||
? normalizedSkuItems.find((item) => Number(item.id || 0) === currentSkuId) || null
|
||||
: null
|
||||
const knapsackItemById = currentSkuId > 0
|
||||
? normalizedKnapsackItems.find((item) => Number(item.id || 0) === currentSkuId) || null
|
||||
: null
|
||||
|
||||
if (skuItemById || knapsackItemById) {
|
||||
const matchedItem = skuItemById || knapsackItemById
|
||||
return {
|
||||
skuId: Number(matchedItem?.id || 0) || 0,
|
||||
skuName: String(currentSkuName || matchedItem?.name || '').trim(),
|
||||
vnKey: KUAISHOU_CLOUD_FIXED_VN_KEY,
|
||||
skuItem: skuItemById,
|
||||
knapsackItem: knapsackItemById,
|
||||
resolvedByName: false,
|
||||
try {
|
||||
return resolveFulfillmentPersistedCloudtentaclesContext()
|
||||
} catch (error) {
|
||||
if (isMissingCloudtentaclesTokenError(error)) {
|
||||
throw createHttpError('当前 cloudtentacles 没有可用 token,请先到平台配置完成登录校验', {
|
||||
statusCode: 409,
|
||||
errorCode: 'admin_task_kuaishou_cloud_missing_cloud_token',
|
||||
cause: error,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const matchedSkuItem = findCloudItemByNames(normalizedSkuItems, nameCandidates)
|
||||
const matchedKnapsackItem = findCloudItemByNames(
|
||||
normalizedKnapsackItems,
|
||||
nameCandidates,
|
||||
matchedSkuItem ? Number(matchedSkuItem.id || 0) : 0,
|
||||
)
|
||||
const matchedItem = matchedSkuItem || matchedKnapsackItem
|
||||
|
||||
return {
|
||||
skuId: Number(matchedItem?.id || 0) || 0,
|
||||
skuName: String(currentSkuName || matchedItem?.name || '').trim(),
|
||||
vnKey: KUAISHOU_CLOUD_FIXED_VN_KEY,
|
||||
skuItem: matchedSkuItem,
|
||||
knapsackItem: matchedKnapsackItem,
|
||||
resolvedByName: Boolean(matchedItem),
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveKuaishouCloudVnKeyCandidates(input: { flow?: KuaishouCloudFlow, binding?: KuaishouCloudBindingResources } = {}): string[] {
|
||||
return [KUAISHOU_CLOUD_FIXED_VN_KEY]
|
||||
}
|
||||
|
||||
export async function prepareKuaishouCloudBindResourceWithFallback(input: {
|
||||
cloudContext?: CloudtentaclesContext
|
||||
vnKeyCandidates?: string[]
|
||||
} = {}): Promise<PreparedKuaishouCloudBindResource> {
|
||||
const { cloudContext = {}, vnKeyCandidates = [] } = input
|
||||
const candidates = Array.isArray(vnKeyCandidates) ? vnKeyCandidates : []
|
||||
let lastError: unknown = null
|
||||
|
||||
for (const vnKey of candidates) {
|
||||
let vnId = 0
|
||||
let vnPhone = ''
|
||||
|
||||
try {
|
||||
const appointed = await appointCloudtentaclesVirtualNumber({
|
||||
...cloudContext,
|
||||
key: vnKey,
|
||||
})
|
||||
vnId = Number(appointed.item?.id || 0)
|
||||
vnPhone = String(appointed.item?.phone || '').trim()
|
||||
|
||||
if (!vnId || !vnPhone) {
|
||||
throw createHttpError('申请虚拟号成功但返回数据不完整', {
|
||||
statusCode: 502,
|
||||
errorCode: 'admin_task_kuaishou_cloud_invalid_vn',
|
||||
})
|
||||
}
|
||||
|
||||
await generateCloudtentaclesLoginCode({
|
||||
...cloudContext,
|
||||
key: vnKey,
|
||||
id: vnId,
|
||||
})
|
||||
|
||||
const fetchedCode = await fetchCloudtentaclesVirtualNumberCode({
|
||||
...cloudContext,
|
||||
key: vnKey,
|
||||
phone: vnPhone,
|
||||
})
|
||||
|
||||
await verifyCloudtentaclesLoginCode({
|
||||
...cloudContext,
|
||||
key: vnKey,
|
||||
id: vnId,
|
||||
code: fetchedCode.code,
|
||||
})
|
||||
|
||||
const bindUrlResult = await getCloudtentaclesBindUrl({
|
||||
...cloudContext,
|
||||
key: vnKey,
|
||||
id: vnId,
|
||||
})
|
||||
|
||||
return {
|
||||
vnKey,
|
||||
vnId,
|
||||
vnPhone,
|
||||
bindUrl: String(bindUrlResult.bindUrl || '').trim(),
|
||||
}
|
||||
} catch (error) {
|
||||
lastError = error
|
||||
|
||||
if (vnId > 0) {
|
||||
try {
|
||||
await backCloudtentaclesVirtualNumber({
|
||||
...cloudContext,
|
||||
key: vnKey,
|
||||
id: vnId,
|
||||
})
|
||||
} catch {
|
||||
// 兜底退号失败时保留原始错误,避免吞掉主链路异常
|
||||
}
|
||||
}
|
||||
|
||||
if (!isRecoverableKuaishouCloudVnKeyError(error)) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError || createHttpError('没有找到可用的 VN Key', {
|
||||
statusCode: 409,
|
||||
errorCode: 'admin_task_kuaishou_cloud_missing_binding_config',
|
||||
})
|
||||
}
|
||||
|
||||
function collectKuaishouCloudNameCandidates(flow: KuaishouCloudFlow): string[] {
|
||||
return Array.from(new Set([
|
||||
String(flow?.binding?.skuName || '').trim(),
|
||||
String(flow?.internalSkuName || '').trim(),
|
||||
String(flow?.internalSkuCode || '').trim(),
|
||||
].filter(Boolean)))
|
||||
}
|
||||
|
||||
function findCloudItemByNames(items: CloudSkuLikeItem[], nameCandidates: string[], preferredId = 0): CloudSkuLikeItem | null {
|
||||
const normalizedItems = Array.isArray(items) ? items : []
|
||||
const normalizedNames = nameCandidates
|
||||
.map((item) => ({
|
||||
raw: String(item || '').trim(),
|
||||
normalized: normalizeProductName(item),
|
||||
}))
|
||||
.filter((item) => item.raw && item.normalized)
|
||||
|
||||
if (normalizedNames.length === 0 || normalizedItems.length === 0) {
|
||||
return preferredId > 0
|
||||
? normalizedItems.find((item) => Number(item.id || 0) === preferredId) || null
|
||||
: null
|
||||
}
|
||||
|
||||
if (preferredId > 0) {
|
||||
const preferred = normalizedItems.find((item) => Number(item.id || 0) === preferredId) || null
|
||||
if (preferred) {
|
||||
return preferred
|
||||
}
|
||||
}
|
||||
|
||||
const exactMatches = normalizedItems.filter((item) => {
|
||||
const itemName = normalizeProductName(item.name)
|
||||
return normalizedNames.some((candidate) => candidate.normalized === itemName)
|
||||
})
|
||||
if (exactMatches.length > 0) {
|
||||
return exactMatches[0]
|
||||
}
|
||||
|
||||
const partialMatches = normalizedItems.filter((item) => {
|
||||
const itemName = normalizeProductName(item.name)
|
||||
return normalizedNames.some((candidate) => itemName.includes(candidate.normalized) || candidate.normalized.includes(itemName))
|
||||
})
|
||||
if (partialMatches.length > 0) {
|
||||
return partialMatches.sort((left, right) => String(left.name || '').length - String(right.name || '').length)[0]
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function isCloudSkuLikeItem(item: unknown): item is CloudSkuLikeItem {
|
||||
const record: JsonObject = item && typeof item === 'object' ? item : {}
|
||||
return Number(record.id || 0) > 0
|
||||
}
|
||||
|
||||
function isRecoverableKuaishouCloudVnKeyError(error: any): boolean {
|
||||
const errorCode = String(error?.errorCode || error?.code || '').trim()
|
||||
const errorMessage = String(error?.message || '').trim()
|
||||
return errorCode === 'cloudtentacles_vn_bind_url_failed'
|
||||
&& errorMessage.includes('不支持的游戏类型')
|
||||
function isMissingCloudtentaclesTokenError(error: unknown): boolean {
|
||||
const current = error as HttpErrorLike | null
|
||||
return String(current?.errorCode || '').trim() === 'kuaishou_cloud_missing_cloud_token'
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { buildClaimUrl, createTaskClaimToken } from '../../claim/claim-service.js'
|
||||
import { createHttpError } from '../../../utils/http.js'
|
||||
import {
|
||||
maskCode as maskCodeValue,
|
||||
maskPhone as maskPhoneValue,
|
||||
} from '../../../utils/masking.js'
|
||||
import {
|
||||
canViewerConfirmAssistedRole,
|
||||
canViewerRedeemAssistedTask,
|
||||
@@ -24,29 +28,11 @@ type ErrorLike = {
|
||||
}
|
||||
|
||||
export function maskPhone(value: unknown): string {
|
||||
const text = String(value || '').trim()
|
||||
if (!text) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (text.length <= 7) {
|
||||
return `${text.slice(0, 2)}***${text.slice(-2)}`
|
||||
}
|
||||
|
||||
return `${text.slice(0, 3)}****${text.slice(-4)}`
|
||||
return maskPhoneValue(value)
|
||||
}
|
||||
|
||||
export function maskCode(value: unknown): string {
|
||||
const text = String(value || '').trim()
|
||||
if (!text) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (text.length <= 8) {
|
||||
return `${text.slice(0, 2)}***${text.slice(-2)}`
|
||||
}
|
||||
|
||||
return `${text.slice(0, 4)}****${text.slice(-4)}`
|
||||
return maskCodeValue(value)
|
||||
}
|
||||
|
||||
export function resolveTaskInventoryGroupCodes(task: TaskRow): string[] | null {
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { buildClaimUrl } from '../claim-service.js'
|
||||
import { createHttpError } from '../../../utils/http.js'
|
||||
import { maskCode as maskCodeValue } from '../../../utils/masking.js'
|
||||
import { formatFenToAmount, normalizeFen } from '../../../utils/money.js'
|
||||
import {
|
||||
parseTaskContext as parseTaskContextValue,
|
||||
parseTaskState as parseTaskStateValue,
|
||||
} from '../../../utils/task-json.js'
|
||||
|
||||
export const CLAIM_TERMINAL_STATUSES = new Set(['expired', 'closed'])
|
||||
export const REDEEM_REPLACEMENT_LIMIT = 10
|
||||
@@ -178,21 +183,7 @@ export function mergeTaskContext(task, patch = {}) {
|
||||
}
|
||||
|
||||
export function parseTaskState(task) {
|
||||
const value = task?.state_json
|
||||
|
||||
if (!value) {
|
||||
return {}
|
||||
}
|
||||
|
||||
if (typeof value === 'object') {
|
||||
return value
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(String(value || '{}'))
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
return parseTaskStateValue(task)
|
||||
}
|
||||
|
||||
export function isAssistedClaimTask(task) {
|
||||
@@ -200,21 +191,7 @@ export function isAssistedClaimTask(task) {
|
||||
}
|
||||
|
||||
export function parseTaskContext(task) {
|
||||
const value = task?.context_json
|
||||
|
||||
if (!value) {
|
||||
return {}
|
||||
}
|
||||
|
||||
if (typeof value === 'object') {
|
||||
return value
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(String(value || '{}'))
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
return parseTaskContextValue(task)
|
||||
}
|
||||
|
||||
export function normalizeClaimLoginType(loginType) {
|
||||
@@ -227,15 +204,5 @@ export function isRecoverableSessionError(error) {
|
||||
}
|
||||
|
||||
export function maskCode(value) {
|
||||
const text = String(value || '').trim()
|
||||
|
||||
if (!text) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (text.length <= 8) {
|
||||
return `${text.slice(0, 2)}****${text.slice(-2)}`
|
||||
}
|
||||
|
||||
return `${text.slice(0, 4)}****${text.slice(-4)}`
|
||||
return maskCodeValue(value, { shortMask: '****' })
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { resolveCloudtentaclesConfig } from "../../platforms/cloudtentacles/shared.js";
|
||||
import {
|
||||
maskCode as maskCodeValue,
|
||||
maskPhone as maskPhoneValue,
|
||||
} from "../../../utils/masking.js";
|
||||
|
||||
export const KUAISHOU_CLOUD_FIXED_VN_KEY = "1";
|
||||
|
||||
@@ -154,29 +158,11 @@ export function normalizeKuaishouCloudRoleInfo(value) {
|
||||
}
|
||||
|
||||
export function maskPhone(value) {
|
||||
const text = String(value || "").trim();
|
||||
if (!text) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (text.length <= 7) {
|
||||
return `${text.slice(0, 2)}***${text.slice(-2)}`;
|
||||
}
|
||||
|
||||
return `${text.slice(0, 3)}****${text.slice(-4)}`;
|
||||
return maskPhoneValue(value);
|
||||
}
|
||||
|
||||
export function maskCode(value) {
|
||||
const text = String(value || "").trim();
|
||||
if (!text) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (text.length <= 8) {
|
||||
return `${text.slice(0, 2)}***${text.slice(-2)}`;
|
||||
}
|
||||
|
||||
return `${text.slice(0, 4)}****${text.slice(-4)}`;
|
||||
return maskCodeValue(value);
|
||||
}
|
||||
|
||||
export function resolveKuaishouCloudBindUrlExpiresAt(preparedAt) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { parseTaskContext as parseTaskContextValue } from "../../../utils/task-json.js";
|
||||
|
||||
export function normalizeActor(actor) {
|
||||
if (!actor || typeof actor !== "object") {
|
||||
return null;
|
||||
@@ -21,21 +23,7 @@ export function normalizeActor(actor) {
|
||||
}
|
||||
|
||||
export function parseTaskContext(task) {
|
||||
const value = task?.context_json;
|
||||
|
||||
if (!value) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (typeof value === "object") {
|
||||
return value;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(String(value || "{}"));
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
return parseTaskContextValue(task);
|
||||
}
|
||||
|
||||
export function getTaskClaimExpiresAt(task) {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
type MaskPhoneOptions = {
|
||||
maskShort?: boolean
|
||||
shortMask?: string
|
||||
}
|
||||
|
||||
type MaskCodeOptions = {
|
||||
shortMask?: string
|
||||
}
|
||||
|
||||
export function maskSecret(value: unknown): string {
|
||||
const normalized = String(value || '').trim()
|
||||
if (!normalized) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (normalized.length <= 10) {
|
||||
return `${normalized.slice(0, 2)}****${normalized.slice(-2)}`
|
||||
}
|
||||
|
||||
return `${normalized.slice(0, 6)}****${normalized.slice(-6)}`
|
||||
}
|
||||
|
||||
export function maskPhone(value: unknown, options: MaskPhoneOptions = {}): string {
|
||||
const text = String(value || '').trim()
|
||||
if (!text) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (text.length <= 7) {
|
||||
if (options.maskShort === false) {
|
||||
return text
|
||||
}
|
||||
|
||||
return `${text.slice(0, 2)}${options.shortMask || '***'}${text.slice(-2)}`
|
||||
}
|
||||
|
||||
return `${text.slice(0, 3)}****${text.slice(-4)}`
|
||||
}
|
||||
|
||||
export function maskCode(value: unknown, options: MaskCodeOptions = {}): string {
|
||||
const text = String(value || '').trim()
|
||||
if (!text) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (text.length <= 8) {
|
||||
return `${text.slice(0, 2)}${options.shortMask || '***'}${text.slice(-2)}`
|
||||
}
|
||||
|
||||
return `${text.slice(0, 4)}****${text.slice(-4)}`
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
export type JsonRecord = Record<string, any>
|
||||
|
||||
export function parseJsonObject(value: unknown): JsonRecord {
|
||||
if (!value) {
|
||||
return {}
|
||||
}
|
||||
|
||||
if (typeof value === 'object' && !Array.isArray(value)) {
|
||||
return value as JsonRecord
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(String(value || '{}'))
|
||||
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
export function parseTaskContext(task: { context_json?: unknown } | null | undefined): JsonRecord {
|
||||
return parseJsonObject(task?.context_json)
|
||||
}
|
||||
|
||||
export function parseTaskState(task: { state_json?: unknown } | null | undefined): JsonRecord {
|
||||
return parseJsonObject(task?.state_json)
|
||||
}
|
||||
Reference in New Issue
Block a user