mirror of
https://github.com/Adamcake/Bolt.git
synced 2026-05-19 06:34:30 -04:00
app: remove variables from store, reorganize logic
This commit is contained in:
@@ -5,9 +5,10 @@
|
||||
import PluginMenu from '$lib/Components/PluginMenu.svelte';
|
||||
import LogView from '$lib/Components/LogView.svelte';
|
||||
import { logger } from '$lib/Util/Logger';
|
||||
import { config, showDisclaimer } from '$lib/Util/store';
|
||||
import { showDisclaimer } from '$lib/Util/store';
|
||||
import DisclaimerModal from '$lib/Components/DisclaimerModal.svelte';
|
||||
import { BoltService } from '$lib/Services/BoltService';
|
||||
import { config } from '$lib/State/Config';
|
||||
|
||||
let showPluginMenu: boolean = false;
|
||||
let authorizing: boolean = false;
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { accountList, config, credentials, isConfigDirty, selectedPlay } from '$lib/Util/store';
|
||||
import { accountList, selectedPlay } from '$lib/Util/store';
|
||||
import { revokeOauthCreds } from '$lib/Util/functions';
|
||||
import { logger } from '$lib/Util/Logger';
|
||||
import { BoltService } from '$lib/Services/BoltService';
|
||||
import { AuthService, type Credentials } from '$lib/Services/AuthService';
|
||||
import { AuthService, type Session } from '$lib/Services/AuthService';
|
||||
import { bolt } from '$lib/State/Bolt';
|
||||
import { config } from '$lib/State/Config';
|
||||
|
||||
// values gather from s()
|
||||
const sOrigin = BoltService.bolt.origin;
|
||||
const clientId = BoltService.bolt.clientid;
|
||||
const sOrigin = bolt.env.origin;
|
||||
const clientId = bolt.env.clientid;
|
||||
const exchangeUrl = sOrigin.concat('/oauth2/token');
|
||||
const revokeUrl = sOrigin.concat('/oauth2/revoke');
|
||||
|
||||
@@ -21,7 +23,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
let creds: Credentials | undefined = $selectedPlay.credentials;
|
||||
let creds: Session | undefined = $selectedPlay.credentials;
|
||||
if ($selectedPlay.account) {
|
||||
$accountList.delete($selectedPlay.account?.userId);
|
||||
$accountList = $accountList; // certain data structure methods won't trigger an update, so we force one manually
|
||||
@@ -47,7 +49,7 @@
|
||||
revokeOauthCreds(creds!.access_token, revokeUrl, clientId).then((res: unknown) => {
|
||||
if (res === 200) {
|
||||
logger.info('Successful logout');
|
||||
removeLogin(<Credentials>creds);
|
||||
removeLogin(<Session>creds);
|
||||
} else {
|
||||
logger.error(`Logout unsuccessful: status ${res}`);
|
||||
}
|
||||
@@ -62,23 +64,22 @@
|
||||
}
|
||||
|
||||
// clear credentials when logout is clicked
|
||||
function removeLogin(creds: Credentials): void {
|
||||
$credentials.delete(creds.sub);
|
||||
BoltService.saveAllCreds();
|
||||
function removeLogin(creds: Session): void {
|
||||
const index = bolt.sessions.findIndex((session) => session.sub === creds.sub);
|
||||
if (index > -1) {
|
||||
bolt.sessions.splice(index, 1);
|
||||
BoltService.saveAllCreds();
|
||||
}
|
||||
}
|
||||
|
||||
// updated active account in selected_play store
|
||||
function accountChanged(): void {
|
||||
isConfigDirty.set(true);
|
||||
|
||||
const key: string = <string>accountSelect[accountSelect.selectedIndex].getAttribute('data-id');
|
||||
$selectedPlay.account = $accountList.get(key);
|
||||
$config.selected_account = key;
|
||||
$selectedPlay.credentials = $credentials.get(<string>$selectedPlay.account?.userId);
|
||||
// Unsure what the equivalent of this is with the refector
|
||||
//$selectedPlay.credentials = $credentials.get(<string>$selectedPlay.account?.userId);
|
||||
|
||||
// state updates can be weird, for some reason, changing the 'options' under the character_select
|
||||
// does not trigger the 'on:change', so we force update it.
|
||||
// I dislike this bit of code but couldn't think of another way to solve the problem other than props from the parent
|
||||
if ($selectedPlay.account && $selectedPlay.account.characters) {
|
||||
const char_select: HTMLSelectElement = <HTMLSelectElement>(
|
||||
document.getElementById('character_select')
|
||||
@@ -99,7 +100,8 @@
|
||||
index++;
|
||||
});
|
||||
|
||||
$selectedPlay.credentials = $credentials.get(<string>$selectedPlay.account?.userId);
|
||||
// Unsure what the equivalent of this is with the refactor
|
||||
//$selectedPlay.credentials = $credentials.get(<string>$selectedPlay.account?.userId);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -120,7 +122,7 @@
|
||||
<button
|
||||
class="mx-auto mr-2 rounded-lg bg-blue-500 p-2 font-bold text-black duration-200 hover:opacity-75"
|
||||
on:click={() => {
|
||||
const { origin, redirect, clientid } = BoltService.bolt;
|
||||
const { origin, redirect, clientid } = bolt.env;
|
||||
AuthService.openLoginWindow(origin, redirect, clientid);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script lang="ts">
|
||||
import Modal from '$lib/Components/CommonUI/Modal.svelte';
|
||||
import { credentials } from '$lib/Util/store';
|
||||
import { bolt } from '$lib/State/Bolt';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let modal: Modal;
|
||||
|
||||
onMount(() => {
|
||||
if ($credentials.size == 0) {
|
||||
if (bolt.sessions.length == 0) {
|
||||
modal.open();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { afterUpdate, onMount } from 'svelte';
|
||||
import { get } from 'svelte/store';
|
||||
import { launchHdos, launchRS3Linux, launchRuneLite } from '$lib/Util/functions';
|
||||
import { Client, Game } from '$lib/Util/interfaces';
|
||||
import { config, hasBoltPlugins, isConfigDirty, selectedPlay } from '$lib/Util/store';
|
||||
import { selectedPlay } from '$lib/Util/store';
|
||||
import { logger } from '$lib/Util/Logger';
|
||||
import { bolt } from '$lib/State/Bolt';
|
||||
import { config } from '$lib/State/Config';
|
||||
|
||||
export let showPluginMenu = false;
|
||||
|
||||
@@ -25,7 +26,6 @@
|
||||
$selectedPlay.character?.accountId
|
||||
);
|
||||
}
|
||||
$isConfigDirty = true;
|
||||
}
|
||||
|
||||
// update selected_play
|
||||
@@ -37,7 +37,6 @@
|
||||
$selectedPlay.client = Client.hdos;
|
||||
$config.selected_client_index = Client.hdos;
|
||||
}
|
||||
$isConfigDirty = true;
|
||||
}
|
||||
|
||||
// when play is clicked, check the selected_play store for all relevant details
|
||||
@@ -128,11 +127,11 @@
|
||||
</select>
|
||||
{:else if $selectedPlay.game == Game.rs3}
|
||||
<button
|
||||
disabled={!get(hasBoltPlugins)}
|
||||
title={get(hasBoltPlugins) ? null : 'Coming soon...'}
|
||||
disabled={!bolt.hasBoltPlugins}
|
||||
title={bolt.hasBoltPlugins ? null : 'Coming soon...'}
|
||||
class="mx-auto mb-2 w-52 rounded-lg p-2 font-bold text-black duration-200 enabled:bg-blue-500 enabled:hover:opacity-75 disabled:bg-gray-500"
|
||||
on:click={() => {
|
||||
showPluginMenu = get(hasBoltPlugins) ?? false;
|
||||
showPluginMenu = bolt.hasBoltPlugins ?? false;
|
||||
}}
|
||||
>
|
||||
Plugin menu
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { get } from 'svelte/store';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { getNewClientListPromise, savePluginConfig } from '$lib/Util/functions';
|
||||
import { type PluginConfig } from '$lib/Util/interfaces';
|
||||
import { clientListPromise, hasBoltPlugins, pluginList, platform } from '$lib/Util/store';
|
||||
import { clientListPromise } from '$lib/Util/store';
|
||||
import { logger } from '$lib/Util/Logger';
|
||||
import Modal from '$lib/Components/CommonUI/Modal.svelte';
|
||||
import { bolt } from '$lib/State/Bolt';
|
||||
|
||||
// props
|
||||
export let showPluginMenu: boolean;
|
||||
@@ -29,7 +29,7 @@
|
||||
};
|
||||
|
||||
const getPluginConfigPromiseFromID = (id: string): Promise<PluginConfig> | null => {
|
||||
const list = get(pluginList);
|
||||
const list = bolt.pluginList;
|
||||
const meta = list[id];
|
||||
if (!meta) return null;
|
||||
const path = meta.path;
|
||||
@@ -44,8 +44,8 @@
|
||||
.then((plugin: PluginConfig) => {
|
||||
do {
|
||||
selectedPlugin = crypto.randomUUID();
|
||||
} while (Object.keys(get(pluginList)).includes(selectedPlugin));
|
||||
$pluginList[selectedPlugin] = {
|
||||
} while (Object.keys(bolt.pluginList).includes(selectedPlugin));
|
||||
bolt.pluginList[selectedPlugin] = {
|
||||
name: plugin.name ?? unnamedPluginName,
|
||||
path: folderPath
|
||||
};
|
||||
@@ -67,7 +67,7 @@
|
||||
// if the user closes the file picker without selecting a file, status here is 204
|
||||
if (xml.status == 200) {
|
||||
const path: string =
|
||||
get(platform) === 'windows' ? xml.responseText.replaceAll('\\', '/') : xml.responseText;
|
||||
bolt.platform === 'windows' ? xml.responseText.replaceAll('\\', '/') : xml.responseText;
|
||||
if (path.endsWith('/bolt.json')) {
|
||||
const subpath: string = path.substring(0, path.length - 9);
|
||||
handleNewPlugin(subpath, path);
|
||||
@@ -176,12 +176,12 @@
|
||||
{/await}
|
||||
</div>
|
||||
<div class="h-full pt-10">
|
||||
{#if hasBoltPlugins}
|
||||
{#if bolt.hasBoltPlugins}
|
||||
<select
|
||||
bind:value={selectedPlugin}
|
||||
class="mx-auto mb-4 w-[min(280px,_45%)] cursor-pointer rounded-lg border-2 border-slate-300 bg-inherit p-2 text-inherit duration-200 hover:opacity-75 dark:border-slate-800"
|
||||
>
|
||||
{#each Object.entries($pluginList) as [id, plugin]}
|
||||
{#each Object.entries(bolt.pluginList) as [id, plugin]}
|
||||
<option class="dark:bg-slate-900" value={id}>{plugin.name ?? unnamedPluginName}</option>
|
||||
{/each}
|
||||
</select>
|
||||
@@ -194,8 +194,8 @@
|
||||
+
|
||||
</button>
|
||||
<br />
|
||||
{#if Object.entries($pluginList).length !== 0}
|
||||
{#if Object.keys(get(pluginList)).includes(selectedPlugin) && managementPluginPromise !== null}
|
||||
{#if Object.entries(bolt.pluginList).length !== 0}
|
||||
{#if Object.keys(bolt.pluginList).includes(selectedPlugin) && managementPluginPromise !== null}
|
||||
{#await managementPluginPromise}
|
||||
<p>loading...</p>
|
||||
{:then plugin}
|
||||
@@ -215,9 +215,9 @@
|
||||
on:click={() => {
|
||||
managementPluginPromise = null;
|
||||
pluginConfigDirty = true;
|
||||
let list = get(pluginList);
|
||||
let list = bolt.pluginList;
|
||||
delete list[selectedPlugin];
|
||||
pluginList.set(list);
|
||||
bolt.pluginList = list;
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
@@ -241,15 +241,15 @@
|
||||
{#await managementPluginPromise}
|
||||
<p>loading...</p>
|
||||
{:then plugin}
|
||||
{#if plugin && plugin.main && Object.keys($pluginList).includes(selectedPlugin)}
|
||||
{#if $pluginList[selectedPlugin].path}
|
||||
{#if plugin && plugin.main && Object.keys(bolt.pluginList).includes(selectedPlugin)}
|
||||
{#if bolt.pluginList[selectedPlugin].path}
|
||||
<button
|
||||
class="mx-auto mb-1 w-auto rounded-lg bg-emerald-500 p-2 font-bold text-black duration-200 hover:opacity-75"
|
||||
on:click={() =>
|
||||
startPlugin(
|
||||
selectedClientId,
|
||||
selectedPlugin,
|
||||
$pluginList[selectedPlugin].path ?? '',
|
||||
bolt.pluginList[selectedPlugin].path ?? '',
|
||||
plugin.main ?? ''
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { config, isConfigDirty, platform, selectedPlay } from '$lib/Util/store';
|
||||
import { selectedPlay } from '$lib/Util/store';
|
||||
import { launchRuneLiteConfigure } from '$lib/Util/functions';
|
||||
import { logger } from '$lib/Util/Logger';
|
||||
import { Platform, bolt } from '$lib/State/Bolt';
|
||||
import { config } from '$lib/State/Config';
|
||||
|
||||
let customJarDiv: HTMLDivElement;
|
||||
let customJarFile: HTMLTextAreaElement;
|
||||
@@ -25,12 +27,10 @@
|
||||
$config.runelite_custom_jar = '';
|
||||
$config.runelite_use_custom_jar = false;
|
||||
}
|
||||
$isConfigDirty = true;
|
||||
}
|
||||
|
||||
function textChanged(): void {
|
||||
$config.runelite_custom_jar = customJarFile.value;
|
||||
$isConfigDirty = true;
|
||||
}
|
||||
|
||||
function selectFile(): void {
|
||||
@@ -47,7 +47,6 @@
|
||||
if (xml.status == 200) {
|
||||
customJarFile.value = xml.responseText;
|
||||
$config.runelite_custom_jar = xml.responseText;
|
||||
$isConfigDirty = true;
|
||||
}
|
||||
customJarFileButton.disabled = false;
|
||||
useJar.disabled = false;
|
||||
@@ -83,7 +82,7 @@
|
||||
$config.runelite_use_custom_jar = false;
|
||||
}
|
||||
|
||||
if ($platform !== 'linux') {
|
||||
if (bolt.platform !== Platform.Linux) {
|
||||
flatpakDiv.remove();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { bolt, config, isConfigDirty, hasBoltPlugins } from '$lib/Util/store';
|
||||
import { bolt } from '$lib/State/Bolt';
|
||||
import { config } from '$lib/State/Config';
|
||||
|
||||
let configUriDiv: HTMLDivElement;
|
||||
let configUriAddress: HTMLTextAreaElement;
|
||||
@@ -11,17 +12,15 @@
|
||||
function toggleUriDiv(): void {
|
||||
configUriDiv.classList.toggle('opacity-25');
|
||||
configUriAddress.disabled = !configUriAddress.disabled;
|
||||
$isConfigDirty = true;
|
||||
|
||||
if (!useUri.checked) {
|
||||
configUriAddress.value = atob($bolt.default_config_uri);
|
||||
configUriAddress.value = atob(bolt.env.default_config_uri);
|
||||
$config.rs_config_uri = '';
|
||||
}
|
||||
}
|
||||
|
||||
function uriAddressChanged(): void {
|
||||
$config.rs_config_uri = configUriAddress.value;
|
||||
$isConfigDirty = true;
|
||||
}
|
||||
|
||||
// loads configs for menu
|
||||
@@ -31,13 +30,13 @@
|
||||
useUri.checked = true;
|
||||
toggleUriDiv();
|
||||
} else {
|
||||
configUriAddress.value = atob($bolt.default_config_uri);
|
||||
configUriAddress.value = atob(bolt.env.default_config_uri);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="rs3_options" class="col-span-3 p-5 pt-10">
|
||||
{#if hasBoltPlugins}
|
||||
{#if bolt.hasBoltPlugins}
|
||||
<div class="mx-auto p-2">
|
||||
<label for="enable_plugins">Enable Bolt plugin loader: </label>
|
||||
<input
|
||||
@@ -45,7 +44,6 @@
|
||||
name="enable_plugins"
|
||||
id="enable_plugins"
|
||||
bind:checked={$config.rs_plugin_loader}
|
||||
on:change={() => isConfigDirty.set(true)}
|
||||
class="ml-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { Game } from '$lib/Util/interfaces';
|
||||
import { config, isConfigDirty, selectedPlay } from '$lib/Util/store';
|
||||
import { selectedPlay } from '$lib/Util/store';
|
||||
import SettingsModal from '$lib/Components/SettingsModal.svelte';
|
||||
import Dropdown from '$lib/Components/CommonUI/Dropdown.svelte';
|
||||
import Account from '$lib/Components/Account.svelte';
|
||||
import { BoltService } from '$lib/Services/BoltService';
|
||||
import { AuthService } from '$lib/Services/AuthService';
|
||||
import { bolt } from '$lib/State/Bolt';
|
||||
import { config } from '$lib/State/Config';
|
||||
|
||||
let settingsModal: SettingsModal;
|
||||
let rs3Button: HTMLButtonElement;
|
||||
@@ -19,14 +20,12 @@
|
||||
$selectedPlay.game = Game.osrs;
|
||||
$selectedPlay.client = $config.selected_client_index;
|
||||
$config.selected_game_index = Game.osrs;
|
||||
$isConfigDirty = true;
|
||||
osrsButton.classList.add('bg-blue-500', 'text-black');
|
||||
rs3Button.classList.remove('bg-blue-500', 'text-black');
|
||||
break;
|
||||
case Game.rs3:
|
||||
$selectedPlay.game = Game.rs3;
|
||||
$config.selected_game_index = Game.rs3;
|
||||
$isConfigDirty = true;
|
||||
osrsButton.classList.remove('bg-blue-500', 'text-black');
|
||||
rs3Button.classList.add('bg-blue-500', 'text-black');
|
||||
break;
|
||||
@@ -92,7 +91,7 @@
|
||||
<button
|
||||
class="h-11 w-48 rounded-lg border-2 border-slate-300 bg-inherit p-2 text-center font-bold text-black duration-200 hover:opacity-75 dark:border-slate-800 dark:text-slate-50"
|
||||
on:click={() => {
|
||||
const { origin, redirect, clientid } = BoltService.bolt;
|
||||
const { origin, redirect, clientid } = bolt.env;
|
||||
AuthService.openLoginWindow(origin, redirect, clientid);
|
||||
}}
|
||||
>
|
||||
|
||||
57
app/src/lib/Services/ApiService.ts
Normal file
57
app/src/lib/Services/ApiService.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
const BASE_URL = 'bolt-internal';
|
||||
|
||||
export class ApiService {
|
||||
static async get(route: string, params?: URLSearchParams): Promise<Response> {
|
||||
let parsedParams = '';
|
||||
if (params instanceof URLSearchParams) {
|
||||
parsedParams += '?' + params.toString();
|
||||
}
|
||||
const url = `${BASE_URL}/${route + parsedParams}`;
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
static async post(route: string, params: unknown): Promise<Response> {
|
||||
const url = `${BASE_URL}/${route}`;
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
static async put(route: string, body: unknown): Promise<Response> {
|
||||
const url = `${BASE_URL}/${route}`;
|
||||
const response = await fetch(url, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
static async delete(route: string): Promise<Response> {
|
||||
const url = `${BASE_URL}/${route}`;
|
||||
const response = await fetch(url, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
import { BoltService } from '$lib/Services/BoltService';
|
||||
import { bolt } from '$lib/State/Bolt';
|
||||
import { ParseUtils } from '$lib/Util/ParseUtils';
|
||||
import { StringUtils } from '$lib/Util/StringUtils';
|
||||
import { unwrap, type Account } from '$lib/Util/interfaces';
|
||||
|
||||
// credential type, passed around often
|
||||
export interface Credentials {
|
||||
export interface Session {
|
||||
access_token: string;
|
||||
id_token: string;
|
||||
refresh_token: string;
|
||||
@@ -23,7 +22,7 @@ interface PendingLogin {
|
||||
export interface Auth {
|
||||
state?: string;
|
||||
nonce?: string;
|
||||
creds?: Credentials;
|
||||
creds?: Session;
|
||||
win?: Window | null;
|
||||
account_info_promise?: Promise<Account>;
|
||||
verifier?: string;
|
||||
@@ -59,9 +58,9 @@ export class AuthService {
|
||||
|
||||
// makes a request to the account_info endpoint and returns the promise
|
||||
// the promise will return either a JSON object on success or a status code on failure
|
||||
static getStandardAccountInfo(creds: Credentials): Promise<Account | number> {
|
||||
static getStandardAccountInfo(creds: Session): Promise<Account | number> {
|
||||
return new Promise((resolve) => {
|
||||
const url = `${BoltService.bolt.api}/users/${creds.sub}/displayName`;
|
||||
const url = `${bolt.env.api}/users/${creds.sub}/displayName`;
|
||||
const xml = new XMLHttpRequest();
|
||||
xml.onreadystatechange = () => {
|
||||
if (xml.readyState == 4) {
|
||||
@@ -82,7 +81,7 @@ export class AuthService {
|
||||
// and renews them using the oauth endpoint if so.
|
||||
// Does not save credentials but sets credentials_are_dirty as appropriate.
|
||||
// Returns null on success or an http status code on failure
|
||||
static async checkRenewCreds(creds: Credentials, url: string, clientId: string) {
|
||||
static async checkRenewCreds(creds: Session, url: string, clientId: string) {
|
||||
return new Promise((resolve) => {
|
||||
// only renew if less than 30 seconds left
|
||||
if (creds.expiry - Date.now() < 30000) {
|
||||
|
||||
@@ -1,44 +1,37 @@
|
||||
import type { Credentials } from '$lib/Services/AuthService';
|
||||
import { configHasPendingChanges, type Config } from '$lib/State/Config';
|
||||
import { bolt } from '$lib/State/Bolt';
|
||||
import { logger } from '$lib/Util/Logger';
|
||||
import type { Bolt, Config } from '$lib/Util/interfaces';
|
||||
import { credentials, isConfigDirty } from '$lib/Util/store';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
let saveConfigInProgress: boolean = false;
|
||||
let saveInProgress: boolean = false;
|
||||
|
||||
export class BoltService {
|
||||
static bolt: Bolt;
|
||||
|
||||
// sends an asynchronous request to save the current user config to disk, if it has changed
|
||||
static saveConfig(configToSave: Config) {
|
||||
if (get(isConfigDirty) && !saveConfigInProgress) {
|
||||
saveConfigInProgress = true;
|
||||
const xml = new XMLHttpRequest();
|
||||
xml.open('POST', '/save-config', true);
|
||||
xml.onreadystatechange = () => {
|
||||
if (xml.readyState == 4) {
|
||||
logger.info(`Save config status: '${xml.responseText.trim()}'`);
|
||||
if (xml.status == 200) {
|
||||
isConfigDirty.set(false);
|
||||
}
|
||||
saveConfigInProgress = false;
|
||||
}
|
||||
};
|
||||
xml.setRequestHeader('Content-Type', 'application/json');
|
||||
if (!configHasPendingChanges || saveInProgress) return;
|
||||
|
||||
// converting map into something that is compatible for JSON.stringify
|
||||
// maybe the map should be converted into a Record<string, string>
|
||||
// but this has other problems and implications
|
||||
const characters: Record<string, string> = {};
|
||||
configToSave.selected_characters?.forEach((value, key) => {
|
||||
characters[key] = value;
|
||||
});
|
||||
const object: Record<string, unknown> = {};
|
||||
Object.assign(object, configToSave);
|
||||
object.selected_characters = characters;
|
||||
const json = JSON.stringify(object, null, 4);
|
||||
xml.send(json);
|
||||
}
|
||||
saveInProgress = true;
|
||||
const xml = new XMLHttpRequest();
|
||||
xml.open('POST', '/save-config', true);
|
||||
xml.onreadystatechange = () => {
|
||||
if (xml.readyState == 4) {
|
||||
logger.info(`Save config status: '${xml.responseText.trim()}'`);
|
||||
saveInProgress = false;
|
||||
}
|
||||
};
|
||||
xml.setRequestHeader('Content-Type', 'application/json');
|
||||
|
||||
// converting map into something that is compatible for JSON.stringify
|
||||
// maybe the map should be converted into a Record<string, string>
|
||||
// but this has other problems and implications
|
||||
const characters: Record<string, string> = {};
|
||||
configToSave.selected_characters?.forEach((value, key) => {
|
||||
characters[key] = value;
|
||||
});
|
||||
const object: Record<string, unknown> = {};
|
||||
Object.assign(object, configToSave);
|
||||
object.selected_characters = characters;
|
||||
const json = JSON.stringify(object, null, 4);
|
||||
xml.send(json);
|
||||
}
|
||||
|
||||
// sends a request to save all credentials to their config file,
|
||||
@@ -58,11 +51,8 @@ export class BoltService {
|
||||
// data.credentials = credentialsSub.get(<string>selectedPlaySub.account?.userId);
|
||||
// return data;
|
||||
// });
|
||||
console.log('saveAllCreds', bolt.sessions);
|
||||
|
||||
const credsList: Array<Credentials> = [];
|
||||
get(credentials).forEach((value) => {
|
||||
credsList.push(value);
|
||||
});
|
||||
xml.send(JSON.stringify(credsList));
|
||||
xml.send(JSON.stringify(bolt.sessions));
|
||||
}
|
||||
}
|
||||
|
||||
50
app/src/lib/State/Bolt.ts
Normal file
50
app/src/lib/State/Bolt.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import type { Session } from '$lib/Services/AuthService';
|
||||
import { ParseUtils } from '$lib/Util/ParseUtils';
|
||||
import type { PluginMeta } from '$lib/Util/interfaces';
|
||||
|
||||
export const enum Platform {
|
||||
Windows = 'windows',
|
||||
Linux = 'linux',
|
||||
MacOS = 'mac'
|
||||
}
|
||||
|
||||
export interface BoltEnv {
|
||||
provider: string;
|
||||
origin: string;
|
||||
origin_2fa: string;
|
||||
redirect: string;
|
||||
clientid: string;
|
||||
api: string;
|
||||
auth_api: string;
|
||||
profile_api: string;
|
||||
shield_url: string;
|
||||
content_url: string;
|
||||
default_config_uri: string;
|
||||
games: string[];
|
||||
}
|
||||
|
||||
export interface Bolt {
|
||||
env: BoltEnv;
|
||||
platform: Platform | null;
|
||||
rs3InstalledHash: string | null;
|
||||
runeLiteInstalledId: string | null;
|
||||
hdosInstalledVersion: string | null;
|
||||
isFlathub: boolean;
|
||||
hasBoltPlugins: boolean;
|
||||
pluginList: { [key: string]: PluginMeta }; // May need to be a writable
|
||||
sessions: Session[];
|
||||
}
|
||||
|
||||
declare const s: () => BoltEnv;
|
||||
|
||||
export const bolt: Bolt = {
|
||||
env: ParseUtils.decodeBolt(s()),
|
||||
platform: null,
|
||||
rs3InstalledHash: null,
|
||||
runeLiteInstalledId: null,
|
||||
hdosInstalledVersion: null,
|
||||
isFlathub: false,
|
||||
hasBoltPlugins: false,
|
||||
pluginList: {},
|
||||
sessions: []
|
||||
};
|
||||
46
app/src/lib/State/Config.ts
Normal file
46
app/src/lib/State/Config.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { logger } from '$lib/Util/Logger';
|
||||
import { onWritableChange } from '$lib/Util/onWritableChange';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export interface Config {
|
||||
use_dark_theme: boolean;
|
||||
rs_plugin_loader: boolean;
|
||||
flatpak_rich_presence: boolean;
|
||||
runelite_use_custom_jar: boolean;
|
||||
selected_game_index: number;
|
||||
selected_client_index: number;
|
||||
rs_config_uri?: string;
|
||||
runelite_custom_jar?: string;
|
||||
selected_account?: string;
|
||||
selected_characters?: Map<string, string>; // account userId, then character accountId
|
||||
selected_game_accounts?: Map<string, string>; // legacy version of selected_characters
|
||||
}
|
||||
|
||||
export let configHasPendingChanges = false;
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const configParam = params.get('config');
|
||||
let parsedConfig: Config = {
|
||||
use_dark_theme: true,
|
||||
rs_plugin_loader: false,
|
||||
flatpak_rich_presence: false,
|
||||
runelite_use_custom_jar: false,
|
||||
selected_characters: new Map(),
|
||||
selected_game_accounts: new Map(),
|
||||
selected_game_index: 1,
|
||||
selected_client_index: 1
|
||||
};
|
||||
|
||||
if (configParam) {
|
||||
try {
|
||||
parsedConfig = JSON.parse(configParam) as Config;
|
||||
} catch (e) {
|
||||
logger.error('Unable to parse config, restoring to default');
|
||||
}
|
||||
}
|
||||
|
||||
export const config = writable<Config>(parsedConfig);
|
||||
|
||||
onWritableChange(config, () => {
|
||||
configHasPendingChanges = true;
|
||||
});
|
||||
@@ -1,21 +1,12 @@
|
||||
import type { Credentials } from '$lib/Services/AuthService';
|
||||
import type { Session } from '$lib/Services/AuthService';
|
||||
import type { BoltEnv } from '$lib/State/Bolt';
|
||||
import { logger } from '$lib/Util/Logger';
|
||||
import type { Bolt, Result } from '$lib/Util/interfaces';
|
||||
import {
|
||||
config,
|
||||
credentials,
|
||||
hasBoltPlugins,
|
||||
hdosInstalledVersion,
|
||||
platform,
|
||||
pluginList,
|
||||
rs3InstalledHash,
|
||||
runeLiteInstalledId
|
||||
} from '$lib/Util/store';
|
||||
import type { Result } from '$lib/Util/interfaces';
|
||||
|
||||
export class ParseUtils {
|
||||
// parses a response from the oauth endpoint
|
||||
// returns a Result, it may
|
||||
static parseCredentials(str: string): Result<Credentials> {
|
||||
static parseCredentials(str: string): Result<Session> {
|
||||
const oauthCreds = JSON.parse(str);
|
||||
const sections = oauthCreds.id_token.split('.');
|
||||
if (sections.length !== 3) {
|
||||
@@ -44,64 +35,64 @@ export class ParseUtils {
|
||||
};
|
||||
}
|
||||
|
||||
static parseUrlParams(url: string) {
|
||||
const query = new URLSearchParams(url);
|
||||
platform.set(query.get('platform'));
|
||||
//isFlathub = query.get('flathub') === '1';
|
||||
rs3InstalledHash.set(query.get('rs3_linux_installed_hash'));
|
||||
runeLiteInstalledId.set(query.get('runelite_installed_id'));
|
||||
hdosInstalledVersion.set(query.get('hdos_installed_version'));
|
||||
const queryPlugins: string | null = query.get('plugins');
|
||||
if (queryPlugins !== null) {
|
||||
hasBoltPlugins.set(true);
|
||||
pluginList.set(JSON.parse(queryPlugins));
|
||||
} else {
|
||||
hasBoltPlugins.set(false);
|
||||
}
|
||||
// static parseUrlParams(url: string) {
|
||||
// const query = new URLSearchParams(url);
|
||||
// platform.set(query.get('platform'));
|
||||
// //isFlathub = query.get('flathub') === '1';
|
||||
// rs3InstalledHash.set(query.get('rs3_linux_installed_hash'));
|
||||
// runeLiteInstalledId.set(query.get('runelite_installed_id'));
|
||||
// hdosInstalledVersion.set(query.get('hdos_installed_version'));
|
||||
// const queryPlugins: string | null = query.get('plugins');
|
||||
// if (queryPlugins !== null) {
|
||||
// hasBoltPlugins.set(true);
|
||||
// pluginList.set(JSON.parse(queryPlugins));
|
||||
// } else {
|
||||
// hasBoltPlugins.set(false);
|
||||
// }
|
||||
|
||||
const creds = query.get('credentials');
|
||||
if (creds) {
|
||||
try {
|
||||
// no need to set credentials_are_dirty here because the contents came directly from the file
|
||||
const credsList: Array<Credentials> = JSON.parse(creds);
|
||||
credsList.forEach((value) => {
|
||||
credentials.update((data) => {
|
||||
data.set(value.sub, value);
|
||||
return data;
|
||||
});
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
logger.error(`Couldn't parse credentials file: ${error}`);
|
||||
}
|
||||
}
|
||||
const conf = query.get('config');
|
||||
if (conf) {
|
||||
try {
|
||||
// as above, no need to set configIsDirty
|
||||
const parsedConf = JSON.parse(conf);
|
||||
config.set(parsedConf);
|
||||
// convert parsed objects into Maps
|
||||
config.update((data) => {
|
||||
if (data.selected_game_accounts) {
|
||||
data.selected_characters = new Map(Object.entries(data.selected_game_accounts));
|
||||
delete data.selected_game_accounts;
|
||||
} else if (data.selected_characters) {
|
||||
data.selected_characters = new Map(Object.entries(data.selected_characters));
|
||||
}
|
||||
return data;
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
logger.error(`Couldn't parse config file: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
// const creds = query.get('credentials');
|
||||
// if (creds) {
|
||||
// try {
|
||||
// // no need to set credentials_are_dirty here because the contents came directly from the file
|
||||
// const credsList: Array<Session> = JSON.parse(creds);
|
||||
// credsList.forEach((value) => {
|
||||
// credentials.update((data) => {
|
||||
// data.set(value.sub, value);
|
||||
// return data;
|
||||
// });
|
||||
// });
|
||||
// } catch (error: unknown) {
|
||||
// logger.error(`Couldn't parse credentials file: ${error}`);
|
||||
// }
|
||||
// }
|
||||
// const conf = query.get('config');
|
||||
// if (conf) {
|
||||
// try {
|
||||
// // as above, no need to set configIsDirty
|
||||
// const parsedConf = JSON.parse(conf);
|
||||
// config.set(parsedConf);
|
||||
// // convert parsed objects into Maps
|
||||
// config.update((data) => {
|
||||
// if (data.selected_game_accounts) {
|
||||
// data.selected_characters = new Map(Object.entries(data.selected_game_accounts));
|
||||
// delete data.selected_game_accounts;
|
||||
// } else if (data.selected_characters) {
|
||||
// data.selected_characters = new Map(Object.entries(data.selected_characters));
|
||||
// }
|
||||
// return data;
|
||||
// });
|
||||
// } catch (error: unknown) {
|
||||
// logger.error(`Couldn't parse config file: ${error}`);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// The bolt object from the C++ app has its values base64 encoded.
|
||||
// This will decode each value, and return the original structure
|
||||
static decodeBolt(encoded: Bolt): Bolt {
|
||||
static decodeBolt(encoded: BoltEnv): BoltEnv {
|
||||
const decodedObject: Record<string, string | string[]> = {};
|
||||
for (const _key in encoded) {
|
||||
const key = _key as keyof Bolt;
|
||||
const key = _key as keyof BoltEnv;
|
||||
const value = encoded[key];
|
||||
if (typeof value === 'string') {
|
||||
decodedObject[key] = atob(value);
|
||||
@@ -112,6 +103,6 @@ export class ParseUtils {
|
||||
}
|
||||
}
|
||||
|
||||
return decodedObject as unknown as Bolt;
|
||||
return decodedObject as unknown as BoltEnv;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,12 @@
|
||||
import { get } from 'svelte/store';
|
||||
import { type Account, type Character, type GameClient } from '$lib/Util/interfaces';
|
||||
import {
|
||||
accountList,
|
||||
config,
|
||||
credentials,
|
||||
pluginList,
|
||||
hdosInstalledVersion,
|
||||
internalUrl,
|
||||
productionClientId,
|
||||
rs3InstalledHash,
|
||||
runeLiteInstalledId,
|
||||
selectedPlay
|
||||
} from '$lib/Util/store';
|
||||
import { accountList, internalUrl, productionClientId, selectedPlay } from '$lib/Util/store';
|
||||
import { logger } from '$lib/Util/Logger';
|
||||
import { BoltService } from '$lib/Services/BoltService';
|
||||
import { AuthService, type Credentials } from '$lib/Services/AuthService';
|
||||
import { AuthService, type Session } from '$lib/Services/AuthService';
|
||||
import { StringUtils } from '$lib/Util/StringUtils';
|
||||
import { bolt } from '$lib/State/Bolt';
|
||||
import { config } from '$lib/State/Config';
|
||||
|
||||
// deprecated?
|
||||
// const rs3_basic_auth = 'Basic Y29tX2phZ2V4X2F1dGhfZGVza3RvcF9yczpwdWJsaWM=';
|
||||
@@ -25,7 +16,7 @@ import { StringUtils } from '$lib/Util/StringUtils';
|
||||
// Handles a new session id as part of the login flow. Can also be called on startup with a
|
||||
// persisted session id.
|
||||
export async function handleNewSessionId(
|
||||
creds: Credentials,
|
||||
creds: Session,
|
||||
accountsUrl: string,
|
||||
accountsInfoPromise: Promise<Account>
|
||||
) {
|
||||
@@ -74,10 +65,10 @@ export async function handleNewSessionId(
|
||||
// called on new successful login with credentials. Delegates to a specific handler based on login_provider value.
|
||||
// however it does not save credentials. You should call saveAllCreds after calling this function any number of times.
|
||||
// Returns true if the credentials should be treated as valid by the caller immediately after return, or false if not.
|
||||
export async function handleLogin(win: Window | null, creds: Credentials) {
|
||||
export async function handleLogin(win: Window | null, creds: Session) {
|
||||
const state = StringUtils.makeRandomState();
|
||||
const nonce: string = crypto.randomUUID();
|
||||
const location = BoltService.bolt.origin.concat('/oauth2/auth?').concat(
|
||||
const location = bolt.env.origin.concat('/oauth2/auth?').concat(
|
||||
new URLSearchParams({
|
||||
id_token_hint: creds.id_token,
|
||||
nonce: btoa(nonce),
|
||||
@@ -109,7 +100,7 @@ export async function handleLogin(win: Window | null, creds: Credentials) {
|
||||
|
||||
return await handleNewSessionId(
|
||||
creds,
|
||||
BoltService.bolt.auth_api.concat('/accounts'),
|
||||
bolt.env.auth_api.concat('/accounts'),
|
||||
<Promise<Account>>accountInfoPromise
|
||||
);
|
||||
}
|
||||
@@ -122,7 +113,9 @@ export function addNewAccount(account: Account) {
|
||||
data.account = account;
|
||||
const [firstKey] = account.characters.keys();
|
||||
data.character = account.characters.get(firstKey);
|
||||
if (get(credentials).size > 0) data.credentials = get(credentials).get(account.userId);
|
||||
if (bolt.sessions.length > 0) {
|
||||
data.credentials = bolt.sessions.find((session) => session.sub === account.userId);
|
||||
}
|
||||
return data;
|
||||
});
|
||||
};
|
||||
@@ -132,6 +125,7 @@ export function addNewAccount(account: Account) {
|
||||
return data;
|
||||
});
|
||||
|
||||
const { config } = config;
|
||||
if (get(selectedPlay).account && get(config).selected_account) {
|
||||
if (account.userId == get(config).selected_account) {
|
||||
updateSelectedPlay();
|
||||
@@ -164,13 +158,14 @@ export function launchRS3Linux(
|
||||
jx_character_id: string,
|
||||
jx_display_name: string
|
||||
) {
|
||||
const { config } = config;
|
||||
BoltService.saveConfig(get(config));
|
||||
|
||||
const launch = (hash?: unknown, deb?: never) => {
|
||||
const launch = (hash?: string, deb?: never) => {
|
||||
const xml = new XMLHttpRequest();
|
||||
const params: Record<string, string> = {};
|
||||
const _config = get(config);
|
||||
if (hash) params.hash = <string>hash;
|
||||
if (hash) params.hash = hash;
|
||||
if (jx_session_id) params.jx_session_id = jx_session_id;
|
||||
if (jx_character_id) params.jx_character_id = jx_character_id;
|
||||
if (jx_display_name) params.jx_display_name = jx_display_name;
|
||||
@@ -178,14 +173,14 @@ export function launchRS3Linux(
|
||||
if (_config.rs_config_uri) {
|
||||
params.config_uri = _config.rs_config_uri;
|
||||
} else {
|
||||
params.config_uri = BoltService.bolt.default_config_uri;
|
||||
params.config_uri = bolt.env.default_config_uri;
|
||||
}
|
||||
xml.open('POST', '/launch-rs3-deb?'.concat(new URLSearchParams(params).toString()), true);
|
||||
xml.onreadystatechange = () => {
|
||||
if (xml.readyState == 4) {
|
||||
logger.info(`Game launch status: '${xml.responseText.trim()}'`);
|
||||
if (xml.status == 200 && hash) {
|
||||
rs3InstalledHash.set(<string>hash);
|
||||
bolt.rs3InstalledHash = hash;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -193,7 +188,7 @@ export function launchRS3Linux(
|
||||
};
|
||||
|
||||
const xml = new XMLHttpRequest();
|
||||
const contentUrl = BoltService.bolt.content_url;
|
||||
const contentUrl = bolt.env.content_url;
|
||||
const url = contentUrl.concat('dists/trusty/non-free/binary-amd64/Packages');
|
||||
xml.open('GET', url, true);
|
||||
xml.onreadystatechange = () => {
|
||||
@@ -208,7 +203,7 @@ export function launchRS3Linux(
|
||||
launch();
|
||||
return;
|
||||
}
|
||||
if (lines.SHA256 !== get(rs3InstalledHash)) {
|
||||
if (lines.SHA256 !== bolt.rs3InstalledHash) {
|
||||
logger.info('Downloading RS3 client...');
|
||||
const exeXml = new XMLHttpRequest();
|
||||
exeXml.open('GET', contentUrl.concat(lines.Filename), true);
|
||||
@@ -251,10 +246,11 @@ function launchRuneLiteInner(
|
||||
jx_display_name: string,
|
||||
configure: boolean
|
||||
) {
|
||||
const { config } = config;
|
||||
BoltService.saveConfig(get(config));
|
||||
const launchPath = configure ? '/launch-runelite-jar-configure?' : '/launch-runelite-jar?';
|
||||
|
||||
const launch = (id?: unknown, jar?: unknown, jarPath?: unknown) => {
|
||||
const launch = (id?: string | null, jar?: unknown, jarPath?: unknown) => {
|
||||
const xml = new XMLHttpRequest();
|
||||
const params: Record<string, string> = {};
|
||||
if (id) params.id = <string>id;
|
||||
@@ -268,7 +264,7 @@ function launchRuneLiteInner(
|
||||
if (xml.readyState == 4) {
|
||||
logger.info(`Game launch status: '${xml.responseText.trim()}'`);
|
||||
if (xml.status == 200 && id) {
|
||||
runeLiteInstalledId.set(<string>id);
|
||||
bolt.runeLiteInstalledId = id;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -290,7 +286,7 @@ function launchRuneLiteInner(
|
||||
.map((x: Record<string, string>) => x.assets)
|
||||
.flat()
|
||||
.find((x: Record<string, string>) => x.name.toLowerCase() == 'runelite.jar');
|
||||
if (runelite.id != get(runeLiteInstalledId)) {
|
||||
if (runelite.id != bolt.runeLiteInstalledId) {
|
||||
logger.info('Downloading RuneLite...');
|
||||
const xmlRl = new XMLHttpRequest();
|
||||
xmlRl.open('GET', runelite.browser_download_url, true);
|
||||
@@ -347,6 +343,7 @@ export function launchHdos(
|
||||
jx_character_id: string,
|
||||
jx_display_name: string
|
||||
) {
|
||||
const { config } = config;
|
||||
BoltService.saveConfig(get(config));
|
||||
|
||||
const launch = (version?: string, jar?: string) => {
|
||||
@@ -361,7 +358,7 @@ export function launchHdos(
|
||||
if (xml.readyState == 4) {
|
||||
logger.info(`Game launch status: '${xml.responseText.trim()}'`);
|
||||
if (xml.status == 200 && version) {
|
||||
hdosInstalledVersion.set(version);
|
||||
bolt.hdosInstalledVersion = version;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -379,7 +376,7 @@ export function launchHdos(
|
||||
);
|
||||
if (versionRegex && versionRegex.length >= 2) {
|
||||
const latestVersion = versionRegex[1];
|
||||
if (latestVersion !== get(hdosInstalledVersion)) {
|
||||
if (latestVersion !== bolt.hdosInstalledVersion) {
|
||||
const jarUrl = `https://cdn.hdos.dev/launcher/v${latestVersion}/hdos-launcher.jar`;
|
||||
logger.info('Downloading HDOS...');
|
||||
const xmlHdos = new XMLHttpRequest();
|
||||
@@ -455,5 +452,5 @@ export function savePluginConfig(): void {
|
||||
logger.info(`Save-plugin-config status: ${xml.responseText.trim()}`);
|
||||
}
|
||||
};
|
||||
xml.send(JSON.stringify(get(pluginList)));
|
||||
xml.send(JSON.stringify(bolt.pluginList));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// file for all interfaces, types, and their helper functions
|
||||
|
||||
import type { Credentials } from '$lib/Services/AuthService';
|
||||
import type { Session } from '$lib/Services/AuthService';
|
||||
|
||||
// result type, similar to rust's implementation
|
||||
// useful if a function may succeed or fail
|
||||
@@ -20,6 +20,14 @@ export function unwrap<T, E = Error>(result: Result<T, E>): T {
|
||||
}
|
||||
}
|
||||
|
||||
export function ok<T>(value: T): Result<T, never> {
|
||||
return { ok: true, value };
|
||||
}
|
||||
|
||||
export function error<T>(error: T): Result<never, T> {
|
||||
return { ok: false, error };
|
||||
}
|
||||
|
||||
// game enum
|
||||
export enum Game {
|
||||
rs3,
|
||||
@@ -33,51 +41,6 @@ export enum Client {
|
||||
rs3
|
||||
}
|
||||
|
||||
// s()
|
||||
export interface Bolt {
|
||||
provider: string;
|
||||
origin: string;
|
||||
origin_2fa: string;
|
||||
redirect: string;
|
||||
clientid: string;
|
||||
api: string;
|
||||
auth_api: string;
|
||||
profile_api: string;
|
||||
shield_url: string;
|
||||
content_url: string;
|
||||
default_config_uri: string;
|
||||
games: string[];
|
||||
}
|
||||
|
||||
// load on start and save on exit
|
||||
export interface Config {
|
||||
use_dark_theme?: boolean;
|
||||
rs_plugin_loader?: boolean;
|
||||
rs_config_uri?: string;
|
||||
flatpak_rich_presence?: boolean;
|
||||
runelite_use_custom_jar?: boolean;
|
||||
runelite_custom_jar?: string;
|
||||
selected_account?: string;
|
||||
selected_characters?: Map<string, string>; // account userId, then character accountId
|
||||
selected_game_accounts?: Map<string, string>; // legacy version of selected_characters
|
||||
selected_game_index?: number;
|
||||
selected_client_index?: number;
|
||||
}
|
||||
|
||||
// if no config is loaded, these defaults are set to ensure the app runs
|
||||
export const defaultConfig: Config = {
|
||||
use_dark_theme: true,
|
||||
flatpak_rich_presence: false,
|
||||
rs_config_uri: '',
|
||||
runelite_custom_jar: '',
|
||||
runelite_use_custom_jar: false,
|
||||
selected_account: '',
|
||||
selected_characters: new Map(),
|
||||
selected_game_accounts: new Map(),
|
||||
selected_game_index: 1,
|
||||
selected_client_index: 1
|
||||
};
|
||||
|
||||
// account info
|
||||
export interface Account {
|
||||
id: string;
|
||||
@@ -99,7 +62,7 @@ export interface Character {
|
||||
export interface SelectedPlay {
|
||||
account?: Account;
|
||||
character?: Character;
|
||||
credentials?: Credentials;
|
||||
credentials?: Session;
|
||||
game?: Game;
|
||||
client?: Client;
|
||||
}
|
||||
|
||||
12
app/src/lib/Util/onWritableChange.ts
Normal file
12
app/src/lib/Util/onWritableChange.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
export function onWritableChange<T>(store: Writable<T>, fn: (newValue: T) => void) {
|
||||
let initialized = false;
|
||||
return store.subscribe((value) => {
|
||||
if (initialized) {
|
||||
fn(value);
|
||||
} else {
|
||||
initialized = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,16 +1,11 @@
|
||||
import { readable, writable, type Readable, type Writable } from 'svelte/store';
|
||||
import {
|
||||
type Config,
|
||||
type Bolt,
|
||||
type Account,
|
||||
type SelectedPlay,
|
||||
type GameClient,
|
||||
type PluginMeta,
|
||||
defaultConfig,
|
||||
Game,
|
||||
Client
|
||||
} from '$lib/Util/interfaces';
|
||||
import type { Credentials } from '$lib/Services/AuthService';
|
||||
|
||||
// readable stores. known at starup
|
||||
export const internalUrl: Readable<string> = readable('https://bolt-internal');
|
||||
@@ -18,18 +13,7 @@ export const productionClientId: Readable<string> = readable(
|
||||
'1fddee4e-b100-4f4e-b2b0-097f9088f9d2'
|
||||
);
|
||||
|
||||
// writable stores
|
||||
export const bolt: Writable<Bolt> = writable();
|
||||
export const platform: Writable<string | null> = writable('');
|
||||
export const config: Writable<Config> = writable(defaultConfig);
|
||||
export const credentials: Writable<Map<string, Credentials>> = writable(new Map());
|
||||
export const hasBoltPlugins: Writable<boolean> = writable(false);
|
||||
export const pluginList: Writable<{ [key: string]: PluginMeta }> = writable();
|
||||
export const clientListPromise: Writable<Promise<GameClient[]>> = writable();
|
||||
export const rs3InstalledHash: Writable<string | null> = writable('');
|
||||
export const runeLiteInstalledId: Writable<string | null> = writable('');
|
||||
export const hdosInstalledVersion: Writable<string | null> = writable('');
|
||||
export const isConfigDirty: Writable<boolean> = writable(false);
|
||||
export const accountList: Writable<Map<string, Account>> = writable(new Map());
|
||||
export const selectedPlay: Writable<SelectedPlay> = writable({
|
||||
game: Game.osrs,
|
||||
|
||||
100
app/src/main.ts
100
app/src/main.ts
@@ -1,41 +1,62 @@
|
||||
import App from '@/App.svelte';
|
||||
import {
|
||||
clientListPromise,
|
||||
internalUrl,
|
||||
credentials,
|
||||
isConfigDirty,
|
||||
showDisclaimer
|
||||
} from '$lib/Util/store';
|
||||
import { clientListPromise, internalUrl, showDisclaimer } from '$lib/Util/store';
|
||||
import { get } from 'svelte/store';
|
||||
import { getNewClientListPromise, handleLogin, handleNewSessionId } from '$lib/Util/functions';
|
||||
import type { Account, Bolt } from '$lib/Util/interfaces';
|
||||
import type { Account } from '$lib/Util/interfaces';
|
||||
import { unwrap } from '$lib/Util/interfaces';
|
||||
import { logger } from '$lib/Util/Logger';
|
||||
import { BoltService } from '$lib/Services/BoltService';
|
||||
import { ParseUtils } from '$lib/Util/ParseUtils';
|
||||
import { AuthService, type Auth, type Credentials } from '$lib/Services/AuthService';
|
||||
import { AuthService, type Auth, type Session } from '$lib/Services/AuthService';
|
||||
import { Platform, bolt } from '$lib/State/Bolt';
|
||||
|
||||
initBolt();
|
||||
addWindowListeners();
|
||||
|
||||
declare const s: () => Bolt;
|
||||
BoltService.bolt = ParseUtils.decodeBolt(s());
|
||||
start();
|
||||
const app = new App({
|
||||
target: document.getElementById('app')!
|
||||
});
|
||||
|
||||
export default app;
|
||||
|
||||
export function start(): void {
|
||||
// TODO: refactor this function and its contents
|
||||
ParseUtils.parseUrlParams(window.location.search);
|
||||
function initBolt() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
bolt.platform = params.get('platform') as Platform | null;
|
||||
bolt.isFlathub = params.get('flathub') === '1';
|
||||
bolt.rs3InstalledHash = params.get('rs3_linux_installed_hash');
|
||||
bolt.runeLiteInstalledId = params.get('runelite_installed_id');
|
||||
bolt.hdosInstalledVersion = params.get('hdos_installed_version');
|
||||
|
||||
const bolt = BoltService.bolt;
|
||||
const origin = bolt.origin;
|
||||
const clientId = bolt.clientid;
|
||||
const origin_2fa = bolt.origin_2fa;
|
||||
const plugins = params.get('plugins');
|
||||
bolt.hasBoltPlugins = plugins !== null;
|
||||
if (plugins !== null) {
|
||||
try {
|
||||
bolt.pluginList = JSON.parse(plugins);
|
||||
} catch (e) {
|
||||
logger.error('Unable to parse plugin list');
|
||||
}
|
||||
}
|
||||
|
||||
const sessionsParam = params.get('credentials');
|
||||
if (sessionsParam) {
|
||||
try {
|
||||
bolt.sessions = JSON.parse(sessionsParam) as Session[];
|
||||
} catch (e) {
|
||||
logger.error('Unable to parse saved credentials');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: refactor to not use listeners
|
||||
function addWindowListeners(): void {
|
||||
const env = bolt.env;
|
||||
const origin = env.origin;
|
||||
const clientId = env.clientid;
|
||||
const origin_2fa = env.origin_2fa;
|
||||
const boltUrl = get(internalUrl);
|
||||
const exchangeUrl = origin.concat('/oauth2/token');
|
||||
|
||||
if (get(credentials).size == 0) {
|
||||
if (bolt.sessions.length == 0) {
|
||||
showDisclaimer.set(true);
|
||||
}
|
||||
|
||||
@@ -53,10 +74,10 @@ export function start(): void {
|
||||
AuthService.pendingOauth = null;
|
||||
const post_data = new URLSearchParams({
|
||||
grant_type: 'authorization_code',
|
||||
client_id: bolt.clientid,
|
||||
client_id: env.clientid,
|
||||
code: event.data.code,
|
||||
code_verifier: <string>pending.verifier,
|
||||
redirect_uri: bolt.redirect
|
||||
redirect_uri: env.redirect
|
||||
});
|
||||
xml.onreadystatechange = () => {
|
||||
if (xml.readyState == 4) {
|
||||
@@ -66,10 +87,7 @@ export function start(): void {
|
||||
if (creds) {
|
||||
handleLogin(<Window>pending?.win, creds).then((x) => {
|
||||
if (x) {
|
||||
credentials.update((data) => {
|
||||
data.set(creds.sub, creds);
|
||||
return data;
|
||||
});
|
||||
bolt.sessions.push(creds);
|
||||
BoltService.saveAllCreds();
|
||||
}
|
||||
});
|
||||
@@ -120,22 +138,19 @@ export function start(): void {
|
||||
logger.error('Incorrect nonce in id_token');
|
||||
break;
|
||||
}
|
||||
const sessionsUrl = bolt.auth_api.concat('/sessions');
|
||||
const sessionsUrl = env.auth_api.concat('/sessions');
|
||||
xml.onreadystatechange = () => {
|
||||
if (xml.readyState == 4) {
|
||||
if (xml.status == 200) {
|
||||
const accountsUrl = bolt.auth_api.concat('/accounts');
|
||||
const accountsUrl = env.auth_api.concat('/accounts');
|
||||
pending!.creds!.session_id = JSON.parse(xml.response).sessionId;
|
||||
handleNewSessionId(
|
||||
<Credentials>pending!.creds,
|
||||
<Session>pending!.creds,
|
||||
accountsUrl,
|
||||
<Promise<Account>>pending!.account_info_promise
|
||||
).then((x) => {
|
||||
if (x) {
|
||||
credentials.update((data) => {
|
||||
data.set(<string>pending?.creds?.sub, <Credentials>pending!.creds);
|
||||
return data;
|
||||
});
|
||||
bolt.sessions.push(<Session>pending!.creds);
|
||||
BoltService.saveAllCreds();
|
||||
}
|
||||
});
|
||||
@@ -160,33 +175,26 @@ export function start(): void {
|
||||
});
|
||||
|
||||
(async () => {
|
||||
if (get(credentials).size > 0) {
|
||||
get(credentials).forEach(async (value) => {
|
||||
if (bolt.sessions.length > 0) {
|
||||
bolt.sessions.forEach(async (value) => {
|
||||
const result = await AuthService.checkRenewCreds(value, exchangeUrl, clientId);
|
||||
if (result !== null && result !== 0) {
|
||||
logger.error(`Discarding expired login for #${value.sub}`);
|
||||
credentials.update((data) => {
|
||||
data.delete(value.sub);
|
||||
return data;
|
||||
});
|
||||
const index = bolt.sessions.findIndex((session) => session.sub === value.sub);
|
||||
if (index > -1) bolt.sessions.splice(index, 1);
|
||||
BoltService.saveAllCreds();
|
||||
}
|
||||
let checkedCred: Record<string, Credentials | boolean>;
|
||||
let checkedCred: Record<string, Session | boolean>;
|
||||
if (result === null && (await handleLogin(null, value))) {
|
||||
checkedCred = { creds: value, valid: true };
|
||||
} else {
|
||||
checkedCred = { creds: value, valid: result === 0 };
|
||||
}
|
||||
if (checkedCred.valid) {
|
||||
const creds = <Credentials>value;
|
||||
credentials.update((data) => {
|
||||
data.set(creds.sub, creds);
|
||||
return data;
|
||||
});
|
||||
bolt.sessions.push(value);
|
||||
BoltService.saveAllCreds();
|
||||
}
|
||||
});
|
||||
}
|
||||
isConfigDirty.set(false); // overrides all cases where this gets set to "true" due to loading existing config values
|
||||
})();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user