抽离后台平台配置上下文解析辅助逻辑
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
// @ts-check
|
||||
|
||||
export function pickFirstNonEmpty(values) {
|
||||
for (const value of values) {
|
||||
const normalized = String(value || '').trim()
|
||||
if (normalized) {
|
||||
return normalized
|
||||
}
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
export function isPlainObject(value) {
|
||||
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
|
||||
}
|
||||
|
||||
export function resolveKuaishouEticketAdminContext(
|
||||
payload = {},
|
||||
options = {},
|
||||
) {
|
||||
const savedSource = /** @type {Record<string, unknown>} */ (options.savedSource || {})
|
||||
const findShopConfig = /** @type {(shopId: string, source: Record<string, unknown>) => unknown} */ (
|
||||
options.findShopConfig || (() => null)
|
||||
)
|
||||
const getFirstAvailableShop = /** @type {(source: Record<string, unknown>) => unknown} */ (
|
||||
options.getFirstAvailableShop || (() => null)
|
||||
)
|
||||
const defaultBaseUrl = String(options.defaultBaseUrl || 'https://s.kwaixiaodian.com').trim()
|
||||
const requestedShopId = String(payload.shopId || '').trim()
|
||||
const configuredShop = /** @type {Record<string, unknown> | null} */ (
|
||||
requestedShopId
|
||||
? findShopConfig(requestedShopId, savedSource)
|
||||
: getFirstAvailableShop(savedSource)
|
||||
)
|
||||
|
||||
return {
|
||||
baseUrl: pickFirstNonEmpty([payload.baseUrl, savedSource.baseUrl, defaultBaseUrl]),
|
||||
cookie: pickFirstNonEmpty([payload.cookie, configuredShop?.cookie]),
|
||||
shopId: pickFirstNonEmpty([requestedShopId, configuredShop?.shopId]),
|
||||
shop: configuredShop,
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveCloudtentaclesAdminContext(
|
||||
payload = {},
|
||||
options = {},
|
||||
) {
|
||||
const savedSource = /** @type {Record<string, unknown>} */ (options.savedSource || {})
|
||||
const persistedSession = /** @type {Record<string, unknown>} */ (options.persistedSession || {})
|
||||
const defaultBaseUrl = String(options.defaultBaseUrl || 'https://123.207.217.176').trim()
|
||||
return {
|
||||
baseUrl: pickFirstNonEmpty([payload.baseUrl, savedSource.baseUrl, defaultBaseUrl]),
|
||||
token: pickFirstNonEmpty([payload.token, persistedSession.token]),
|
||||
deviceId: pickFirstNonEmpty([payload.deviceId, savedSource.deviceId, persistedSession.deviceId, '-']),
|
||||
deviceType: payload.deviceType ?? savedSource.deviceType ?? persistedSession.deviceType,
|
||||
}
|
||||
}
|
||||
|
||||
export function hasCloudtentaclesCredentialContextChanged(current = {}, next = {}) {
|
||||
return (
|
||||
String(current.baseUrl || '').trim() !== String(next.baseUrl || '').trim()
|
||||
|| String(current.username || '').trim() !== String(next.username || '').trim()
|
||||
|| String(current.phone || '').trim() !== String(next.phone || '').trim()
|
||||
|| String(current.deviceId || '').trim() !== String(next.deviceId || '').trim()
|
||||
|| Number(current.deviceType || 0) !== Number(next.deviceType || 0)
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
import {
|
||||
hasCloudtentaclesCredentialContextChanged,
|
||||
isPlainObject,
|
||||
pickFirstNonEmpty,
|
||||
resolveCloudtentaclesAdminContext,
|
||||
resolveKuaishouEticketAdminContext,
|
||||
} from './admin-platform-config-context.js'
|
||||
|
||||
test('pickFirstNonEmpty returns first trimmed non-empty string value', () => {
|
||||
assert.equal(pickFirstNonEmpty(['', ' ', ' value ', 'fallback']), 'value')
|
||||
assert.equal(pickFirstNonEmpty([null, undefined, 0, '']), '')
|
||||
})
|
||||
|
||||
test('isPlainObject only accepts non-array objects', () => {
|
||||
assert.equal(isPlainObject({ a: 1 }), true)
|
||||
assert.equal(isPlainObject([]), false)
|
||||
assert.equal(isPlainObject(null), false)
|
||||
assert.equal(isPlainObject('x'), false)
|
||||
})
|
||||
|
||||
test('resolveKuaishouEticketAdminContext prefers explicit payload values', () => {
|
||||
const context = resolveKuaishouEticketAdminContext(
|
||||
{
|
||||
baseUrl: ' https://override.example.com ',
|
||||
shopId: ' shop-2 ',
|
||||
cookie: ' cookie-2 ',
|
||||
},
|
||||
{
|
||||
savedSource: { baseUrl: 'https://saved.example.com' },
|
||||
findShopConfig(requestedShopId) {
|
||||
assert.equal(requestedShopId, 'shop-2')
|
||||
return { shopId: 'shop-2', cookie: 'saved-cookie-2' }
|
||||
},
|
||||
getFirstAvailableShop() {
|
||||
throw new Error('should not use first available shop')
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
assert.deepEqual(context, {
|
||||
baseUrl: 'https://override.example.com',
|
||||
cookie: 'cookie-2',
|
||||
shopId: 'shop-2',
|
||||
shop: { shopId: 'shop-2', cookie: 'saved-cookie-2' },
|
||||
})
|
||||
})
|
||||
|
||||
test('resolveKuaishouEticketAdminContext falls back to first available shop', () => {
|
||||
const context = resolveKuaishouEticketAdminContext(
|
||||
{},
|
||||
{
|
||||
savedSource: { baseUrl: ' https://saved.example.com ' },
|
||||
getFirstAvailableShop() {
|
||||
return { shopId: 'shop-1', cookie: 'cookie-1' }
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
assert.deepEqual(context, {
|
||||
baseUrl: 'https://saved.example.com',
|
||||
cookie: 'cookie-1',
|
||||
shopId: 'shop-1',
|
||||
shop: { shopId: 'shop-1', cookie: 'cookie-1' },
|
||||
})
|
||||
})
|
||||
|
||||
test('resolveCloudtentaclesAdminContext merges payload source and persisted session', () => {
|
||||
const context = resolveCloudtentaclesAdminContext(
|
||||
{
|
||||
token: ' token-1 ',
|
||||
deviceType: 3,
|
||||
},
|
||||
{
|
||||
savedSource: {
|
||||
baseUrl: ' https://saved.example.com ',
|
||||
deviceId: ' device-saved ',
|
||||
deviceType: 1,
|
||||
},
|
||||
persistedSession: {
|
||||
token: 'token-persisted',
|
||||
deviceId: 'device-persisted',
|
||||
deviceType: 2,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
assert.deepEqual(context, {
|
||||
baseUrl: 'https://saved.example.com',
|
||||
token: 'token-1',
|
||||
deviceId: 'device-saved',
|
||||
deviceType: 3,
|
||||
})
|
||||
})
|
||||
|
||||
test('hasCloudtentaclesCredentialContextChanged detects normalized credential changes', () => {
|
||||
assert.equal(
|
||||
hasCloudtentaclesCredentialContextChanged(
|
||||
{ baseUrl: ' https://a ', username: 'u1', phone: '13812345678', deviceId: 'd1', deviceType: 1 },
|
||||
{ baseUrl: 'https://a', username: 'u1', phone: '13812345678', deviceId: 'd1', deviceType: 1 },
|
||||
),
|
||||
false,
|
||||
)
|
||||
|
||||
assert.equal(
|
||||
hasCloudtentaclesCredentialContextChanged(
|
||||
{ baseUrl: 'https://a', username: 'u1', phone: '13812345678', deviceId: 'd1', deviceType: 1 },
|
||||
{ baseUrl: 'https://b', username: 'u1', phone: '13812345678', deviceId: 'd1', deviceType: 1 },
|
||||
),
|
||||
true,
|
||||
)
|
||||
})
|
||||
@@ -80,6 +80,13 @@ import { formatFenToAmount } from '../../utils/money.js'
|
||||
import { syncConfiguredFulfillmentBindings } from '../bootstrap/fulfillment-bootstrap-service.js'
|
||||
import { normalizeProductName } from '../order/product-match-service.js'
|
||||
import { resolveDisplayShopName } from './admin-read-shared-helpers.js'
|
||||
import {
|
||||
hasCloudtentaclesCredentialContextChanged,
|
||||
isPlainObject,
|
||||
pickFirstNonEmpty,
|
||||
resolveCloudtentaclesAdminContext,
|
||||
resolveKuaishouEticketAdminContext,
|
||||
} from './admin-platform-config-context.js'
|
||||
import {
|
||||
mapAdminCloudtentaclesSession,
|
||||
mapAdminCloudtentaclesSourceConfig,
|
||||
@@ -396,7 +403,11 @@ export async function syncAdminKhhaoOrders(payload = /** @type {AdminKhhaoOrderQ
|
||||
|
||||
/** @param {AdminKuaishouEticketDetailQueryInput} [payload] */
|
||||
export async function queryAdminKuaishouEticketDetail(payload = /** @type {AdminKuaishouEticketDetailQueryInput} */ ({})) {
|
||||
const context = resolveKuaishouEticketAdminContext(payload)
|
||||
const context = resolveKuaishouEticketAdminContext(payload, {
|
||||
savedSource: getKuaishouEticketSourceConfig(),
|
||||
findShopConfig: findKuaishouEticketShopConfig,
|
||||
getFirstAvailableShop: getFirstAvailableKuaishouEticketShop,
|
||||
})
|
||||
|
||||
return queryKuaishouEticketConsumeDetail({
|
||||
baseUrl: context.baseUrl,
|
||||
@@ -407,7 +418,11 @@ export async function queryAdminKuaishouEticketDetail(payload = /** @type {Admin
|
||||
|
||||
/** @param {AdminKuaishouEticketShopInfoInput} [payload] */
|
||||
export async function queryAdminKuaishouEticketShopInfo(payload = /** @type {AdminKuaishouEticketShopInfoInput} */ ({})) {
|
||||
const context = resolveKuaishouEticketAdminContext(payload)
|
||||
const context = resolveKuaishouEticketAdminContext(payload, {
|
||||
savedSource: getKuaishouEticketSourceConfig(),
|
||||
findShopConfig: findKuaishouEticketShopConfig,
|
||||
getFirstAvailableShop: getFirstAvailableKuaishouEticketShop,
|
||||
})
|
||||
return queryKuaishouEticketStableInfo({
|
||||
baseUrl: context.baseUrl,
|
||||
cookie: context.cookie,
|
||||
@@ -416,7 +431,11 @@ export async function queryAdminKuaishouEticketShopInfo(payload = /** @type {Adm
|
||||
|
||||
/** @param {AdminKuaishouEticketConsumeInput} [payload] */
|
||||
export async function consumeAdminKuaishouEticket(payload = /** @type {AdminKuaishouEticketConsumeInput} */ ({})) {
|
||||
const context = resolveKuaishouEticketAdminContext(payload)
|
||||
const context = resolveKuaishouEticketAdminContext(payload, {
|
||||
savedSource: getKuaishouEticketSourceConfig(),
|
||||
findShopConfig: findKuaishouEticketShopConfig,
|
||||
getFirstAvailableShop: getFirstAvailableKuaishouEticketShop,
|
||||
})
|
||||
|
||||
return consumeKuaishouEticket({
|
||||
baseUrl: context.baseUrl,
|
||||
@@ -557,25 +576,37 @@ export async function validateAdminCloudtentaclesSession(payload = /** @type {Ad
|
||||
|
||||
/** @param {AdminCloudtentaclesCatalogQueryInput} [payload] */
|
||||
export async function getAdminCloudtentaclesAsset(payload = /** @type {AdminCloudtentaclesCatalogQueryInput} */ ({})) {
|
||||
const context = resolveCloudtentaclesAdminContext(payload)
|
||||
const context = resolveCloudtentaclesAdminContext(payload, {
|
||||
savedSource: getCloudtentaclesSourceConfig(),
|
||||
persistedSession: getCloudtentaclesSessionState(),
|
||||
})
|
||||
return getCloudtentaclesAsset(context)
|
||||
}
|
||||
|
||||
/** @param {AdminCloudtentaclesCatalogQueryInput} [payload] */
|
||||
export async function getAdminCloudtentaclesCategories(payload = /** @type {AdminCloudtentaclesCatalogQueryInput} */ ({})) {
|
||||
const context = resolveCloudtentaclesAdminContext(payload)
|
||||
const context = resolveCloudtentaclesAdminContext(payload, {
|
||||
savedSource: getCloudtentaclesSourceConfig(),
|
||||
persistedSession: getCloudtentaclesSessionState(),
|
||||
})
|
||||
return getCloudtentaclesCategories(context)
|
||||
}
|
||||
|
||||
/** @param {AdminCloudtentaclesCatalogQueryInput} [payload] */
|
||||
export async function getAdminCloudtentaclesSkuList(payload = /** @type {AdminCloudtentaclesCatalogQueryInput} */ ({})) {
|
||||
const context = resolveCloudtentaclesAdminContext(payload)
|
||||
const context = resolveCloudtentaclesAdminContext(payload, {
|
||||
savedSource: getCloudtentaclesSourceConfig(),
|
||||
persistedSession: getCloudtentaclesSessionState(),
|
||||
})
|
||||
return listCloudtentaclesSku(context)
|
||||
}
|
||||
|
||||
/** @param {AdminCloudtentaclesSkuBuyInput} [payload] */
|
||||
export async function buyAdminCloudtentaclesSku(payload = /** @type {AdminCloudtentaclesSkuBuyInput} */ ({})) {
|
||||
const context = resolveCloudtentaclesAdminContext(payload)
|
||||
const context = resolveCloudtentaclesAdminContext(payload, {
|
||||
savedSource: getCloudtentaclesSourceConfig(),
|
||||
persistedSession: getCloudtentaclesSessionState(),
|
||||
})
|
||||
return buyCloudtentaclesSku({
|
||||
...context,
|
||||
id: payload.id,
|
||||
@@ -585,7 +616,10 @@ export async function buyAdminCloudtentaclesSku(payload = /** @type {AdminCloudt
|
||||
|
||||
/** @param {AdminCloudtentaclesSkuUseInput} [payload] */
|
||||
export async function useAdminCloudtentaclesSku(payload = /** @type {AdminCloudtentaclesSkuUseInput} */ ({})) {
|
||||
const context = resolveCloudtentaclesAdminContext(payload)
|
||||
const context = resolveCloudtentaclesAdminContext(payload, {
|
||||
savedSource: getCloudtentaclesSourceConfig(),
|
||||
persistedSession: getCloudtentaclesSessionState(),
|
||||
})
|
||||
return useCloudtentaclesSku({
|
||||
...context,
|
||||
id: payload.id,
|
||||
@@ -596,13 +630,19 @@ export async function useAdminCloudtentaclesSku(payload = /** @type {AdminCloudt
|
||||
|
||||
/** @param {AdminCloudtentaclesCatalogQueryInput} [payload] */
|
||||
export async function getAdminCloudtentaclesKnapsack(payload = /** @type {AdminCloudtentaclesCatalogQueryInput} */ ({})) {
|
||||
const context = resolveCloudtentaclesAdminContext(payload)
|
||||
const context = resolveCloudtentaclesAdminContext(payload, {
|
||||
savedSource: getCloudtentaclesSourceConfig(),
|
||||
persistedSession: getCloudtentaclesSessionState(),
|
||||
})
|
||||
return getCloudtentaclesKnapsack(context)
|
||||
}
|
||||
|
||||
/** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */
|
||||
export async function listAdminCloudtentaclesVirtualNumbers(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) {
|
||||
const context = resolveCloudtentaclesAdminContext(payload)
|
||||
const context = resolveCloudtentaclesAdminContext(payload, {
|
||||
savedSource: getCloudtentaclesSourceConfig(),
|
||||
persistedSession: getCloudtentaclesSessionState(),
|
||||
})
|
||||
return listCloudtentaclesVirtualNumbers({
|
||||
...context,
|
||||
key: payload.key,
|
||||
@@ -611,7 +651,10 @@ export async function listAdminCloudtentaclesVirtualNumbers(payload = /** @type
|
||||
|
||||
/** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */
|
||||
export async function appointAdminCloudtentaclesVirtualNumber(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) {
|
||||
const context = resolveCloudtentaclesAdminContext(payload)
|
||||
const context = resolveCloudtentaclesAdminContext(payload, {
|
||||
savedSource: getCloudtentaclesSourceConfig(),
|
||||
persistedSession: getCloudtentaclesSessionState(),
|
||||
})
|
||||
return appointCloudtentaclesVirtualNumber({
|
||||
...context,
|
||||
key: payload.key,
|
||||
@@ -620,7 +663,10 @@ export async function appointAdminCloudtentaclesVirtualNumber(payload = /** @typ
|
||||
|
||||
/** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */
|
||||
export async function generateAdminCloudtentaclesLoginCode(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) {
|
||||
const context = resolveCloudtentaclesAdminContext(payload)
|
||||
const context = resolveCloudtentaclesAdminContext(payload, {
|
||||
savedSource: getCloudtentaclesSourceConfig(),
|
||||
persistedSession: getCloudtentaclesSessionState(),
|
||||
})
|
||||
return generateCloudtentaclesLoginCode({
|
||||
...context,
|
||||
key: payload.key,
|
||||
@@ -630,7 +676,10 @@ export async function generateAdminCloudtentaclesLoginCode(payload = /** @type {
|
||||
|
||||
/** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */
|
||||
export async function fetchAdminCloudtentaclesVirtualNumberCode(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) {
|
||||
const context = resolveCloudtentaclesAdminContext(payload)
|
||||
const context = resolveCloudtentaclesAdminContext(payload, {
|
||||
savedSource: getCloudtentaclesSourceConfig(),
|
||||
persistedSession: getCloudtentaclesSessionState(),
|
||||
})
|
||||
return fetchCloudtentaclesVirtualNumberCode({
|
||||
...context,
|
||||
key: payload.key,
|
||||
@@ -640,7 +689,10 @@ export async function fetchAdminCloudtentaclesVirtualNumberCode(payload = /** @t
|
||||
|
||||
/** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */
|
||||
export async function verifyAdminCloudtentaclesLoginCode(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) {
|
||||
const context = resolveCloudtentaclesAdminContext(payload)
|
||||
const context = resolveCloudtentaclesAdminContext(payload, {
|
||||
savedSource: getCloudtentaclesSourceConfig(),
|
||||
persistedSession: getCloudtentaclesSessionState(),
|
||||
})
|
||||
return verifyCloudtentaclesLoginCode({
|
||||
...context,
|
||||
key: payload.key,
|
||||
@@ -651,7 +703,10 @@ export async function verifyAdminCloudtentaclesLoginCode(payload = /** @type {Ad
|
||||
|
||||
/** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */
|
||||
export async function getAdminCloudtentaclesBindUrl(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) {
|
||||
const context = resolveCloudtentaclesAdminContext(payload)
|
||||
const context = resolveCloudtentaclesAdminContext(payload, {
|
||||
savedSource: getCloudtentaclesSourceConfig(),
|
||||
persistedSession: getCloudtentaclesSessionState(),
|
||||
})
|
||||
return getCloudtentaclesBindUrl({
|
||||
...context,
|
||||
key: payload.key,
|
||||
@@ -661,7 +716,10 @@ export async function getAdminCloudtentaclesBindUrl(payload = /** @type {AdminCl
|
||||
|
||||
/** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */
|
||||
export async function backAdminCloudtentaclesVirtualNumber(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) {
|
||||
const context = resolveCloudtentaclesAdminContext(payload)
|
||||
const context = resolveCloudtentaclesAdminContext(payload, {
|
||||
savedSource: getCloudtentaclesSourceConfig(),
|
||||
persistedSession: getCloudtentaclesSessionState(),
|
||||
})
|
||||
return backCloudtentaclesVirtualNumber({
|
||||
...context,
|
||||
key: payload.key,
|
||||
@@ -671,7 +729,10 @@ export async function backAdminCloudtentaclesVirtualNumber(payload = /** @type {
|
||||
|
||||
/** @param {AdminCloudtentaclesFullFlowInput} [payload] */
|
||||
export async function runAdminCloudtentaclesFullFlow(payload = /** @type {AdminCloudtentaclesFullFlowInput} */ ({})) {
|
||||
const context = resolveCloudtentaclesAdminContext(payload)
|
||||
const context = resolveCloudtentaclesAdminContext(payload, {
|
||||
savedSource: getCloudtentaclesSourceConfig(),
|
||||
persistedSession: getCloudtentaclesSessionState(),
|
||||
})
|
||||
return runCloudtentaclesFullDebugFlow({
|
||||
...context,
|
||||
skuId: payload.skuId,
|
||||
@@ -1125,55 +1186,3 @@ function mapAdminObservedProductItem(item, bindings = []) {
|
||||
function findMatchingObservedBinding(bindings, observed) {
|
||||
return (Array.isArray(bindings) ? bindings : []).find((binding) => matchesObservedProduct(binding, observed)) || null
|
||||
}
|
||||
|
||||
function resolveKuaishouEticketAdminContext(payload = {}) {
|
||||
const savedSource = getKuaishouEticketSourceConfig()
|
||||
const requestedShopId = String(payload.shopId || '').trim()
|
||||
const configuredShop = requestedShopId
|
||||
? findKuaishouEticketShopConfig(requestedShopId, savedSource)
|
||||
: getFirstAvailableKuaishouEticketShop(savedSource)
|
||||
|
||||
return {
|
||||
baseUrl: pickFirstNonEmpty([payload.baseUrl, savedSource.baseUrl, 'https://s.kwaixiaodian.com']),
|
||||
cookie: pickFirstNonEmpty([payload.cookie, configuredShop?.cookie]),
|
||||
shopId: pickFirstNonEmpty([requestedShopId, configuredShop?.shopId]),
|
||||
shop: configuredShop,
|
||||
}
|
||||
}
|
||||
|
||||
function resolveCloudtentaclesAdminContext(payload = {}) {
|
||||
const savedSource = getCloudtentaclesSourceConfig()
|
||||
const persistedSession = getCloudtentaclesSessionState()
|
||||
|
||||
return {
|
||||
baseUrl: pickFirstNonEmpty([payload.baseUrl, savedSource.baseUrl, 'https://123.207.217.176']),
|
||||
token: pickFirstNonEmpty([payload.token, persistedSession.token]),
|
||||
deviceId: pickFirstNonEmpty([payload.deviceId, savedSource.deviceId, persistedSession.deviceId, '-']),
|
||||
deviceType: payload.deviceType ?? savedSource.deviceType ?? persistedSession.deviceType,
|
||||
}
|
||||
}
|
||||
|
||||
function hasCloudtentaclesCredentialContextChanged(current, next) {
|
||||
return (
|
||||
String(current.baseUrl || '').trim() !== String(next.baseUrl || '').trim()
|
||||
|| String(current.username || '').trim() !== String(next.username || '').trim()
|
||||
|| String(current.phone || '').trim() !== String(next.phone || '').trim()
|
||||
|| String(current.deviceId || '').trim() !== String(next.deviceId || '').trim()
|
||||
|| Number(current.deviceType || 0) !== Number(next.deviceType || 0)
|
||||
)
|
||||
}
|
||||
|
||||
function isPlainObject(value) {
|
||||
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
|
||||
}
|
||||
|
||||
function pickFirstNonEmpty(values) {
|
||||
for (const value of values) {
|
||||
const normalized = String(value || '').trim()
|
||||
if (normalized) {
|
||||
return normalized
|
||||
}
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user