统一前后端代码格式化配置
This commit is contained in:
@@ -17,7 +17,9 @@ type BarkSendInput = {
|
||||
}
|
||||
|
||||
export async function sendBarkNotification(input: BarkSendInput) {
|
||||
const serverUrl = String(input.serverUrl || 'https://api.day.app').trim().replace(/\/+$/, '')
|
||||
const serverUrl = String(input.serverUrl || 'https://api.day.app')
|
||||
.trim()
|
||||
.replace(/\/+$/, '')
|
||||
const deviceKey = String(input.recipient?.deviceKey || '').trim()
|
||||
const title = String(input.title || '').trim()
|
||||
const body = String(input.body || '').trim()
|
||||
@@ -88,11 +90,7 @@ export async function sendBarkNotification(input: BarkSendInput) {
|
||||
}
|
||||
|
||||
function buildBarkEndpoint(serverUrl: string, deviceKey: string, title: string, body: string) {
|
||||
const segments = [
|
||||
serverUrl,
|
||||
encodeURIComponent(deviceKey),
|
||||
encodeURIComponent(title),
|
||||
]
|
||||
const segments = [serverUrl, encodeURIComponent(deviceKey), encodeURIComponent(title)]
|
||||
|
||||
if (body) {
|
||||
segments.push(encodeURIComponent(body))
|
||||
@@ -124,7 +122,10 @@ function isBarkFailure(parsed: unknown) {
|
||||
|
||||
function resolveBarkErrorMessage(parsed: unknown, responseText: string, status: number) {
|
||||
if (isPlainObject(parsed)) {
|
||||
return String(parsed.message || parsed.msg || parsed.error || '').trim() || `Bark 通知发送失败,HTTP ${status}`
|
||||
return (
|
||||
String(parsed.message || parsed.msg || parsed.error || '').trim() ||
|
||||
`Bark 通知发送失败,HTTP ${status}`
|
||||
)
|
||||
}
|
||||
|
||||
return String(responseText || '').trim() || `Bark 通知发送失败,HTTP ${status}`
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
import {
|
||||
createDefaultNotificationConfig,
|
||||
normalizeNotificationConfig,
|
||||
} from './config-service.js'
|
||||
import { createDefaultNotificationConfig, normalizeNotificationConfig } from './config-service.js'
|
||||
|
||||
test('normalizeNotificationConfig normalizes bark and wpush recipients', () => {
|
||||
assert.deepEqual(
|
||||
|
||||
@@ -3,10 +3,7 @@ import type { JsonObject } from '../../types/json.js'
|
||||
|
||||
import { APP_CONFIG_KEYS } from '../../config/app-config-keys.js'
|
||||
import { PROJECT_ROOT } from '../../config/runtime.js'
|
||||
import {
|
||||
readAppConfigEntry,
|
||||
saveAppConfigEntry,
|
||||
} from '../config/app-config-store.js'
|
||||
import { readAppConfigEntry, saveAppConfigEntry } from '../config/app-config-store.js'
|
||||
|
||||
const NOTIFICATION_CONFIG_FILE_PATH = path.join(PROJECT_ROOT, 'data', 'notification-config.json')
|
||||
const DEFAULT_BARK_SERVER_URL = 'https://api.day.app'
|
||||
@@ -46,8 +43,9 @@ export function listEnabledBarkRecipients(config: JsonObject = getNotificationCo
|
||||
return []
|
||||
}
|
||||
|
||||
return (Array.isArray(config.channels?.bark?.recipients) ? config.channels.bark.recipients : [])
|
||||
.filter((item: JsonObject) => item.enabled !== false && String(item.deviceKey || '').trim())
|
||||
return (
|
||||
Array.isArray(config.channels?.bark?.recipients) ? config.channels.bark.recipients : []
|
||||
).filter((item: JsonObject) => item.enabled !== false && String(item.deviceKey || '').trim())
|
||||
}
|
||||
|
||||
export function listEnabledWpushRecipients(config: JsonObject = getNotificationConfig()) {
|
||||
@@ -55,8 +53,11 @@ export function listEnabledWpushRecipients(config: JsonObject = getNotificationC
|
||||
return []
|
||||
}
|
||||
|
||||
return (Array.isArray(config.channels?.wpush?.recipients) ? config.channels.wpush.recipients : [])
|
||||
.filter((item: JsonObject) => item.enabled !== false && String(item.apiKey || item.apikey || '').trim())
|
||||
return (
|
||||
Array.isArray(config.channels?.wpush?.recipients) ? config.channels.wpush.recipients : []
|
||||
).filter(
|
||||
(item: JsonObject) => item.enabled !== false && String(item.apiKey || item.apikey || '').trim(),
|
||||
)
|
||||
}
|
||||
|
||||
export function normalizeNotificationConfig(rawValue: unknown) {
|
||||
|
||||
@@ -107,11 +107,9 @@ export function notifyKuaishouCloudAssetNotEnough({
|
||||
`商品:${String(skuName || binding.skuName || flowRecord.internalSkuName || '').trim() || '-'}`,
|
||||
].join('\n'),
|
||||
category: 'kuaishou_cloud_asset_not_enough',
|
||||
cooldownKey: [
|
||||
'kuaishou_cloud_asset_not_enough',
|
||||
taskRecord.id || '',
|
||||
binding.skuId || '',
|
||||
].join(':'),
|
||||
cooldownKey: ['kuaishou_cloud_asset_not_enough', taskRecord.id || '', binding.skuId || ''].join(
|
||||
':',
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -135,9 +133,16 @@ export function notifyCloudtentaclesAuthExpired({
|
||||
`接口:${String(pathname || '').trim() || '-'}`,
|
||||
`错误码:${String(errorCode || '').trim() || '-'}`,
|
||||
`原因:${String(message || '').trim() || '登录态已失效,请到后台重新登录'}`,
|
||||
].filter(Boolean).join('\n'),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n'),
|
||||
category: 'cloudtentacles_auth_expired',
|
||||
cooldownKey: ['cloudtentacles_auth_expired', normalizedSourceKey || 'default', pathname, errorCode].join(':'),
|
||||
cooldownKey: [
|
||||
'cloudtentacles_auth_expired',
|
||||
normalizedSourceKey || 'default',
|
||||
pathname,
|
||||
errorCode,
|
||||
].join(':'),
|
||||
cooldownMs: Number(cooldownSeconds || 600) * 1000,
|
||||
})
|
||||
}
|
||||
@@ -161,9 +166,13 @@ export function notifyCloudtentaclesAssetLow({
|
||||
`当前余额:${Number(asset || 0)}`,
|
||||
`提醒阈值:${Number(threshold || 0)}`,
|
||||
'请及时补充 cloudtentacles 余额,避免自动履约失败。',
|
||||
].filter(Boolean).join('\n'),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n'),
|
||||
category: 'cloudtentacles_asset_low',
|
||||
cooldownKey: ['cloudtentacles_asset_low', normalizedSourceKey || 'default', threshold].join(':'),
|
||||
cooldownKey: ['cloudtentacles_asset_low', normalizedSourceKey || 'default', threshold].join(
|
||||
':',
|
||||
),
|
||||
cooldownMs: Number(cooldownSeconds || 1800) * 1000,
|
||||
})
|
||||
}
|
||||
@@ -186,7 +195,11 @@ export function notifyOpen91PendingConfig({
|
||||
`原因:${String(reason || '').trim() || '未命中履约配置'}`,
|
||||
].join('\n'),
|
||||
category: 'open91_pending_config',
|
||||
cooldownKey: ['open91_pending_config', orderNo || orderRecord.platform_order_id || '', productNo].join(':'),
|
||||
cooldownKey: [
|
||||
'open91_pending_config',
|
||||
orderNo || orderRecord.platform_order_id || '',
|
||||
productNo,
|
||||
].join(':'),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -258,9 +271,7 @@ function formatTaskLine(task: unknown) {
|
||||
}
|
||||
|
||||
function toRecord(value: unknown): JsonObject {
|
||||
return value && typeof value === 'object' && !Array.isArray(value)
|
||||
? value as JsonObject
|
||||
: {}
|
||||
return value && typeof value === 'object' && !Array.isArray(value) ? (value as JsonObject) : {}
|
||||
}
|
||||
|
||||
function isNotificationCoolingDown(cooldownKey: unknown, cooldownMs = DEFAULT_COOLDOWN_MS) {
|
||||
|
||||
@@ -37,59 +37,79 @@ export async function sendInternalNotification(input: NotificationInput = {}) {
|
||||
}
|
||||
|
||||
const results = [
|
||||
...(await Promise.all(barkRecipients.map(async (recipient: JsonObject) => {
|
||||
try {
|
||||
const result = await sendBarkNotification({
|
||||
recipient,
|
||||
title,
|
||||
body,
|
||||
group: `订单系统/${category}`,
|
||||
...(bark.serverUrl !== undefined ? { serverUrl: bark.serverUrl } : {}),
|
||||
...(input.url !== undefined ? { url: input.url } : {}),
|
||||
})
|
||||
return mapNotificationResult('bark', recipient, recipient.deviceKey, true, '', result.status, result.response)
|
||||
} catch (error) {
|
||||
logWarn('[notification/bark]', 'Bark 内部通知发送失败', {
|
||||
recipientId: recipient.id,
|
||||
recipientName: recipient.name,
|
||||
error,
|
||||
})
|
||||
return mapNotificationResult(
|
||||
'bark',
|
||||
recipient,
|
||||
recipient.deviceKey,
|
||||
false,
|
||||
error instanceof Error ? error.message : 'Bark 内部通知发送失败',
|
||||
isPlainObject(error) ? Number(error.statusCode || 0) || 0 : 0,
|
||||
isPlainObject(error) ? error.context || null : null,
|
||||
)
|
||||
}
|
||||
}))),
|
||||
...(await Promise.all(wpushRecipients.map(async (recipient: JsonObject) => {
|
||||
try {
|
||||
const result = await sendWpushNotification({
|
||||
recipient,
|
||||
title,
|
||||
body,
|
||||
})
|
||||
return mapNotificationResult('wpush', recipient, recipient.apiKey, true, '', result.status, result.response)
|
||||
} catch (error) {
|
||||
logWarn('[notification/wpush]', 'WPush 内部通知发送失败', {
|
||||
recipientId: recipient.id,
|
||||
recipientName: recipient.name,
|
||||
error,
|
||||
})
|
||||
return mapNotificationResult(
|
||||
'wpush',
|
||||
recipient,
|
||||
recipient.apiKey,
|
||||
false,
|
||||
error instanceof Error ? error.message : 'WPush 内部通知发送失败',
|
||||
isPlainObject(error) ? Number(error.statusCode || 0) || 0 : 0,
|
||||
isPlainObject(error) ? error.context || null : null,
|
||||
)
|
||||
}
|
||||
}))),
|
||||
...(await Promise.all(
|
||||
barkRecipients.map(async (recipient: JsonObject) => {
|
||||
try {
|
||||
const result = await sendBarkNotification({
|
||||
recipient,
|
||||
title,
|
||||
body,
|
||||
group: `订单系统/${category}`,
|
||||
...(bark.serverUrl !== undefined ? { serverUrl: bark.serverUrl } : {}),
|
||||
...(input.url !== undefined ? { url: input.url } : {}),
|
||||
})
|
||||
return mapNotificationResult(
|
||||
'bark',
|
||||
recipient,
|
||||
recipient.deviceKey,
|
||||
true,
|
||||
'',
|
||||
result.status,
|
||||
result.response,
|
||||
)
|
||||
} catch (error) {
|
||||
logWarn('[notification/bark]', 'Bark 内部通知发送失败', {
|
||||
recipientId: recipient.id,
|
||||
recipientName: recipient.name,
|
||||
error,
|
||||
})
|
||||
return mapNotificationResult(
|
||||
'bark',
|
||||
recipient,
|
||||
recipient.deviceKey,
|
||||
false,
|
||||
error instanceof Error ? error.message : 'Bark 内部通知发送失败',
|
||||
isPlainObject(error) ? Number(error.statusCode || 0) || 0 : 0,
|
||||
isPlainObject(error) ? error.context || null : null,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)),
|
||||
...(await Promise.all(
|
||||
wpushRecipients.map(async (recipient: JsonObject) => {
|
||||
try {
|
||||
const result = await sendWpushNotification({
|
||||
recipient,
|
||||
title,
|
||||
body,
|
||||
})
|
||||
return mapNotificationResult(
|
||||
'wpush',
|
||||
recipient,
|
||||
recipient.apiKey,
|
||||
true,
|
||||
'',
|
||||
result.status,
|
||||
result.response,
|
||||
)
|
||||
} catch (error) {
|
||||
logWarn('[notification/wpush]', 'WPush 内部通知发送失败', {
|
||||
recipientId: recipient.id,
|
||||
recipientName: recipient.name,
|
||||
error,
|
||||
})
|
||||
return mapNotificationResult(
|
||||
'wpush',
|
||||
recipient,
|
||||
recipient.apiKey,
|
||||
false,
|
||||
error instanceof Error ? error.message : 'WPush 内部通知发送失败',
|
||||
isPlainObject(error) ? Number(error.statusCode || 0) || 0 : 0,
|
||||
isPlainObject(error) ? error.context || null : null,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)),
|
||||
]
|
||||
|
||||
return {
|
||||
|
||||
@@ -66,11 +66,12 @@ test('sendWpushNotification sends POST json payload', async () => {
|
||||
|
||||
test('sendWpushNotification rejects missing api key', async () => {
|
||||
await assert.rejects(
|
||||
() => sendWpushNotification({
|
||||
recipient: {},
|
||||
title: '测试标题',
|
||||
body: '测试内容',
|
||||
}),
|
||||
() =>
|
||||
sendWpushNotification({
|
||||
recipient: {},
|
||||
title: '测试标题',
|
||||
body: '测试内容',
|
||||
}),
|
||||
(error: any) => {
|
||||
assert.equal(error?.errorCode, 'wpush_api_key_required')
|
||||
return true
|
||||
|
||||
@@ -97,7 +97,12 @@ export async function sendWpushNotification(input: WpushSendInput) {
|
||||
|
||||
async function requestViaNodeHttp(
|
||||
url: URL,
|
||||
{ method, headers, body, signal }: {
|
||||
{
|
||||
method,
|
||||
headers,
|
||||
body,
|
||||
signal,
|
||||
}: {
|
||||
method: string
|
||||
headers: Record<string, string>
|
||||
body?: string
|
||||
@@ -107,25 +112,29 @@ async function requestViaNodeHttp(
|
||||
const transport = url.protocol === 'https:' ? https : http
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = transport.request(url, {
|
||||
method,
|
||||
headers,
|
||||
rejectUnauthorized: false,
|
||||
}, (response) => {
|
||||
const chunks: Buffer[] = []
|
||||
const request = transport.request(
|
||||
url,
|
||||
{
|
||||
method,
|
||||
headers,
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
(response) => {
|
||||
const chunks: Buffer[] = []
|
||||
|
||||
response.on('data', (chunk) => {
|
||||
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))
|
||||
})
|
||||
|
||||
response.on('end', () => {
|
||||
resolve({
|
||||
ok: Number(response.statusCode || 0) >= 200 && Number(response.statusCode || 0) < 300,
|
||||
status: Number(response.statusCode || 0),
|
||||
bodyText: Buffer.concat(chunks).toString('utf8'),
|
||||
response.on('data', (chunk) => {
|
||||
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
response.on('end', () => {
|
||||
resolve({
|
||||
ok: Number(response.statusCode || 0) >= 200 && Number(response.statusCode || 0) < 300,
|
||||
status: Number(response.statusCode || 0),
|
||||
bodyText: Buffer.concat(chunks).toString('utf8'),
|
||||
})
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
request.on('error', reject)
|
||||
|
||||
@@ -135,11 +144,15 @@ async function requestViaNodeHttp(
|
||||
error.name = 'AbortError'
|
||||
request.destroy(error)
|
||||
} else {
|
||||
signal.addEventListener('abort', () => {
|
||||
const error = new Error('Request aborted')
|
||||
error.name = 'AbortError'
|
||||
request.destroy(error)
|
||||
}, { once: true })
|
||||
signal.addEventListener(
|
||||
'abort',
|
||||
() => {
|
||||
const error = new Error('Request aborted')
|
||||
error.name = 'AbortError'
|
||||
request.destroy(error)
|
||||
},
|
||||
{ once: true },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +192,10 @@ function isWpushFailure(parsed: unknown) {
|
||||
|
||||
function resolveWpushErrorMessage(parsed: unknown, responseText: string, status: number) {
|
||||
if (isPlainObject(parsed)) {
|
||||
return String(parsed.message || parsed.msg || parsed.error || '').trim() || `WPush 通知发送失败,HTTP ${status}`
|
||||
return (
|
||||
String(parsed.message || parsed.msg || parsed.error || '').trim() ||
|
||||
`WPush 通知发送失败,HTTP ${status}`
|
||||
)
|
||||
}
|
||||
|
||||
return String(responseText || '').trim() || `WPush 通知发送失败,HTTP ${status}`
|
||||
|
||||
Reference in New Issue
Block a user