edge-core-js
Version:
Edge account & wallet management library
188 lines (154 loc) • 4.37 kB
JavaScript
import { asObject, asOptional, asString, uncleaner } from 'cleaners'
import { bridgifyObject, close } from 'yaob'
import { shuffle } from '../../util/shuffle'
import { asLobbyLoginPayload } from '../login/edge'
import { fetchLobbyRequest, sendLobbyReply } from '../login/lobby'
import {
decryptChildKey,
sanitizeLoginStash,
searchTree,
syncLogin
} from '../login/login'
import { getStashById } from '../login/login-selectors'
import { ensureAccountExists } from './account-init'
const wasLobbyLoginPayload = uncleaner(asLobbyLoginPayload)
const asAppIdInfo = asObject({
appName: asString,
darkImageUrl: asOptional(asString),
lightImageUrl: asOptional(asString),
imageUrl: asOptional(asString)
})
/**
* Translate an appId into a user-presentable icon and string.
*/
export async function fetchAppIdInfo(
ai,
appId
) {
try {
const [infoServerUri] = shuffle(ai.props.state.infoServers)
const url = `${infoServerUri}/v1/appIdInfo/${appId}`
const response = await ai.props.io.fetch(url, { corsBypass: 'never' })
if (response.status === 404) {
return { appName: appId }
}
if (!response.ok) {
throw new Error(`Fetching ${url} returned ${response.status}`)
}
const clean = asAppIdInfo(await response.json())
// Upgrade legacy responses:
if (clean.lightImageUrl == null) clean.lightImageUrl = clean.imageUrl
if (clean.darkImageUrl == null) clean.darkImageUrl = clean.imageUrl
return clean
} catch (error) {
// Log failures, but still return the appId as a fallback:
ai.props.onError(error)
return { appName: appId }
}
}
/**
* Performs an edge login, approving the request in the provided lobby JSON.
*/
async function approveLoginRequest(
ai,
accountId,
appId,
lobbyId,
lobbyJson
) {
const { sessionKey } = ai.props.state.accounts[accountId]
// For crash errors:
ai.props.log.breadcrumb('approveLoginRequest', {})
// Ensure that the login object & account repo exist:
await syncLogin(ai, sessionKey)
const { stashTree } = getStashById(ai, sessionKey.loginId)
const newStashTree = await ensureAccountExists(
ai,
stashTree,
sessionKey,
appId
)
const appStash = searchTree(newStashTree, stash => stash.appId === appId)
if (appStash == null) {
throw new Error('Failed to create the requested login object')
}
const appKey = decryptChildKey(newStashTree, sessionKey, appStash.loginId)
// Send the reply:
const replyData = wasLobbyLoginPayload({
appId,
loginKey: appKey.loginKey,
loginStash: sanitizeLoginStash(newStashTree, appId)
})
await sendLobbyReply(ai, lobbyId, lobbyJson, replyData)
let timeout
const accountApi = ai.props.output.accounts[accountId].accountApi
if (accountApi != null) {
accountApi.on('close', () => {
if (timeout != null) clearTimeout(timeout)
})
}
timeout = setTimeout(() => {
timeout = undefined
syncLogin(ai, sessionKey)
.then(() => {
timeout = setTimeout(() => {
timeout = undefined
syncLogin(ai, sessionKey).catch(error => ai.props.onError(error))
}, 20000)
})
.catch(error => ai.props.onError(error))
}, 10000)
}
/**
* Fetches the contents of a lobby and returns them as an EdgeLobby API.
*/
export async function makeLobbyApi(
ai,
accountId,
lobbyId
) {
// Look up the lobby on the server:
const lobbyJson = await fetchLobbyRequest(ai, lobbyId)
// If the lobby has a login request, set up that API:
let loginRequest
if (lobbyJson.loginRequest != null) {
loginRequest = await unpackLoginRequest(
ai,
accountId,
lobbyId,
lobbyJson,
lobbyJson.loginRequest.appId
)
}
const out = {
loginRequest
}
bridgifyObject(out)
return out
}
async function unpackLoginRequest(
ai,
accountId,
lobbyId,
lobbyJson,
appId
) {
// For crash errors:
ai.props.log.breadcrumb('unpackLoginRequest', {})
const info = await fetchAppIdInfo(ai, appId)
// Make the API:
const out = {
appId,
displayName: info.appName,
displayImageDarkUrl: info.darkImageUrl,
displayImageLightUrl: info.lightImageUrl,
approve() {
return approveLoginRequest(ai, accountId, appId, lobbyId, lobbyJson)
},
async close() {
close(out)
}
}
bridgifyObject(out)
return out
}