import { createRequestError } from './request-client.js' import { normalizeOptionalNumericId, normalizeResponseText } from './response-normalizers.js' import { normalizeBusinessFileAccess } from './business-file-contract.js' const promotionPlacements = new Set(['home_banner', 'home_bottom', 'message_bottom', 'profile_bottom']) export const normalizePromotionPlacement = (value) => { if (!promotionPlacements.has(value)) throw new TypeError('推广位无效') return value } export const normalizeAppPromotions = (value, expectedPlacement) => { if (!Array.isArray(value)) throw createRequestError('应用推广响应不是列表', 'PROMOTION_RESPONSE_INVALID') const promotions = value.map((item) => { if (!item || typeof item !== 'object' || Array.isArray(item)) { throw createRequestError('应用推广响应包含无效条目', 'PROMOTION_RESPONSE_INVALID') } const id = normalizeOptionalNumericId(item.promotionId, '推广标识', 'PROMOTION_RESPONSE_INVALID') const title = normalizeResponseText(item.promotionTitle, 'promotionTitle') if (!id || !title) { throw createRequestError('应用推广响应缺少稳定字段', 'PROMOTION_RESPONSE_INVALID') } const platform = normalizeResponseText(item.platform, 'platform') const placement = normalizeResponseText(item.placement, 'placement') if ((platform && platform !== 'app' && platform !== 'all') || placement !== expectedPlacement) { throw createRequestError('应用推广响应位置与请求不匹配', 'PROMOTION_RESPONSE_INVALID') } const targetUrl = normalizeResponseText(item.targetUrl, 'targetUrl') if ( targetUrl && !(/^\/(?!\/)[^\s]*$/.test(targetUrl) || /^https:\/\/[^\s/?#]+(?:[/?#][^\s]*)?$/.test(targetUrl)) ) { throw createRequestError('应用推广跳转地址无效', 'PROMOTION_RESPONSE_INVALID') } return { id, title, description: normalizeResponseText(item.promotionDesc, 'promotionDesc'), coverFile: normalizeBusinessFileAccess(item.coverFile, '推广封面', 'PROMOTION_RESPONSE_INVALID'), targetUrl, placement } }) if (new Set(promotions.map((item) => item.id)).size !== promotions.length) { throw createRequestError('应用推广响应包含重复标识', 'PROMOTION_RESPONSE_INVALID') } return promotions } const normalizeHelpArticleText = (value, field) => { if (typeof value !== 'string' || !value.trim()) { throw createRequestError(`帮助文章缺少 ${field}`, 'HELP_ARTICLE_RESPONSE_INVALID') } return value.trim() } const normalizeOptionalHelpArticleText = (value, field) => { if (value === undefined || value === null) return '' return normalizeHelpArticleText(value, field) } const helpArticleCategories = new Set(['common', 'member', 'lineage']) export const normalizeHelpArticles = (value) => { if (!Array.isArray(value)) throw createRequestError('帮助文章响应不是列表', 'HELP_ARTICLE_RESPONSE_INVALID') const articles = value.map((item) => { if (!item || typeof item !== 'object' || Array.isArray(item)) { throw createRequestError('帮助文章响应包含无效条目', 'HELP_ARTICLE_RESPONSE_INVALID') } const id = normalizeOptionalNumericId(item.helpId, '帮助文章标识', 'HELP_ARTICLE_RESPONSE_INVALID') const category = normalizeHelpArticleText(item.helpCategory, 'helpCategory') if (!id || !helpArticleCategories.has(category)) { throw createRequestError('帮助文章标识或分类无效', 'HELP_ARTICLE_RESPONSE_INVALID') } return { key: `help-${id}`, category, categoryLabel: normalizeOptionalHelpArticleText(item.helpCategoryLabel, 'helpCategoryLabel') || category, title: normalizeHelpArticleText(item.helpTitle, 'helpTitle'), content: normalizeHelpArticleText(item.helpContent, 'helpContent') } }) if (new Set(articles.map((article) => article.key)).size !== articles.length) { throw createRequestError('帮助文章响应包含重复标识', 'HELP_ARTICLE_RESPONSE_INVALID') } return articles } export const COMPLIANCE_DOCUMENT_KEY = Object.freeze({ USER_AGREEMENT: 'user_agreement', PRIVACY_POLICY: 'privacy_policy' }) const complianceDocumentKeys = new Set(Object.values(COMPLIANCE_DOCUMENT_KEY)) export const normalizeComplianceDocumentKey = (value) => { if (typeof value !== 'string' || !complianceDocumentKeys.has(value)) { throw new TypeError('合规文档类型无效') } return value } export const normalizeComplianceDocument = (value, expectedKey) => { if (!value || typeof value !== 'object' || Array.isArray(value)) { throw createRequestError('协议正文响应无效', 'COMPLIANCE_DOCUMENT_RESPONSE_INVALID') } const key = normalizeResponseText(value.documentKey, 'documentKey') const title = normalizeResponseText(value.documentTitle, 'documentTitle') const versionNo = normalizeResponseText(value.versionNo, 'versionNo') const content = normalizeResponseText(value.documentContent, 'documentContent') if (key !== expectedKey || !title || !versionNo || !content) { throw createRequestError('协议正文响应缺少稳定字段', 'COMPLIANCE_DOCUMENT_RESPONSE_INVALID') } return { key, title, versionNo, content, effectiveAt: normalizeResponseText(value.effectiveAt, 'effectiveAt'), publishedAt: normalizeResponseText(value.publishedAt, 'publishedAt') } }