refactors

This commit is contained in:
Takanashi Hoshino
2026-09-13 12:04:37 +08:00
parent c478367c6c
commit ddfd680301
15 changed files with 196 additions and 651 deletions
-93
View File
@@ -1,93 +0,0 @@
/**
* 凭证刷新层:把“如何获取”与“状态存放”解耦。
*
* - util/runtimeState.js 负责进程内状态的存取(纯内存、并发去重);
* - 本模块负责实际的网络获取,并在成功后写入 runtimeState。
*
* 两者分离后,runtimeState 可被纯单测覆盖(注入 mock 刷新函数),
* 而本模块是唯一允许 util -> module 依赖的地方。
*/
const runtimeState = require('./runtimeState')
const registerXeapiKey = require('../module/register_xeapikey')
const { cookieToJson } = require('./index')
// 惰性加载 request,规避 util/request -> credentials -> request 的启动期循环依赖。
// runtimeState 与本模块均不在 require 阶段触发网络或文件 I/O。
let requestModule = null
const loadRequest = () => {
if (!requestModule) {
requestModule = require('./request')
}
return requestModule
}
/**
* 通过网络注册匿名账号,获取并写入 anonymous_token。
* @returns {Promise<string>} 最新 token
*/
const refreshAnonymousToken = async () => {
const { register_anonimous } = require('../module/register_anonimous')
const res = await register_anonimous({}, loadRequest())
const cookie = res && res.body ? res.body.cookie : ''
if (!cookie) {
throw new Error('anonymous token response missing cookie')
}
const cookieObj = cookieToJson(cookie)
const token = cookieObj.MUSIC_A
if (!token) {
throw new Error('anonymous token response missing MUSIC_A')
}
runtimeState.setAnonymousToken(token)
return token
}
/**
* 获取 xeapi public key(纯获取,不写状态——由 runtimeState 统一合并/写入)。
*
* 兼容 serverless:内存缺失时由调用方触发;若服务端未下发 version,
* 沿用旧状态里的 version,避免无谓的重取判定。
*
* @returns {Promise<Record<string, any>>}
*/
const fetchXeapiPublicKey = async () => {
const current = runtimeState.getXeapiPublicKey() || {}
const result = await registerXeapiKey(
{
deviceId: global.deviceId,
currentKeyVersion: current.version || '',
},
null,
)
const publicKey = result && result.body ? result.body : {}
if (!publicKey.sk && !current.sk) {
throw new Error('xeapi public key response missing sk')
}
if (!publicKey.version && current.version) {
publicKey.version = current.version
}
return publicKey
}
/**
* 确保 anonymous_token 可用(缺失时刷新)。
* @returns {Promise<string>}
*/
const ensureAnonymousToken = () =>
runtimeState.ensureAnonymousToken(refreshAnonymousToken)
/**
* 确保 xeapi public key 可用。
*
* @param {{ force?: boolean }} [options]
* @returns {Promise<Record<string, any> | null>}
*/
const ensureXeapiPublicKey = (options = {}) =>
runtimeState.ensureXeapiPublicKey(fetchXeapiPublicKey, options)
module.exports = {
refreshAnonymousToken,
fetchXeapiPublicKey,
ensureAnonymousToken,
ensureXeapiPublicKey,
}
+42 -22
View File
@@ -148,7 +148,7 @@ const processCookieObject = (cookie, crypto) => {
if (!processedCookie.MUSIC_U) {
processedCookie.MUSIC_A =
processedCookie.MUSIC_A || runtimeState.getAnonymousToken()
processedCookie.MUSIC_A || runtimeState.anonymousToken
}
return processedCookie
@@ -175,6 +175,33 @@ const generateRequestId = () => {
.padStart(4, '0')}`
}
// ---- 运行时凭证的强制刷新 ----
// 匿名令牌与 xeapi 公钥存于 util/runtimeState(纯内存、零文件 I/O),
// 由对应的 register_* 模块在成功获取后写入,此处只负责编排调用。
// 刷新自身的请求带 skipTokenEnsure 标记:跳过下方匿名令牌的按需引导,
// 避免“刷新任务等待自己”的死锁(注册匿名账号本就不依赖旧令牌)
const refreshTokenRequest = (uri, data, options = {}) =>
createRequest(uri, data, { ...options, skipTokenEnsure: true })
// 强制刷新匿名令牌(成功后由 register_anonimous 写入 runtimeState)
const refreshAnonymousToken = async () => {
const registerAnonimous = require('../module/register_anonimous')
await registerAnonimous({}, refreshTokenRequest)
}
// 强制刷新 xeapi 公钥(register_xeapikey 直接走 axios,无重入问题;
// 成功后由其写入 runtimeState)
const refreshXeapiPublicKey = async () => {
const registerXeapikey = require('../module/register_xeapikey')
await registerXeapikey(
{
currentKeyVersion: (runtimeState.xeapiPublicKey || {}).version || '',
},
null,
)
}
const createRequest = async (uri, data, options) => {
let token = ''
switch (options.checkToken) {
@@ -188,37 +215,27 @@ const createRequest = async (uri, data, options) => {
break
}
// 懒加载匿名 token:仅当内存中缺失时刷新一次,失败不阻塞本次请求。
// 兼容 serverless 冷启动——不假设启动阶段已刷新过。
// 刷新类请求自身跳过(否则会等待自己造成死锁)。
if (
!runtimeState.getAnonymousToken() &&
!runtimeState.isAnonymousTokenRefreshing()
) {
// 匿名令牌缺失时按需引导:并发请求共享同一次刷新任务;
// 刷新自身的请求(skipTokenEnsure)跳过,避免任务等待自己造成死锁
if (!runtimeState.anonymousToken && !options.skipTokenEnsure) {
try {
const { ensureAnonymousToken } = require('./credentials')
await ensureAnonymousToken()
await runtimeState.ensure('anonymousToken', refreshAnonymousToken)
} catch (error) {
console.log('[ERR]', error)
}
}
// 懒加载 xeapi public key:仅在本次请求确实使用 xeapi 加密、且内存缺失时刷新。
// 必须在进入同步 executor 前完成,因为加密过程需要同步拿到本状态。
const resolvedCrypto =
options.crypto === '' || options.crypto === undefined
// 本请求走 xeapi 加密且公钥缺失时按需获取
// (须在进入下方同步 executor 前完成,加密过程需要同步拿到公钥)
const pendingCrypto =
options.crypto === undefined || options.crypto === ''
? APP_CONF.encrypt
? 'eapi'
: 'api'
: options.crypto
if (
resolvedCrypto === 'xeapi' &&
!runtimeState.getXeapiPublicKey() &&
!runtimeState.isXeapiPublicKeyRefreshing()
) {
if (pendingCrypto === 'xeapi' && !runtimeState.xeapiPublicKey) {
try {
const { ensureXeapiPublicKey } = require('./credentials')
await ensureXeapiPublicKey()
await runtimeState.ensure('xeapiPublicKey', refreshXeapiPublicKey)
} catch (error) {
console.log('[ERR]', error)
}
@@ -288,7 +305,7 @@ const createRequest = async (uri, data, options) => {
break
case 'xeapi':
const xeapiPublicKey = runtimeState.getXeapiPublicKey()
const xeapiPublicKey = runtimeState.xeapiPublicKey
if (!xeapiPublicKey) {
throw new Error('xeapi public key is missing')
}
@@ -531,3 +548,6 @@ const createRequest = async (uri, data, options) => {
}
module.exports = createRequest
// 供 generateConfig 等复用的强制刷新入口(成功后写入 runtimeState)
module.exports.refreshAnonymousToken = refreshAnonymousToken
module.exports.refreshXeapiPublicKey = refreshXeapiPublicKey
+45 -250
View File
@@ -1,261 +1,56 @@
/**
* 统一运行时状态容器。
*
* 历史上 anonymous_token / xeapi_public_key 以“文件当 IPC”的方式存放在
* os.tmpdir()(/tmp)。这在可移植性上存在两个问题:
* 1. 并非所有平台都提供可写的 /tmp(如 Android / iOS / 嵌入式 / serverless);
* 2. main.js / request.js 会在 require 阶段就执行文件 I/O,只读文件系统上直接
* 导致模块加载失败。
*
* 但这些值本质上只是进程内的动态配置,读写双方都在同一个进程里,完全没有
* 落盘的必要。本模块把它们收敛到统一的内存状态:
* - 默认纯内存,get/set 绝不做任何文件 I/O(可在只读 FS / serverless 安全使用);
* - 可选地通过 loadSeed() 从环境变量或旧 /tmp 文件读取“种子”,仅用于平滑迁移;
* - 支持懒加载与按需刷新,适配 serverless 冷启动(不假设启动时已刷新过)。
*/
// 统一的运行时动态状态存储(仅内存,不落盘)
//
// 存放需要在进程内共享、且会在运行期间变化的动态配置,
// 目前包括匿名令牌(MUSIC_A)与 xeapi 公钥。
//
// 设计约定:
// - 只存在于内存中,不做任何文件 I/O —— 在没有 /tmp 或临时目录
// 不可写的平台(Android / iOS / 嵌入式 / serverless)上同样可用
// - 写入方(module/register_anonimous.js、module/register_xeapikey.js)
// 在成功获取新值后立即写入,读取方(util/request.js)每次请求实时读取,
// 进程内即时生效,无需重启
// - 凭证缺失时可通过 ensure() 按需刷新,并发调用共享同一次刷新任务
// - 以后新增的动态配置请放在这里,不要再往 os.tmpdir() 写文件
const fs = require('fs')
const path = require('path')
const os = require('os')
const runtimeState = {
/** 匿名令牌(MUSIC_A cookie),由 module/register_anonimous.js 成功后写入 */
anonymousToken: '',
/** @type {string} */
let anonymousToken = ''
/** @type {Record<string, any> | null} */
let xeapiPublicKey = null
/** 进行中的刷新 Promise,用于并发去重 */
let anonymousTokenTask = null
let xeapiPublicKeyTask = null
/**
* 防重入标记:刷新凭证本身也要走 util/request 发 HTTP 请求,
* 若不跳过 request 层的“缺失即刷新”逻辑,会等待自己造成死锁。
*/
let anonymousTokenRefreshing = false
let xeapiPublicKeyRefreshing = false
/** 旧版本遗留的磁盘路径,仅作为可选迁移种子读取,永不写入 */
const legacyTmpPath = (() => {
try {
return os.tmpdir()
} catch (_) {
return ''
}
})()
const safeReadFile = (filePath) => {
if (!filePath) return null
try {
return fs.readFileSync(filePath, 'utf-8')
} catch (_) {
// 文件不存在 / 不可读 / 只读文件系统:静默降级
return null
}
/**
* xeapi 公钥(含 sk / version / deviceId 等字段),
* 由 module/register_xeapikey.js 成功后写入
*/
xeapiPublicKey: null,
}
/**
* 读取 anonymous_token 的可选种子(环境变量优先,其次旧 /tmp 文件)。
* 仅在显式调用时读取,绝不在 require 阶段触发。
* @returns {string}
*/
const readAnonymousTokenSeed = () => {
const fromEnv = process.env.NETEASE_ANONYMOUS_TOKEN
if (fromEnv) return fromEnv.trim()
const fromFile = safeReadFile(
legacyTmpPath ? path.resolve(legacyTmpPath, 'anonymous_token') : '',
)
return fromFile ? fromFile.trim() : ''
}
/** 进行中的按需刷新任务:并发调用共享同一次刷新,避免重复请求 */
const refreshTasks = new Map()
/**
* 读取 xeapi_public_key 的可选种子(环境变量优先,其次旧 /tmp 文件)。
* @returns {Record<string, any> | null}
* 确保某项动态状态可用:已有值时直接返回;缺失时执行 refresh 刷新
* (refresh 成功后需自行把新值写入 runtimeState[key])。
* 并发调用会共享同一个进行中的刷新任务。
*
* @param {string} key runtimeState 上的字段名
* @param {() => Promise} refresh 刷新函数
* @returns {Promise<*>} 该状态当前的值(刷新失败时抛出错误)
*/
const readXeapiPublicKeySeed = () => {
const fromEnv = process.env.NETEASE_XEAPI_PUBLIC_KEY
const raw =
fromEnv ||
safeReadFile(
legacyTmpPath ? path.resolve(legacyTmpPath, 'xeapi_public_key') : '',
runtimeState.ensure = async (key, refresh) => {
if (runtimeState[key]) return runtimeState[key]
if (!refreshTasks.has(key)) {
refreshTasks.set(
key,
Promise.resolve()
.then(refresh)
.finally(() => {
refreshTasks.delete(key)
}),
)
if (!raw) return null
try {
return JSON.parse(raw)
} catch (_) {
return null
}
await refreshTasks.get(key)
return runtimeState[key]
}
/**
* 获取当前 anonymous_token(纯内存,不触发任何 I/O)。
* @returns {string}
*/
const getAnonymousToken = () => anonymousToken
/**
* 写入 anonymous_token。
* @param {string} token
*/
const setAnonymousToken = (token) => {
if (typeof token === 'string' && token) {
anonymousToken = token
}
}
/**
* 确保 anonymous_token 可用:优先返回内存值,缺失时刷新一次。
* 并发调用会共享同一个刷新任务,避免重复请求。
* @param {() => Promise<string>} refresh 刷新函数,返回新的 token
* @returns {Promise<string>}
*/
const ensureAnonymousToken = async (refresh) => {
const cached = getAnonymousToken()
if (cached) return cached
if (typeof refresh !== 'function') return cached
if (!anonymousTokenTask) {
anonymousTokenRefreshing = true
anonymousTokenTask = Promise.resolve()
.then(refresh)
.then((token) => {
setAnonymousToken(token)
return token || ''
})
.finally(() => {
anonymousTokenTask = null
anonymousTokenRefreshing = false
})
}
return anonymousTokenTask
}
/**
* 获取当前 xeapi public key 状态(纯内存,不触发任何 I/O)。
* @returns {Record<string, any> | null}
*/
const getXeapiPublicKey = () => xeapiPublicKey
/**
* 写入 xeapi public key 状态。
* @param {Record<string, any>} publicKey
*/
const setXeapiPublicKey = (publicKey) => {
if (publicKey && typeof publicKey === 'object') {
xeapiPublicKey = publicKey
}
}
/**
* 合并并写入 xeapi public key,保留旧状态里的 sk(服务端并非每次下发 sk)。
* @param {Record<string, any>} publicKey
* @returns {Record<string, any>}
*/
const mergeXeapiPublicKey = (publicKey) => {
const previous = getXeapiPublicKey() || {}
const merged = { ...publicKey }
if (!merged.sk && previous.sk) {
merged.sk = previous.sk
}
xeapiPublicKey = merged
return merged
}
/**
* 确保 xeapi public key 可用。
*
* 语义:有缓存且未声明 force 时直接复用;否则调用 refresh 刷新,并发去重。
* “version 是否变化”由调用方判断并通过 options.force 表达。
*
* @param {() => Promise<Record<string, any>>} refresh 刷新函数,返回新的 public key 状态
* @param {{ force?: boolean }} [options]
* @returns {Promise<Record<string, any> | null>}
*/
const ensureXeapiPublicKey = async (refresh, options = {}) => {
const cached = getXeapiPublicKey()
if (cached && !options.force) {
return cached
}
if (typeof refresh !== 'function') {
return cached
}
if (!xeapiPublicKeyTask) {
xeapiPublicKeyRefreshing = true
xeapiPublicKeyTask = Promise.resolve()
.then(refresh)
.then((result) => mergeXeapiPublicKey(result || {}))
.finally(() => {
xeapiPublicKeyTask = null
xeapiPublicKeyRefreshing = false
})
}
return xeapiPublicKeyTask
}
/**
* 刷新类请求是否正在进行中。request 层据此跳过“缺失即刷新”,避免死锁。
* @returns {boolean}
*/
const isRefreshing = () => anonymousTokenRefreshing || xeapiPublicKeyRefreshing
/**
* 匿名 token 刷新是否进行中。
* @returns {boolean}
*/
const isAnonymousTokenRefreshing = () => anonymousTokenRefreshing
/**
* xeapi public key 刷新是否进行中。
* @returns {boolean}
*/
const isXeapiPublicKeyRefreshing = () => xeapiPublicKeyRefreshing
/**
* 清空内存状态。仅用于测试,或需要强制重新初始化的场景。
*/
const reset = () => {
anonymousToken = ''
xeapiPublicKey = null
anonymousTokenTask = null
xeapiPublicKeyTask = null
anonymousTokenRefreshing = false
xeapiPublicKeyRefreshing = false
}
/**
* 从环境变量 / 旧 /tmp 文件加载可选种子到内存。
* 默认不启用——由 app/generateConfig 等启动路径显式调用;
* 测试与只读 FS 场景可以不调用,从而完全避免文件 I/O。
*
* @returns {{ anonymousToken: string, xeapiPublicKey: Record<string, any> | null }}
*/
const loadSeed = () => {
const token = readAnonymousTokenSeed()
if (token && !anonymousToken) {
anonymousToken = token
}
const publicKey = readXeapiPublicKeySeed()
if (publicKey && !xeapiPublicKey) {
xeapiPublicKey = publicKey
}
return { anonymousToken, xeapiPublicKey }
}
module.exports = {
getAnonymousToken,
setAnonymousToken,
ensureAnonymousToken,
getXeapiPublicKey,
setXeapiPublicKey,
mergeXeapiPublicKey,
ensureXeapiPublicKey,
isRefreshing,
isAnonymousTokenRefreshing,
isXeapiPublicKeyRefreshing,
loadSeed,
reset,
// 供 generateConfig 等迁移逻辑按需加载种子
readAnonymousTokenSeed,
readXeapiPublicKeySeed,
}
module.exports = runtimeState
-235
View File
@@ -1,235 +0,0 @@
const assert = require('assert')
describe('util/runtimeState', () => {
let runtimeState
beforeEach(() => {
// 每个用例从干净状态开始,并清掉可能影响 seed 的环境变量
delete process.env.NETEASE_ANONYMOUS_TOKEN
delete process.env.NETEASE_XEAPI_PUBLIC_KEY
delete require.cache[require.resolve('./runtimeState')]
runtimeState = require('./runtimeState')
})
afterEach(() => {
delete process.env.NETEASE_ANONYMOUS_TOKEN
delete process.env.NETEASE_XEAPI_PUBLIC_KEY
})
describe('anonymous token', () => {
it('set/get 往返', () => {
runtimeState.setAnonymousToken('TOKEN_A')
assert.strictEqual(runtimeState.getAnonymousToken(), 'TOKEN_A')
})
it('set 忽略空值,不覆盖已有 token', () => {
runtimeState.setAnonymousToken('TOKEN_A')
runtimeState.setAnonymousToken('')
runtimeState.setAnonymousToken(null)
assert.strictEqual(runtimeState.getAnonymousToken(), 'TOKEN_A')
})
it('优先从环境变量读取种子(需显式 loadSeed)', () => {
process.env.NETEASE_ANONYMOUS_TOKEN = 'ENV_TOKEN'
assert.strictEqual(runtimeState.getAnonymousToken(), '')
runtimeState.loadSeed()
assert.strictEqual(runtimeState.getAnonymousToken(), 'ENV_TOKEN')
})
it('缺失时 ensureAnonymousToken 调用 refresh 并写入', async () => {
let calls = 0
const token = await runtimeState.ensureAnonymousToken(async () => {
calls++
return 'FRESH_TOKEN'
})
assert.strictEqual(token, 'FRESH_TOKEN')
assert.strictEqual(runtimeState.getAnonymousToken(), 'FRESH_TOKEN')
assert.strictEqual(calls, 1)
})
it('已有 token 时 ensureAnonymousToken 不再调用 refresh', async () => {
runtimeState.setAnonymousToken('CACHED')
let calls = 0
const token = await runtimeState.ensureAnonymousToken(async () => {
calls++
return 'FRESH'
})
assert.strictEqual(token, 'CACHED')
assert.strictEqual(calls, 0)
})
it('并发调用共享同一个刷新任务(去重)', async () => {
let calls = 0
const slowRefresh = () =>
new Promise((resolve) =>
setTimeout(() => {
calls++
resolve('SHARED_TOKEN')
}, 20),
)
const results = await Promise.all([
runtimeState.ensureAnonymousToken(slowRefresh),
runtimeState.ensureAnonymousToken(slowRefresh),
runtimeState.ensureAnonymousToken(slowRefresh),
])
assert.deepStrictEqual(results, [
'SHARED_TOKEN',
'SHARED_TOKEN',
'SHARED_TOKEN',
])
assert.strictEqual(calls, 1)
})
it('refresh 失败后任务被清理,可再次重试', async () => {
await assert.rejects(
runtimeState.ensureAnonymousToken(async () => {
throw new Error('boom')
}),
)
assert.strictEqual(runtimeState.isAnonymousTokenRefreshing(), false)
const token = await runtimeState.ensureAnonymousToken(
async () => 'RETRY_OK',
)
assert.strictEqual(token, 'RETRY_OK')
})
it('刷新期间 isAnonymousTokenRefreshing 为 true', async () => {
let sawRefreshing = false
await runtimeState.ensureAnonymousToken(async () => {
sawRefreshing = runtimeState.isAnonymousTokenRefreshing()
return 'X'
})
assert.strictEqual(sawRefreshing, true)
assert.strictEqual(runtimeState.isAnonymousTokenRefreshing(), false)
})
})
describe('xeapi public key', () => {
it('set/get 往返', () => {
runtimeState.setXeapiPublicKey({ publicKey: 'PK', version: 'v1' })
assert.deepStrictEqual(runtimeState.getXeapiPublicKey(), {
publicKey: 'PK',
version: 'v1',
})
})
it('优先从环境变量读取 JSON 种子(需显式 loadSeed)', () => {
process.env.NETEASE_XEAPI_PUBLIC_KEY = JSON.stringify({
publicKey: 'ENV_PK',
version: 'env-v',
})
assert.strictEqual(runtimeState.getXeapiPublicKey(), null)
runtimeState.loadSeed()
assert.deepStrictEqual(runtimeState.getXeapiPublicKey(), {
publicKey: 'ENV_PK',
version: 'env-v',
})
})
it('环境变量中的非法 JSON 被忽略', () => {
process.env.NETEASE_XEAPI_PUBLIC_KEY = 'not-json'
runtimeState.loadSeed()
assert.strictEqual(runtimeState.getXeapiPublicKey(), null)
})
it('mergeXeapiPublicKey 保留旧 sk(服务端未下发时)', () => {
runtimeState.setXeapiPublicKey({ publicKey: 'PK1', sk: 'SK1' })
const merged = runtimeState.mergeXeapiPublicKey({ publicKey: 'PK2' })
assert.strictEqual(merged.sk, 'SK1')
assert.strictEqual(merged.publicKey, 'PK2')
})
it('mergeXeapiPublicKey 优先使用新 sk', () => {
runtimeState.setXeapiPublicKey({ publicKey: 'PK1', sk: 'SK1' })
const merged = runtimeState.mergeXeapiPublicKey({
publicKey: 'PK2',
sk: 'SK2',
})
assert.strictEqual(merged.sk, 'SK2')
})
it('缺失时 ensureXeapiPublicKey 调用 refresh 并合并写入', async () => {
let calls = 0
const result = await runtimeState.ensureXeapiPublicKey(async () => {
calls++
return { publicKey: 'FRESH_PK', version: 'v2' }
})
assert.strictEqual(calls, 1)
assert.deepStrictEqual(result, { publicKey: 'FRESH_PK', version: 'v2' })
assert.deepStrictEqual(runtimeState.getXeapiPublicKey(), {
publicKey: 'FRESH_PK',
version: 'v2',
})
})
it('已有 key 且未 force 时不刷新', async () => {
runtimeState.setXeapiPublicKey({ publicKey: 'CACHED' })
let calls = 0
const result = await runtimeState.ensureXeapiPublicKey(async () => {
calls++
return { publicKey: 'FRESH' }
})
assert.strictEqual(calls, 0)
assert.strictEqual(result.publicKey, 'CACHED')
})
it('force 时即使已有 key 也刷新', async () => {
runtimeState.setXeapiPublicKey({ publicKey: 'CACHED', sk: 'OLD_SK' })
let calls = 0
const result = await runtimeState.ensureXeapiPublicKey(
async () => {
calls++
return { publicKey: 'FRESH' }
},
{ force: true },
)
assert.strictEqual(calls, 1)
assert.strictEqual(result.publicKey, 'FRESH')
// 旧 sk 被保留
assert.strictEqual(result.sk, 'OLD_SK')
})
it('并发 ensureXeapiPublicKey 共享同一刷新任务', async () => {
let calls = 0
const slowRefresh = () =>
new Promise((resolve) =>
setTimeout(() => {
calls++
resolve({ publicKey: 'SHARED_PK' })
}, 20),
)
const results = await Promise.all([
runtimeState.ensureXeapiPublicKey(slowRefresh),
runtimeState.ensureXeapiPublicKey(slowRefresh),
])
assert.strictEqual(calls, 1)
assert.strictEqual(results[0].publicKey, 'SHARED_PK')
assert.strictEqual(results[1].publicKey, 'SHARED_PK')
})
it('刷新期间 isXeapiPublicKeyRefreshing 为 true', async () => {
let sawRefreshing = false
await runtimeState.ensureXeapiPublicKey(async () => {
sawRefreshing = runtimeState.isXeapiPublicKeyRefreshing()
return { publicKey: 'X' }
})
assert.strictEqual(sawRefreshing, true)
assert.strictEqual(runtimeState.isXeapiPublicKeyRefreshing(), false)
})
})
describe('reset', () => {
it('清空 token 与 publicKey', () => {
runtimeState.setAnonymousToken('TOKEN')
runtimeState.setXeapiPublicKey({ publicKey: 'PK' })
runtimeState.reset()
assert.strictEqual(runtimeState.getAnonymousToken(), '')
assert.strictEqual(runtimeState.getXeapiPublicKey(), null)
})
})
})
-24
View File
@@ -1,24 +0,0 @@
const registerXeapiKey = require('../module/register_xeapikey')
const getXeapiPublicKey = async (currentPublicKey = {}, deviceId = '') => {
const result = await registerXeapiKey(
{
deviceId,
currentKeyVersion: currentPublicKey.version || '',
},
null,
)
const publicKey = result.body
if (!publicKey.sk && currentPublicKey.sk) {
publicKey.sk = currentPublicKey.sk
}
if (!publicKey.sk) {
throw new Error('xeapi public key response missing sk')
}
return publicKey
}
module.exports = {
getXeapiPublicKey,
}