mirror of
https://github.com/wukko/cobalt.git
synced 2025-01-23 19:26:26 +01:00
web/api: jwt session token, clean up, move related modules to own dir
This commit is contained in:
parent
16acf62886
commit
4857030933
9 changed files with 255 additions and 145 deletions
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import "@fontsource-variable/noto-sans-mono";
|
import "@fontsource-variable/noto-sans-mono";
|
||||||
|
|
||||||
import API from "$lib/api";
|
import API from "$lib/api/api";
|
||||||
import { t } from "$lib/i18n/translations";
|
import { t } from "$lib/i18n/translations";
|
||||||
import { createDialog } from "$lib/dialogs";
|
import { createDialog } from "$lib/dialogs";
|
||||||
import { downloadFile } from "$lib/download";
|
import { downloadFile } from "$lib/download";
|
||||||
|
|
|
@ -1,140 +0,0 @@
|
||||||
import { get } from "svelte/store";
|
|
||||||
|
|
||||||
import turnstile from "$lib/turnstile";
|
|
||||||
import env, { apiURL } from "$lib/env";
|
|
||||||
import { t } from "$lib/i18n/translations";
|
|
||||||
import settings, { updateSetting } from "$lib/state/settings";
|
|
||||||
|
|
||||||
import { createDialog } from "$lib/dialogs";
|
|
||||||
|
|
||||||
import type { CobaltAPIResponse } from "$lib/types/api";
|
|
||||||
import type { Optional } from "$lib/types/generic";
|
|
||||||
|
|
||||||
const request = async (url: string) => {
|
|
||||||
const saveSettings = get(settings).save;
|
|
||||||
|
|
||||||
const request = {
|
|
||||||
url,
|
|
||||||
|
|
||||||
downloadMode: saveSettings.downloadMode,
|
|
||||||
|
|
||||||
audioFormat: saveSettings.audioFormat,
|
|
||||||
tiktokFullAudio: saveSettings.tiktokFullAudio,
|
|
||||||
youtubeDubBrowserLang: saveSettings.youtubeDubBrowserLang,
|
|
||||||
|
|
||||||
youtubeVideoCodec: saveSettings.youtubeVideoCodec,
|
|
||||||
videoQuality: saveSettings.videoQuality,
|
|
||||||
|
|
||||||
filenameStyle: saveSettings.filenameStyle,
|
|
||||||
disableMetadata: saveSettings.disableMetadata,
|
|
||||||
|
|
||||||
twitterGif: saveSettings.twitterGif,
|
|
||||||
tiktokH265: saveSettings.tiktokH265,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (env.DEFAULT_API && !get(settings).processing.seenOverrideWarning) {
|
|
||||||
let _actions: {
|
|
||||||
resolve: () => void;
|
|
||||||
reject: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const promise = new Promise<void>(
|
|
||||||
(resolve, reject) => (_actions = { resolve, reject })
|
|
||||||
).catch(() => {
|
|
||||||
return {}
|
|
||||||
});
|
|
||||||
|
|
||||||
createDialog({
|
|
||||||
id: "security-api-override",
|
|
||||||
type: "small",
|
|
||||||
icon: "warn-red",
|
|
||||||
title: get(t)("dialog.api.override.title"),
|
|
||||||
bodyText: get(t)("dialog.api.override.body", { value: env.DEFAULT_API }),
|
|
||||||
dismissable: false,
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
text: get(t)("button.cancel"),
|
|
||||||
main: false,
|
|
||||||
action: () => {
|
|
||||||
_actions.reject();
|
|
||||||
updateSetting({
|
|
||||||
processing: {
|
|
||||||
seenOverrideWarning: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: get(t)("button.continue"),
|
|
||||||
color: "red",
|
|
||||||
main: true,
|
|
||||||
timeout: 5000,
|
|
||||||
action: () => {
|
|
||||||
_actions.resolve();
|
|
||||||
updateSetting({
|
|
||||||
processing: {
|
|
||||||
allowDefaultOverride: true,
|
|
||||||
seenOverrideWarning: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
await promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
let api = apiURL;
|
|
||||||
if (env.DEFAULT_API && get(settings).processing.allowDefaultOverride) {
|
|
||||||
api = env.DEFAULT_API;
|
|
||||||
}
|
|
||||||
|
|
||||||
let turnstileHeader = {};
|
|
||||||
if (env.TURNSTILE_KEY) {
|
|
||||||
const turnstileResponse = turnstile.getResponse();
|
|
||||||
if (turnstileResponse) {
|
|
||||||
turnstileHeader = {
|
|
||||||
"cf-turnstile-response": turnstileResponse
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const response: Optional<CobaltAPIResponse> = await fetch(api, {
|
|
||||||
method: "POST",
|
|
||||||
redirect: "manual",
|
|
||||||
signal: AbortSignal.timeout(10000),
|
|
||||||
body: JSON.stringify(request),
|
|
||||||
headers: {
|
|
||||||
"Accept": "application/json",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
...turnstileHeader,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then(r => r.json())
|
|
||||||
.catch((e) => {
|
|
||||||
if (e?.message?.includes("timed out")) {
|
|
||||||
return {
|
|
||||||
status: "error",
|
|
||||||
error: {
|
|
||||||
code: "error.api.timed_out"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
const probeCobaltStream = async (url: string) => {
|
|
||||||
const request = await fetch(`${url}&p=1`).catch(() => {});
|
|
||||||
if (request?.status === 200) {
|
|
||||||
return request?.status;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
request,
|
|
||||||
probeCobaltStream,
|
|
||||||
}
|
|
11
web/src/lib/api/api-url.ts
Normal file
11
web/src/lib/api/api-url.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { get } from "svelte/store";
|
||||||
|
|
||||||
|
import env, { apiURL } from "$lib/env";
|
||||||
|
import settings from "$lib/state/settings";
|
||||||
|
|
||||||
|
export const currentApiURL = () => {
|
||||||
|
if (env.DEFAULT_API && get(settings).processing.allowDefaultOverride) {
|
||||||
|
return env.DEFAULT_API;
|
||||||
|
}
|
||||||
|
return apiURL;
|
||||||
|
}
|
88
web/src/lib/api/api.ts
Normal file
88
web/src/lib/api/api.ts
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
import { get } from "svelte/store";
|
||||||
|
|
||||||
|
import settings from "$lib/state/settings";
|
||||||
|
import { getSession } from "$lib/api/session";
|
||||||
|
import { currentApiURL } from "$lib/api/api-url";
|
||||||
|
import { apiOverrideWarning } from "$lib/api/override-warning";
|
||||||
|
|
||||||
|
import type { Optional } from "$lib/types/generic";
|
||||||
|
import type { CobaltAPIResponse, CobaltErrorResponse } from "$lib/types/api";
|
||||||
|
|
||||||
|
const request = async (url: string) => {
|
||||||
|
const saveSettings = get(settings).save;
|
||||||
|
|
||||||
|
const request = {
|
||||||
|
url,
|
||||||
|
|
||||||
|
downloadMode: saveSettings.downloadMode,
|
||||||
|
|
||||||
|
audioFormat: saveSettings.audioFormat,
|
||||||
|
tiktokFullAudio: saveSettings.tiktokFullAudio,
|
||||||
|
youtubeDubBrowserLang: saveSettings.youtubeDubBrowserLang,
|
||||||
|
|
||||||
|
youtubeVideoCodec: saveSettings.youtubeVideoCodec,
|
||||||
|
videoQuality: saveSettings.videoQuality,
|
||||||
|
|
||||||
|
filenameStyle: saveSettings.filenameStyle,
|
||||||
|
disableMetadata: saveSettings.disableMetadata,
|
||||||
|
|
||||||
|
twitterGif: saveSettings.twitterGif,
|
||||||
|
tiktokH265: saveSettings.tiktokH265,
|
||||||
|
}
|
||||||
|
|
||||||
|
await apiOverrideWarning();
|
||||||
|
const api = currentApiURL();
|
||||||
|
const session = await getSession();
|
||||||
|
|
||||||
|
let extraHeaders = {}
|
||||||
|
|
||||||
|
if (session) {
|
||||||
|
if ("error" in session) {
|
||||||
|
if (session.error.code !== "error.api.auth.not_configured") {
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
extraHeaders = {
|
||||||
|
"Authorization": `Bearer ${session.token}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const response: Optional<CobaltAPIResponse> = await fetch(api, {
|
||||||
|
method: "POST",
|
||||||
|
redirect: "manual",
|
||||||
|
signal: AbortSignal.timeout(10000),
|
||||||
|
body: JSON.stringify(request),
|
||||||
|
headers: {
|
||||||
|
"Accept": "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
...extraHeaders,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(r => r.json())
|
||||||
|
.catch((e) => {
|
||||||
|
if (e?.message?.includes("timed out")) {
|
||||||
|
return {
|
||||||
|
status: "error",
|
||||||
|
error: {
|
||||||
|
code: "error.api.timed_out"
|
||||||
|
}
|
||||||
|
} as CobaltErrorResponse;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
const probeCobaltStream = async (url: string) => {
|
||||||
|
const request = await fetch(`${url}&p=1`).catch(() => {});
|
||||||
|
if (request?.status === 200) {
|
||||||
|
return request?.status;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
request,
|
||||||
|
probeCobaltStream,
|
||||||
|
}
|
62
web/src/lib/api/override-warning.ts
Normal file
62
web/src/lib/api/override-warning.ts
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import { get } from "svelte/store";
|
||||||
|
|
||||||
|
import env from "$lib/env";
|
||||||
|
import { t } from "$lib/i18n/translations";
|
||||||
|
import settings, { updateSetting } from "$lib/state/settings";
|
||||||
|
|
||||||
|
import { createDialog } from "$lib/dialogs";
|
||||||
|
|
||||||
|
export const apiOverrideWarning = async () => {
|
||||||
|
if (env.DEFAULT_API && !get(settings).processing.seenOverrideWarning) {
|
||||||
|
let _actions: {
|
||||||
|
resolve: () => void;
|
||||||
|
reject: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const promise = new Promise<void>(
|
||||||
|
(resolve, reject) => (_actions = { resolve, reject })
|
||||||
|
).catch(() => {
|
||||||
|
return {}
|
||||||
|
});
|
||||||
|
|
||||||
|
createDialog({
|
||||||
|
id: "security-api-override",
|
||||||
|
type: "small",
|
||||||
|
icon: "warn-red",
|
||||||
|
title: get(t)("dialog.api.override.title"),
|
||||||
|
bodyText: get(t)("dialog.api.override.body", { value: env.DEFAULT_API }),
|
||||||
|
dismissable: false,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: get(t)("button.cancel"),
|
||||||
|
main: false,
|
||||||
|
action: () => {
|
||||||
|
_actions.reject();
|
||||||
|
updateSetting({
|
||||||
|
processing: {
|
||||||
|
seenOverrideWarning: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: get(t)("button.continue"),
|
||||||
|
color: "red",
|
||||||
|
main: true,
|
||||||
|
timeout: 5000,
|
||||||
|
action: () => {
|
||||||
|
_actions.resolve();
|
||||||
|
updateSetting({
|
||||||
|
processing: {
|
||||||
|
allowDefaultOverride: true,
|
||||||
|
seenOverrideWarning: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
await promise;
|
||||||
|
}
|
||||||
|
}
|
65
web/src/lib/api/session.ts
Normal file
65
web/src/lib/api/session.ts
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import { get } from "svelte/store";
|
||||||
|
|
||||||
|
import turnstile from "$lib/api/turnstile";
|
||||||
|
import { currentApiURL } from "$lib/api/api-url";
|
||||||
|
import { cachedSession } from "$lib/state/session";
|
||||||
|
|
||||||
|
import type { CobaltSessionResponse, CobaltErrorResponse } from "$lib/types/api";
|
||||||
|
|
||||||
|
export const requestSession = async() => {
|
||||||
|
const apiEndpoint = `${currentApiURL()}session`;
|
||||||
|
|
||||||
|
let requestHeaders = {};
|
||||||
|
|
||||||
|
const turnstileResponse = turnstile.getResponse();
|
||||||
|
if (turnstileResponse) {
|
||||||
|
requestHeaders = {
|
||||||
|
"cf-turnstile-response": turnstileResponse
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const response: CobaltSessionResponse = await fetch(apiEndpoint, {
|
||||||
|
method: "POST",
|
||||||
|
redirect: "manual",
|
||||||
|
signal: AbortSignal.timeout(10000),
|
||||||
|
headers: requestHeaders,
|
||||||
|
})
|
||||||
|
.then(r => r.json())
|
||||||
|
.catch((e) => {
|
||||||
|
if (e?.message?.includes("timed out")) {
|
||||||
|
return {
|
||||||
|
status: "error",
|
||||||
|
error: {
|
||||||
|
code: "error.api.timed_out"
|
||||||
|
}
|
||||||
|
} as CobaltErrorResponse
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
turnstile.update();
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getSession = async () => {
|
||||||
|
const currentTime = new Date().getTime();
|
||||||
|
const cache = get(cachedSession);
|
||||||
|
|
||||||
|
if (cache?.token && cache?.exp > currentTime) {
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newSession = await requestSession();
|
||||||
|
|
||||||
|
if (!newSession) return {
|
||||||
|
status: "error",
|
||||||
|
error: {
|
||||||
|
code: "error.api.generic"
|
||||||
|
}
|
||||||
|
} as CobaltErrorResponse
|
||||||
|
|
||||||
|
if (!("status" in newSession)) {
|
||||||
|
cachedSession.set(newSession);
|
||||||
|
}
|
||||||
|
return newSession;
|
||||||
|
}
|
|
@ -8,6 +8,17 @@ const getResponse = () => {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
const update = () => {
|
||||||
getResponse
|
const turnstileElement = document.getElementById("turnstile-widget");
|
||||||
|
|
||||||
|
if (turnstileElement) {
|
||||||
|
return window?.turnstile?.reset(turnstileElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getResponse,
|
||||||
|
update,
|
||||||
}
|
}
|
6
web/src/lib/state/session.ts
Normal file
6
web/src/lib/state/session.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
|
import type { Writable } from "svelte/store";
|
||||||
|
import type { CobaltSession } from "$lib/types/api";
|
||||||
|
|
||||||
|
export const cachedSession: Writable<CobaltSession | null> = writable();
|
|
@ -5,7 +5,7 @@ enum CobaltResponseType {
|
||||||
Stream = 'stream',
|
Stream = 'stream',
|
||||||
}
|
}
|
||||||
|
|
||||||
type CobaltErrorResponse = {
|
export type CobaltErrorResponse = {
|
||||||
status: CobaltResponseType.Error,
|
status: CobaltResponseType.Error,
|
||||||
error: {
|
error: {
|
||||||
code: string,
|
code: string,
|
||||||
|
@ -13,7 +13,7 @@ type CobaltErrorResponse = {
|
||||||
service?: string,
|
service?: string,
|
||||||
limit?: number,
|
limit?: number,
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
type CobaltPartialURLResponse = {
|
type CobaltPartialURLResponse = {
|
||||||
|
@ -38,6 +38,13 @@ type CobaltStreamResponse = {
|
||||||
status: CobaltResponseType.Stream,
|
status: CobaltResponseType.Stream,
|
||||||
} & CobaltPartialURLResponse;
|
} & CobaltPartialURLResponse;
|
||||||
|
|
||||||
|
export type CobaltSession = {
|
||||||
|
token: string,
|
||||||
|
exp: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CobaltSessionResponse = CobaltSession | CobaltErrorResponse;
|
||||||
|
|
||||||
export type CobaltAPIResponse = CobaltErrorResponse
|
export type CobaltAPIResponse = CobaltErrorResponse
|
||||||
| CobaltPickerResponse
|
| CobaltPickerResponse
|
||||||
| CobaltRedirectResponse
|
| CobaltRedirectResponse
|
||||||
|
|
Loading…
Reference in a new issue