feat: Added tar archive support fix: Downloaded games and emulator execute permission now updated fix: Fixed rclone for linux fix: on screen keyaboard only now shows up when using a gamepad or touch
205 lines
No EOL
7.8 KiB
TypeScript
205 lines
No EOL
7.8 KiB
TypeScript
import Elysia, { status } from "elysia";
|
|
import { config, events, plugins, taskQueue } from "./app";
|
|
import z from "zod";
|
|
import { getCurrentUserApiUsersMeGet, tokenApiTokenPost, UserSchema } from "@clients/romm";
|
|
import secrets from '../api/secrets';
|
|
import { LoginJob } from "./jobs/login-job";
|
|
import TwitchLoginJob from "./jobs/twitch-login-job";
|
|
|
|
export default new Elysia()
|
|
.post('/login/twitch', async ({ body: { openInBrowser } }) =>
|
|
{
|
|
if (taskQueue.hasActiveOfType(TwitchLoginJob))
|
|
{
|
|
return status("Conflict", `Twitch Authentication already in progress`);
|
|
}
|
|
|
|
if (!process.env.TWITCH_CLIENT_ID)
|
|
{
|
|
return status("Not Found", "Twitch Client ID not set");
|
|
}
|
|
|
|
return taskQueue.enqueue(TwitchLoginJob.id, new TwitchLoginJob(process.env.TWITCH_CLIENT_ID, openInBrowser ?? false));
|
|
},
|
|
{ body: z.object({ openInBrowser: z.boolean().optional() }) })
|
|
.post('/logout/twitch', async () =>
|
|
{
|
|
if (!process.env.TWITCH_CLIENT_ID)
|
|
{
|
|
return status("Not Found", "Twitch Client ID not set");
|
|
}
|
|
|
|
const res = await fetch('https://id.twitch.tv/oauth2/revoke', {
|
|
method: "POST", headers: {
|
|
"Content-Type": "application/x-www-form-urlencoded"
|
|
},
|
|
body: new URLSearchParams({
|
|
client_id: process.env.TWITCH_CLIENT_ID
|
|
})
|
|
});
|
|
|
|
await secrets.delete({ service: 'gamflow_twitch', name: 'access_token' });
|
|
await secrets.delete({ service: 'gamflow_twitch', name: 'refresh_token' });
|
|
await secrets.delete({ service: 'gamflow_twitch', name: 'expires_in' });
|
|
|
|
await plugins.hooks.auth.loginComplete.promise({ service: 'twitch' });
|
|
|
|
return status(res.status, res.statusText);
|
|
})
|
|
.get('/login/twitch', checkLoginAndRefreshTwitch)
|
|
.post('/login/romm/qr', async () =>
|
|
{
|
|
if (taskQueue.hasActiveOfType(LoginJob))
|
|
{
|
|
return status("Conflict", "Login Already Active");
|
|
}
|
|
|
|
return taskQueue.enqueue(LoginJob.id, new LoginJob());
|
|
})
|
|
.get('/user/romm', async () =>
|
|
{
|
|
const data = await getCurrentUserApiUsersMeGet();
|
|
if (data.error) return status("Internal Server Error", data.response.statusText);
|
|
return data.data as UserSchema;
|
|
})
|
|
.post('/login/romm', async ({ body }) => tryLoginAndSave(body), { body: z.object({ host: z.url(), username: z.string(), password: z.string() }) })
|
|
.get('/login/romm', checkLoginAndRefreshRomm,
|
|
{ response: z.object({ hasLogin: z.boolean() }) })
|
|
.post('/logout/romm', async () =>
|
|
{
|
|
await secrets.delete({ service: 'gameflow', name: 'romm_access_token' });
|
|
await secrets.delete({ service: 'gameflow', name: 'romm_refresh_token' });
|
|
await secrets.delete({ service: 'gameflow', name: 'romm_expires_in' });
|
|
return status(200);
|
|
}, { response: z.any() });
|
|
|
|
|
|
export async function checkLoginAndRefreshTwitch ()
|
|
{
|
|
const access_token = await secrets.get({ service: 'gamflow_twitch', name: 'access_token' });
|
|
if (!access_token)
|
|
{
|
|
return status('Not Found', "Not Logged In");
|
|
}
|
|
|
|
const res = await fetch('https://id.twitch.tv/oauth2/validate', { headers: { Authorization: `OAuth ${access_token}` } });
|
|
if (res.ok)
|
|
{
|
|
return await res.json() as { login: string, expires_in: number; client_id: string, user_id: string; };
|
|
}
|
|
|
|
if (!process.env.TWITCH_CLIENT_ID)
|
|
{
|
|
return status("Not Found", "Twitch Client ID not set");
|
|
}
|
|
|
|
const refresh_token = await secrets.get({ service: 'gamflow_twitch', name: "refresh_token" });
|
|
if (!refresh_token)
|
|
{
|
|
return status("Not Found", "Refresh Token Not Found");
|
|
}
|
|
|
|
// refresh token
|
|
const refreshResponse = await fetch('https://id.twitch.tv/oauth2/token', {
|
|
method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({
|
|
client_id: process.env.TWITCH_CLIENT_ID,
|
|
access_token,
|
|
grant_type: "refresh_token",
|
|
refresh_token
|
|
})
|
|
});
|
|
|
|
if (refreshResponse.ok)
|
|
{
|
|
const data: {
|
|
access_token: string,
|
|
refresh_token: string,
|
|
token_type: string;
|
|
expires_in: number;
|
|
} = await refreshResponse.json();
|
|
|
|
await secrets.set({ service: 'gamflow_twitch', name: 'access_token', value: data.access_token });
|
|
await secrets.set({ service: 'gamflow_twitch', name: 'refresh_token', value: data.refresh_token });
|
|
await secrets.set({ service: 'gamflow_twitch', name: 'expires_in', value: new Date(new Date().getTime() + data.expires_in).toString() });
|
|
|
|
await plugins.hooks.auth.loginComplete.promise({ service: 'twitch' });
|
|
|
|
events.emit('notification', { message: "Twitch Refresh Successful", type: 'success' });
|
|
|
|
const res = await fetch('https://id.twitch.tv/oauth2/validate', { headers: { Authorization: `OAuth ${data.access_token}` } });
|
|
if (res.ok)
|
|
{
|
|
return await res.json() as { login: string, expires_in: number; client_id: string, user_id: string; };
|
|
}
|
|
}
|
|
|
|
return status(400, res.statusText);
|
|
}
|
|
|
|
export async function checkLoginAndRefreshRomm ()
|
|
{
|
|
const access_token = await secrets.get({ service: 'gameflow', name: 'romm_access_token' });
|
|
if (!access_token)
|
|
{
|
|
return { hasLogin: false };
|
|
}
|
|
|
|
const expires_in = await secrets.get({ service: 'gameflow', name: "romm_expires_in" });
|
|
if (expires_in)
|
|
{
|
|
const date = new Date(expires_in);
|
|
if (date > new Date())
|
|
{
|
|
return { hasLogin: true };
|
|
}
|
|
}
|
|
|
|
const refresh_token = await secrets.get({ service: 'gameflow', name: "romm_refresh_token" });
|
|
if (!refresh_token)
|
|
{
|
|
return { hasLogin: false };
|
|
}
|
|
|
|
const refreshResponse = await tokenApiTokenPost({ body: { grant_type: "refresh_token", refresh_token: refresh_token } });
|
|
|
|
if (refreshResponse.response.ok && refreshResponse.data)
|
|
{
|
|
await secrets.set({ service: 'gameflow', name: 'romm_access_token', value: refreshResponse.data.access_token });
|
|
if (refreshResponse.data.refresh_token)
|
|
await secrets.set({ service: 'gameflow', name: 'romm_refresh_token', value: refreshResponse.data.refresh_token });
|
|
await secrets.set({ service: 'gameflow', name: 'romm_expires_in', value: new Date(new Date().getTime() + refreshResponse.data.expires * 1000).toString() });
|
|
|
|
await plugins.hooks.auth.loginComplete.promise({ service: 'romm' });
|
|
|
|
events.emit('notification', { message: "Romm Refresh Successful", type: 'success' });
|
|
return { hasLogin: true };
|
|
}
|
|
|
|
return status(refreshResponse.response.status, refreshResponse.response.statusText) as any;
|
|
}
|
|
|
|
export async function tryLoginAndSave ({ host, username, password }: { host: string, username: string, password: string; })
|
|
{
|
|
const response = await tokenApiTokenPost({
|
|
body: {
|
|
password,
|
|
username,
|
|
scope: 'me.read roms.read platforms.read assets.read assets.write firmware.read roms.user.read collections.read me.write roms.user.write'
|
|
}, baseUrl: host
|
|
});
|
|
|
|
if (response.response.ok && response.data)
|
|
{
|
|
await secrets.set({ service: 'gameflow', name: 'romm_access_token', value: response.data.access_token });
|
|
await secrets.set({ service: 'gameflow', name: 'romm_expires_in', value: new Date(new Date().getTime() + response.data.expires * 1000).toString() });
|
|
if (response.data.refresh_token)
|
|
{
|
|
await secrets.set({ service: 'gameflow', name: 'romm_refresh_token', value: response.data.refresh_token });
|
|
}
|
|
|
|
config.set('rommAddress', host);
|
|
await plugins.hooks.auth.loginComplete.promise({ service: 'twitch' });
|
|
}
|
|
|
|
return response;
|
|
} |