feat: 完善 StarLight 皮肤站集成
This commit is contained in:
@ -35,7 +35,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
.build()
|
||||
}
|
||||
|
||||
/// Checks if the authentication servers are reachable.
|
||||
/// Checks if the StarLight authentication server is reachable.
|
||||
#[tauri::command]
|
||||
pub async fn check_reachable() -> Result<()> {
|
||||
minecraft_auth::check_reachable().await?;
|
||||
|
||||
@ -1,98 +1,254 @@
|
||||
// Runs inside the embedded skin site only. The JWT never leaves that origin.
|
||||
(() => {
|
||||
if (location.origin !== 'https://skin.starlight.cool' || window.parent === window) return
|
||||
;(() => {
|
||||
if (location.origin !== 'https://skin.starlight.cool' || window.parent === window) return
|
||||
|
||||
const launcherOrigins = new Set([
|
||||
'http://localhost:5201',
|
||||
'http://tauri.localhost',
|
||||
'https://tauri.localhost',
|
||||
'tauri://localhost',
|
||||
])
|
||||
let parentOrigin
|
||||
let lastToken
|
||||
let lastCheck = 0
|
||||
let generation = 0
|
||||
let pending
|
||||
let snapshot = { status: 'checking', user: null }
|
||||
const launcherOrigins = new Set([
|
||||
'http://localhost:5201',
|
||||
'http://tauri.localhost',
|
||||
'https://tauri.localhost',
|
||||
'tauri://localhost',
|
||||
])
|
||||
let parentOrigin
|
||||
let lastToken
|
||||
let lastCheck = 0
|
||||
let generation = 0
|
||||
let pending
|
||||
const pendingLuck = new Map()
|
||||
const pendingPlayers = new Map()
|
||||
let snapshot = { status: 'checking', user: null }
|
||||
|
||||
function publish(status, user = null) {
|
||||
snapshot = { status, user }
|
||||
if (parentOrigin) {
|
||||
window.parent.postMessage({ type: 'starlight-skin-session', ...snapshot }, parentOrigin)
|
||||
}
|
||||
}
|
||||
function publish(status, user = null) {
|
||||
snapshot = { status, user }
|
||||
if (parentOrigin) {
|
||||
window.parent.postMessage({ type: 'starlight-skin-session', ...snapshot }, parentOrigin)
|
||||
}
|
||||
}
|
||||
|
||||
async function checkSession(force = false) {
|
||||
if (!parentOrigin) return
|
||||
let token
|
||||
try {
|
||||
token = localStorage.getItem('loginToken') || ''
|
||||
} catch {
|
||||
publish('error')
|
||||
return
|
||||
}
|
||||
const changed = token !== lastToken
|
||||
if (!changed && (pending || (!force && Date.now() - lastCheck < 60_000))) return
|
||||
const revision = ++generation
|
||||
pending?.abort()
|
||||
pending = undefined
|
||||
lastToken = token
|
||||
lastCheck = Date.now()
|
||||
if (!token) {
|
||||
publish('signed-out')
|
||||
return
|
||||
}
|
||||
if (changed) publish('checking')
|
||||
const controller = new AbortController()
|
||||
pending = controller
|
||||
const timeout = setTimeout(() => controller.abort(), 10_000)
|
||||
try {
|
||||
const response = await fetch('/starlight/user', {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
signal: controller.signal,
|
||||
cache: 'no-store',
|
||||
redirect: 'error',
|
||||
})
|
||||
const body = response.ok ? await response.json() : null
|
||||
// A logout or account switch must win over an older in-flight response.
|
||||
if (revision !== generation || localStorage.getItem('loginToken') !== token) return
|
||||
if (response.status === 401 || response.status === 403 || body?.payload?.banned) {
|
||||
publish('signed-out')
|
||||
} else if (
|
||||
response.ok && typeof body?.payload?.uuid === 'string' &&
|
||||
typeof body.payload.username === 'string' && body.payload.username.length > 0
|
||||
) {
|
||||
publish('signed-in', { uuid: body.payload.uuid, username: body.payload.username })
|
||||
} else {
|
||||
publish('error')
|
||||
}
|
||||
} catch {
|
||||
if (revision === generation) publish('error')
|
||||
} finally {
|
||||
clearTimeout(timeout)
|
||||
if (revision === generation) pending = undefined
|
||||
}
|
||||
}
|
||||
function publishLuck(requestId, result) {
|
||||
if (!parentOrigin) return
|
||||
window.parent.postMessage(
|
||||
{ type: 'starlight-skin-luck-result', requestId, ...result },
|
||||
parentOrigin,
|
||||
)
|
||||
}
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
if (event.source !== window.parent || !launcherOrigins.has(event.origin)) return
|
||||
if (event.data?.type !== 'starlight-skin-session-connect') return
|
||||
parentOrigin = event.origin
|
||||
publish(snapshot.status, snapshot.user)
|
||||
void checkSession(true)
|
||||
})
|
||||
// Storage events cover other tabs; polling also covers same-document SPA login/logout.
|
||||
window.addEventListener('storage', () => void checkSession())
|
||||
let timer = setInterval(() => void checkSession(), 1000)
|
||||
window.addEventListener('pagehide', () => {
|
||||
clearInterval(timer)
|
||||
++generation
|
||||
pending?.abort()
|
||||
pending = undefined
|
||||
})
|
||||
window.addEventListener('pageshow', (event) => {
|
||||
if (!event.persisted) return
|
||||
timer = setInterval(() => void checkSession(), 1000)
|
||||
void checkSession(true)
|
||||
})
|
||||
function publishPlayers(requestId, result) {
|
||||
if (!parentOrigin) return
|
||||
window.parent.postMessage(
|
||||
{ type: 'starlight-skin-players-result', requestId, ...result },
|
||||
parentOrigin,
|
||||
)
|
||||
}
|
||||
|
||||
async function requestPlayers(requestId) {
|
||||
if (pendingPlayers.has(requestId)) return
|
||||
let token
|
||||
try {
|
||||
token = localStorage.getItem('loginToken') || ''
|
||||
} catch {
|
||||
publishPlayers(requestId, { ok: false, error: 'Unable to read the skin site session.' })
|
||||
return
|
||||
}
|
||||
if (!token) {
|
||||
publish('signed-out')
|
||||
publishPlayers(requestId, { ok: false, error: 'Sign in to the skin site first.' })
|
||||
return
|
||||
}
|
||||
|
||||
const controller = new AbortController()
|
||||
pendingPlayers.set(requestId, controller)
|
||||
const timeout = setTimeout(() => controller.abort(), 10_000)
|
||||
try {
|
||||
const response = await fetch('/starlight/skin/player', {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
signal: controller.signal,
|
||||
cache: 'no-store',
|
||||
redirect: 'error',
|
||||
})
|
||||
const body = await response.json().catch(() => null)
|
||||
if (localStorage.getItem('loginToken') !== token) {
|
||||
publishPlayers(requestId, { ok: false, error: 'The skin site account changed.' })
|
||||
return
|
||||
}
|
||||
if (response.status === 401 || response.status === 403) publish('signed-out')
|
||||
const rawPlayers = Array.isArray(body?.payload) ? body.payload : null
|
||||
const players = rawPlayers?.slice(0, 100).flatMap((player) => {
|
||||
if (
|
||||
typeof player?.uuid !== 'string' ||
|
||||
!/^[0-9a-f-]{32,36}$/i.test(player.uuid) ||
|
||||
typeof player?.name !== 'string' ||
|
||||
player.name.length < 1 ||
|
||||
player.name.length > 64
|
||||
)
|
||||
return []
|
||||
return [{ uuid: player.uuid, name: player.name, isMojang: player.isMojang === true }]
|
||||
})
|
||||
if (response.ok && players) {
|
||||
publishPlayers(requestId, { ok: true, players })
|
||||
} else {
|
||||
publishPlayers(requestId, {
|
||||
ok: false,
|
||||
error:
|
||||
typeof body?.errorMessage === 'string' && body.errorMessage
|
||||
? body.errorMessage.slice(0, 300)
|
||||
: 'The skin site returned an invalid player list.',
|
||||
})
|
||||
}
|
||||
} catch {
|
||||
publishPlayers(requestId, { ok: false, error: 'Unable to reach the player service.' })
|
||||
} finally {
|
||||
clearTimeout(timeout)
|
||||
pendingPlayers.delete(requestId)
|
||||
}
|
||||
}
|
||||
|
||||
async function requestLuck(requestId) {
|
||||
if (pendingLuck.has(requestId)) return
|
||||
let token
|
||||
try {
|
||||
token = localStorage.getItem('loginToken') || ''
|
||||
} catch {
|
||||
publishLuck(requestId, { ok: false, error: 'Unable to read the skin site session.' })
|
||||
return
|
||||
}
|
||||
if (!token) {
|
||||
publish('signed-out')
|
||||
publishLuck(requestId, { ok: false, error: 'Sign in to the skin site first.' })
|
||||
return
|
||||
}
|
||||
|
||||
const controller = new AbortController()
|
||||
pendingLuck.set(requestId, controller)
|
||||
const timeout = setTimeout(() => controller.abort(), 10_000)
|
||||
try {
|
||||
const response = await fetch('/starlight/luck', {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
signal: controller.signal,
|
||||
cache: 'no-store',
|
||||
redirect: 'error',
|
||||
})
|
||||
const body = await response.json().catch(() => null)
|
||||
if (localStorage.getItem('loginToken') !== token) {
|
||||
publishLuck(requestId, { ok: false, error: 'The skin site account changed.' })
|
||||
return
|
||||
}
|
||||
if (response.status === 401 || response.status === 403) publish('signed-out')
|
||||
const luck = Number(body?.payload?.luck)
|
||||
if (response.ok && Number.isFinite(luck) && luck >= 0 && luck <= 100) {
|
||||
publishLuck(requestId, { ok: true, luck })
|
||||
} else {
|
||||
publishLuck(requestId, {
|
||||
ok: false,
|
||||
error:
|
||||
typeof body?.errorMessage === 'string' && body.errorMessage
|
||||
? body.errorMessage.slice(0, 300)
|
||||
: 'The skin site returned an invalid luck result.',
|
||||
})
|
||||
}
|
||||
} catch {
|
||||
publishLuck(requestId, { ok: false, error: 'Unable to reach the luck service.' })
|
||||
} finally {
|
||||
clearTimeout(timeout)
|
||||
pendingLuck.delete(requestId)
|
||||
}
|
||||
}
|
||||
|
||||
async function checkSession(force = false) {
|
||||
if (!parentOrigin) return
|
||||
let token
|
||||
try {
|
||||
token = localStorage.getItem('loginToken') || ''
|
||||
} catch {
|
||||
publish('error')
|
||||
return
|
||||
}
|
||||
const changed = token !== lastToken
|
||||
if (!changed && (pending || (!force && Date.now() - lastCheck < 60_000))) return
|
||||
const revision = ++generation
|
||||
pending?.abort()
|
||||
pending = undefined
|
||||
lastToken = token
|
||||
lastCheck = Date.now()
|
||||
if (!token) {
|
||||
publish('signed-out')
|
||||
return
|
||||
}
|
||||
if (changed) publish('checking')
|
||||
const controller = new AbortController()
|
||||
pending = controller
|
||||
const timeout = setTimeout(() => controller.abort(), 10_000)
|
||||
try {
|
||||
const response = await fetch('/starlight/user', {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
signal: controller.signal,
|
||||
cache: 'no-store',
|
||||
redirect: 'error',
|
||||
})
|
||||
const body = response.ok ? await response.json() : null
|
||||
// A logout or account switch must win over an older in-flight response.
|
||||
if (revision !== generation || localStorage.getItem('loginToken') !== token) return
|
||||
if (response.status === 401 || response.status === 403 || body?.payload?.banned) {
|
||||
publish('signed-out')
|
||||
} else if (
|
||||
response.ok &&
|
||||
typeof body?.payload?.uuid === 'string' &&
|
||||
typeof body.payload.username === 'string' &&
|
||||
body.payload.username.length > 0
|
||||
) {
|
||||
publish('signed-in', { uuid: body.payload.uuid, username: body.payload.username })
|
||||
} else {
|
||||
publish('error')
|
||||
}
|
||||
} catch {
|
||||
if (revision === generation) publish('error')
|
||||
} finally {
|
||||
clearTimeout(timeout)
|
||||
if (revision === generation) pending = undefined
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
if (event.source !== window.parent || !launcherOrigins.has(event.origin)) return
|
||||
if (event.data?.type === 'starlight-skin-session-connect') {
|
||||
parentOrigin = event.origin
|
||||
publish(snapshot.status, snapshot.user)
|
||||
void checkSession(true)
|
||||
return
|
||||
}
|
||||
if (
|
||||
event.data?.type === 'starlight-skin-luck-request' &&
|
||||
parentOrigin === event.origin &&
|
||||
typeof event.data.requestId === 'string' &&
|
||||
/^skin-luck-\d+-\d+$/.test(event.data.requestId)
|
||||
) {
|
||||
void requestLuck(event.data.requestId)
|
||||
return
|
||||
}
|
||||
if (
|
||||
event.data?.type === 'starlight-skin-players-request' &&
|
||||
parentOrigin === event.origin &&
|
||||
typeof event.data.requestId === 'string' &&
|
||||
/^skin-players-\d+-\d+$/.test(event.data.requestId)
|
||||
) {
|
||||
void requestPlayers(event.data.requestId)
|
||||
}
|
||||
})
|
||||
// Storage events cover other tabs; polling also covers same-document SPA login/logout.
|
||||
window.addEventListener('storage', () => void checkSession())
|
||||
let timer = setInterval(() => void checkSession(), 1000)
|
||||
window.addEventListener('pagehide', () => {
|
||||
clearInterval(timer)
|
||||
++generation
|
||||
pending?.abort()
|
||||
pending = undefined
|
||||
for (const controller of pendingLuck.values()) controller.abort()
|
||||
pendingLuck.clear()
|
||||
for (const controller of pendingPlayers.values()) controller.abort()
|
||||
pendingPlayers.clear()
|
||||
})
|
||||
window.addEventListener('pageshow', (event) => {
|
||||
if (!event.persisted) return
|
||||
timer = setInterval(() => void checkSession(), 1000)
|
||||
void checkSession(true)
|
||||
})
|
||||
})()
|
||||
|
||||
@ -5,63 +5,206 @@ const test = require('node:test')
|
||||
const source = fs.readFileSync(require('node:path').join(__dirname, 'skin_site_bridge.js'), 'utf8')
|
||||
|
||||
function harness(origin = 'https://skin.starlight.cool') {
|
||||
let token = null
|
||||
const listeners = {}, messages = [], requests = []
|
||||
let tick
|
||||
let result = async () => ({ ok: true, status: 200, json: async () => ({ payload: { uuid: 'one', username: 'One' } }) })
|
||||
const parent = { postMessage(data, target) { messages.push({ data, target }) } }
|
||||
const context = {
|
||||
location: { origin },
|
||||
window: { parent, addEventListener(type, callback) { listeners[type] = callback } },
|
||||
localStorage: { getItem() { return token } },
|
||||
fetch(...args) { requests.push(args); return result(...args) },
|
||||
AbortController, Date, setTimeout, clearTimeout,
|
||||
setInterval(callback) { tick = callback; return 1 }, clearInterval() {},
|
||||
}
|
||||
vm.runInNewContext(source, context)
|
||||
return {
|
||||
messages, requests, listeners, parent,
|
||||
token(value) { token = value }, result(value) { result = value },
|
||||
async tick() { tick?.(); await new Promise(setImmediate) },
|
||||
async connect(origin = 'http://localhost:5201', source = parent) {
|
||||
listeners.message?.({ source, origin, data: { type: 'starlight-skin-session-connect' } })
|
||||
await new Promise(setImmediate)
|
||||
},
|
||||
}
|
||||
let token = null
|
||||
const listeners = {},
|
||||
messages = [],
|
||||
requests = []
|
||||
let tick
|
||||
let result = async () => ({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ payload: { uuid: 'one', username: 'One' } }),
|
||||
})
|
||||
const parent = {
|
||||
postMessage(data, target) {
|
||||
messages.push({ data, target })
|
||||
},
|
||||
}
|
||||
const context = {
|
||||
location: { origin },
|
||||
window: {
|
||||
parent,
|
||||
addEventListener(type, callback) {
|
||||
listeners[type] = callback
|
||||
},
|
||||
},
|
||||
localStorage: {
|
||||
getItem() {
|
||||
return token
|
||||
},
|
||||
},
|
||||
fetch(...args) {
|
||||
requests.push(args)
|
||||
return result(...args)
|
||||
},
|
||||
AbortController,
|
||||
Date,
|
||||
setTimeout,
|
||||
clearTimeout,
|
||||
setInterval(callback) {
|
||||
tick = callback
|
||||
return 1
|
||||
},
|
||||
clearInterval() {},
|
||||
}
|
||||
vm.runInNewContext(source, context)
|
||||
return {
|
||||
messages,
|
||||
requests,
|
||||
listeners,
|
||||
parent,
|
||||
token(value) {
|
||||
token = value
|
||||
},
|
||||
result(value) {
|
||||
result = value
|
||||
},
|
||||
async tick() {
|
||||
tick?.()
|
||||
await new Promise(setImmediate)
|
||||
},
|
||||
async connect(origin = 'http://localhost:5201', source = parent) {
|
||||
listeners.message?.({ source, origin, data: { type: 'starlight-skin-session-connect' } })
|
||||
await new Promise(setImmediate)
|
||||
},
|
||||
async message(data, origin = 'http://localhost:5201', source = parent) {
|
||||
listeners.message?.({ source, origin, data })
|
||||
await new Promise(setImmediate)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
test('bridge is restricted to the skin origin and an allowlisted parent', async () => {
|
||||
assert.equal(harness('https://evil.example').listeners.message, undefined)
|
||||
const h = harness(); h.token('test-token')
|
||||
await h.connect('https://evil.example'); await h.connect('http://localhost:5201', {})
|
||||
assert.equal(h.requests.length, 0); assert.equal(h.messages.length, 0)
|
||||
await h.connect()
|
||||
assert.equal(h.requests[0][0], '/starlight/user')
|
||||
assert.equal(h.requests[0][1].headers.Authorization, 'Bearer test-token')
|
||||
assert.equal(h.requests[0][1].redirect, 'error')
|
||||
assert.equal(h.messages.at(-1).data.status, 'signed-in')
|
||||
assert.ok(h.messages.every(m => m.target === 'http://localhost:5201'))
|
||||
assert.ok(!JSON.stringify(h.messages).includes('test-token'))
|
||||
assert.equal(harness('https://evil.example').listeners.message, undefined)
|
||||
const h = harness()
|
||||
h.token('test-token')
|
||||
await h.connect('https://evil.example')
|
||||
await h.connect('http://localhost:5201', {})
|
||||
assert.equal(h.requests.length, 0)
|
||||
assert.equal(h.messages.length, 0)
|
||||
await h.connect()
|
||||
assert.equal(h.requests[0][0], '/starlight/user')
|
||||
assert.equal(h.requests[0][1].headers.Authorization, 'Bearer test-token')
|
||||
assert.equal(h.requests[0][1].redirect, 'error')
|
||||
assert.equal(h.messages.at(-1).data.status, 'signed-in')
|
||||
assert.ok(h.messages.every((m) => m.target === 'http://localhost:5201'))
|
||||
assert.ok(!JSON.stringify(h.messages).includes('test-token'))
|
||||
})
|
||||
|
||||
test('logout, invalid token, network error and account switching replace old state', async () => {
|
||||
const h = harness(); await h.connect()
|
||||
assert.equal(h.messages.at(-1).data.status, 'signed-out')
|
||||
h.token('first'); await h.tick(); assert.equal(h.messages.at(-1).data.user.username, 'One')
|
||||
h.result(async () => ({ ok: false, status: 401 }))
|
||||
h.token('expired'); await h.tick(); assert.equal(h.messages.at(-1).data.status, 'signed-out')
|
||||
h.result(async () => { throw Error('offline') })
|
||||
h.token('network-error'); await h.tick(); assert.equal(h.messages.at(-1).data.status, 'error')
|
||||
h.token(null); await h.tick(); assert.equal(h.messages.at(-1).data.status, 'signed-out')
|
||||
const h = harness()
|
||||
await h.connect()
|
||||
assert.equal(h.messages.at(-1).data.status, 'signed-out')
|
||||
h.token('first')
|
||||
await h.tick()
|
||||
assert.equal(h.messages.at(-1).data.user.username, 'One')
|
||||
h.result(async () => ({ ok: false, status: 401 }))
|
||||
h.token('expired')
|
||||
await h.tick()
|
||||
assert.equal(h.messages.at(-1).data.status, 'signed-out')
|
||||
h.result(async () => {
|
||||
throw Error('offline')
|
||||
})
|
||||
h.token('network-error')
|
||||
await h.tick()
|
||||
assert.equal(h.messages.at(-1).data.status, 'error')
|
||||
h.token(null)
|
||||
await h.tick()
|
||||
assert.equal(h.messages.at(-1).data.status, 'signed-out')
|
||||
})
|
||||
|
||||
test('a delayed response cannot restore the user after logout', async () => {
|
||||
const h = harness(); let finish
|
||||
h.result(() => new Promise(resolve => { finish = resolve }))
|
||||
h.token('first'); await h.connect()
|
||||
h.token(null); await h.tick()
|
||||
finish({ ok: true, status: 200, json: async () => ({ payload: { uuid: 'one', username: 'One' } }) })
|
||||
await new Promise(setImmediate)
|
||||
assert.equal(h.messages.at(-1).data.status, 'signed-out')
|
||||
assert.ok(!h.messages.some(m => m.data.status === 'signed-in'))
|
||||
const h = harness()
|
||||
let finish
|
||||
h.result(
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
finish = resolve
|
||||
}),
|
||||
)
|
||||
h.token('first')
|
||||
await h.connect()
|
||||
h.token(null)
|
||||
await h.tick()
|
||||
finish({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ payload: { uuid: 'one', username: 'One' } }),
|
||||
})
|
||||
await new Promise(setImmediate)
|
||||
assert.equal(h.messages.at(-1).data.status, 'signed-out')
|
||||
assert.ok(!h.messages.some((m) => m.data.status === 'signed-in'))
|
||||
})
|
||||
|
||||
test('luck requests stay on the skin origin and return only the validated score', async () => {
|
||||
const h = harness()
|
||||
h.token('test-token')
|
||||
await h.connect()
|
||||
h.result(async () => ({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ payload: { luck: 73 } }),
|
||||
}))
|
||||
await h.message({ type: 'starlight-skin-luck-request', requestId: 'skin-luck-1-1' })
|
||||
const request = h.requests.at(-1)
|
||||
assert.equal(request[0], '/starlight/luck')
|
||||
assert.equal(request[1].headers.Authorization, 'Bearer test-token')
|
||||
assert.equal(request[1].redirect, 'error')
|
||||
const message = h.messages.at(-1)
|
||||
assert.equal(message.target, 'http://localhost:5201')
|
||||
assert.equal(message.data.type, 'starlight-skin-luck-result')
|
||||
assert.equal(message.data.requestId, 'skin-luck-1-1')
|
||||
assert.equal(message.data.ok, true)
|
||||
assert.equal(message.data.luck, 73)
|
||||
assert.ok(!JSON.stringify(h.messages).includes('test-token'))
|
||||
})
|
||||
|
||||
test('luck requests reject untrusted parents, missing sessions, and invalid scores', async () => {
|
||||
const h = harness()
|
||||
await h.connect()
|
||||
const before = h.requests.length
|
||||
await h.message(
|
||||
{ type: 'starlight-skin-luck-request', requestId: 'skin-luck-2-1' },
|
||||
'https://evil.example',
|
||||
)
|
||||
assert.equal(h.requests.length, before)
|
||||
await h.message({ type: 'starlight-skin-luck-request', requestId: 'skin-luck-2-2' })
|
||||
assert.equal(h.requests.length, before)
|
||||
assert.equal(h.messages.at(-1).data.ok, false)
|
||||
|
||||
h.token('test-token')
|
||||
h.result(async () => ({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ payload: { luck: 101 } }),
|
||||
}))
|
||||
await h.message({ type: 'starlight-skin-luck-request', requestId: 'skin-luck-2-3' })
|
||||
assert.equal(h.messages.at(-1).data.ok, false)
|
||||
})
|
||||
|
||||
test('player requests return a sanitized complete player collection without exposing the token', async () => {
|
||||
const h = harness()
|
||||
h.token('test-token')
|
||||
await h.connect()
|
||||
h.result(async () => ({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({
|
||||
payload: [
|
||||
{ uuid: '0123456789abcdef0123456789abcdef', name: 'PlayerOne', isMojang: false },
|
||||
{ uuid: 'fedcba9876543210fedcba9876543210', name: 'Official', isMojang: true },
|
||||
{ uuid: 'bad', name: 'Ignored', isMojang: false },
|
||||
],
|
||||
}),
|
||||
}))
|
||||
await h.message({ type: 'starlight-skin-players-request', requestId: 'skin-players-1-1' })
|
||||
const request = h.requests.at(-1)
|
||||
assert.equal(request[0], '/starlight/skin/player')
|
||||
assert.equal(request[1].headers.Authorization, 'Bearer test-token')
|
||||
const message = h.messages.at(-1)
|
||||
assert.equal(message.data.type, 'starlight-skin-players-result')
|
||||
assert.equal(message.data.ok, true)
|
||||
assert.equal(message.data.players.length, 2)
|
||||
assert.equal(message.data.players[1].isMojang, true)
|
||||
assert.ok(!JSON.stringify(h.messages).includes('test-token'))
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user