|
|
|
@@ -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'
|
|
|
|
|
}
|
|
|
|
|