dependencies: bump (major)

This commit is contained in:
Jesse Chan
2021-10-23 20:52:11 -07:00
parent cb148f7e09
commit 9c9675df34
30 changed files with 2169 additions and 2398 deletions

View File

@@ -20,7 +20,7 @@
{ {
"files": ["*.js", "*.jsx"], "files": ["*.js", "*.jsx"],
"extends": ["prettier"], "extends": ["prettier"],
"parser": "babel-eslint", "parser": "@babel/eslint-parser",
"rules": { "rules": {
"@typescript-eslint/no-var-requires": 0 "@typescript-eslint/no-var-requires": 0
} }

View File

@@ -1,6 +1,5 @@
{ {
"bracketSpacing": false, "bracketSpacing": false,
"jsxBracketSameLine": true,
"printWidth": 120, "printWidth": 120,
"singleQuote": true, "singleQuote": true,
"trailingComma": "all" "trailingComma": "all"

View File

@@ -1,11 +1,13 @@
import axios, {AxiosError, AxiosResponse} from 'axios'; import axios, {AxiosError} from 'axios';
import AuthStore from '@client/stores/AuthStore'; import AuthStore from '@client/stores/AuthStore';
import ConfigStore from '@client/stores/ConfigStore'; import ConfigStore from '@client/stores/ConfigStore';
import type { import type {
AuthAuthenticationOptions, AuthAuthenticationOptions,
AuthAuthenticationResponse,
AuthRegistrationOptions, AuthRegistrationOptions,
AuthRegistrationResponse,
AuthUpdateUserOptions, AuthUpdateUserOptions,
AuthVerificationResponse, AuthVerificationResponse,
} from '@shared/schema/api/auth'; } from '@shared/schema/api/auth';
@@ -20,10 +22,9 @@ const {baseURI} = ConfigStore;
const AuthActions = { const AuthActions = {
authenticate: (options: AuthAuthenticationOptions) => authenticate: (options: AuthAuthenticationOptions) =>
axios axios
.post(`${baseURI}api/auth/authenticate`, options) .post<AuthAuthenticationResponse>(`${baseURI}api/auth/authenticate`, options)
.then((json) => json.data)
.then( .then(
(data) => { ({data}) => {
AuthStore.handleLoginSuccess(data); AuthStore.handleLoginSuccess(data);
}, },
(error) => { (error) => {
@@ -53,21 +54,15 @@ const AuthActions = {
), ),
createUser: (options: AuthRegistrationOptions) => createUser: (options: AuthRegistrationOptions) =>
axios axios.post<AuthRegistrationResponse>(`${baseURI}api/auth/register?cookie=false`, options).then(({data}) => {
.post(`${baseURI}api/auth/register?cookie=false`, options)
.then((json) => json.data)
.then((data) => {
AuthStore.handleCreateUserSuccess(data); AuthStore.handleCreateUserSuccess(data);
}), }),
updateUser: (username: Credentials['username'], options: AuthUpdateUserOptions) => updateUser: (username: Credentials['username'], options: AuthUpdateUserOptions) =>
axios.patch(`${baseURI}api/auth/users/${encodeURIComponent(username)}`, options).then((json) => json.data), axios.patch(`${baseURI}api/auth/users/${encodeURIComponent(username)}`, options).then((res) => res.data),
deleteUser: (username: Credentials['username']) => deleteUser: (username: Credentials['username']) =>
axios axios.delete(`${baseURI}api/auth/users/${encodeURIComponent(username)}`).then(
.delete(`${baseURI}api/auth/users/${encodeURIComponent(username)}`)
.then((json) => json.data)
.then(
() => { () => {
// do nothing. // do nothing.
}, },
@@ -77,10 +72,7 @@ const AuthActions = {
), ),
fetchUsers: () => fetchUsers: () =>
axios axios.get<Array<Credentials>>(`${baseURI}api/auth/users`).then(({data}) => {
.get(`${baseURI}api/auth/users`)
.then((json) => json.data)
.then((data) => {
AuthStore.handleListUsersSuccess(data); AuthStore.handleListUsersSuccess(data);
}), }),
@@ -95,11 +87,8 @@ const AuthActions = {
), ),
register: (options: AuthRegistrationOptions) => register: (options: AuthRegistrationOptions) =>
axios axios.post<AuthRegistrationResponse>(`${baseURI}api/auth/register`, options).then(
.post(`${baseURI}api/auth/register`, options) ({data}) => {
.then((json) => json.data)
.then(
(data) => {
AuthStore.handleRegisterSuccess(data); AuthStore.handleRegisterSuccess(data);
}, },
(error: AxiosError) => { (error: AxiosError) => {
@@ -109,33 +98,31 @@ const AuthActions = {
verify: () => verify: () =>
axios axios
.get(`${baseURI}api/auth/verify?${Date.now()}`) .get<AuthVerificationResponse>(`${baseURI}api/auth/verify?${Date.now()}`)
.then( .then(
(res: AxiosResponse) => { ({data}) => {
if (res.data.configs != null) { if (data.configs != null) {
ConfigStore.handlePreloadConfigs(res.data.configs); ConfigStore.handlePreloadConfigs(data.configs);
} }
return res.data; return data;
}, },
(error: AxiosError) => { (error: AxiosError<AuthVerificationResponse>) => {
if (error.response?.data?.configs != null) { if (error.response?.data?.configs != null) {
ConfigStore.handlePreloadConfigs(error.response.data.configs); ConfigStore.handlePreloadConfigs(error.response.data.configs);
} }
throw error; throw error;
}, },
) )
.then( .then((data) => {
(data: AuthVerificationResponse) => {
AuthStore.handleAuthVerificationSuccess(data); AuthStore.handleAuthVerificationSuccess(data);
return Promise.all([ClientActions.fetchSettings(), SettingActions.fetchSettings()]).then(() => data); return Promise.all([ClientActions.fetchSettings(), SettingActions.fetchSettings()]).then(() => data);
}, })
(error) => { .catch((error) => {
AuthStore.handleAuthVerificationError(); AuthStore.handleAuthVerificationError();
throw error; throw error;
}, }),
),
} as const; } as const;
export default AuthActions; export default AuthActions;

View File

@@ -11,11 +11,8 @@ const {baseURI} = ConfigStore;
const ClientActions = { const ClientActions = {
fetchSettings: async (): Promise<void> => fetchSettings: async (): Promise<void> =>
axios axios.get<ClientSettings>(`${baseURI}api/client/settings`).then(
.get(`${baseURI}api/client/settings`) ({data}) => {
.then((json) => json.data)
.then(
(data) => {
SettingStore.handleClientSettingsFetchSuccess(data); SettingStore.handleClientSettingsFetchSuccess(data);
}, },
() => { () => {
@@ -27,30 +24,22 @@ const ClientActions = {
if (Object.keys(settings).length > 0) { if (Object.keys(settings).length > 0) {
SettingStore.saveClientSettings(settings); SettingStore.saveClientSettings(settings);
let err = false; const success = await axios.patch(`${baseURI}api/client/settings`, settings).then(
await axios () => true,
.patch(`${baseURI}api/client/settings`, settings) () => false,
.then((json) => json.data)
.then(
() => {
// do nothing.
},
() => {
err = true;
},
); );
if (options?.alert) { if (options?.alert) {
// TODO: More precise error message. // TODO: More precise error message.
AlertStore.add( AlertStore.add(
err success
? { ? {
id: 'general.error.unknown',
type: 'error',
}
: {
id: 'alert.settings.saved', id: 'alert.settings.saved',
type: 'success', type: 'success',
}
: {
id: 'general.error.unknown',
type: 'error',
}, },
); );
} }
@@ -61,10 +50,7 @@ const ClientActions = {
ClientActions.saveSettings({[property]: data}), ClientActions.saveSettings({[property]: data}),
testConnection: async (): Promise<void> => testConnection: async (): Promise<void> =>
axios axios.get(`${baseURI}api/client/connection-test`).then(() => {
.get(`${baseURI}api/client/connection-test`)
.then((json) => json.data)
.then(() => {
// do nothing. // do nothing.
}), }),
} as const; } as const;

View File

@@ -3,41 +3,24 @@ import axios from 'axios';
import ConfigStore from '@client/stores/ConfigStore'; import ConfigStore from '@client/stores/ConfigStore';
import FeedStore from '@client/stores/FeedStore'; import FeedStore from '@client/stores/FeedStore';
import type {Feed, Item, Rule} from '@shared/types/Feed';
import type {AddFeedOptions, AddRuleOptions, ModifyFeedOptions} from '@shared/types/api/feed-monitor'; import type {AddFeedOptions, AddRuleOptions, ModifyFeedOptions} from '@shared/types/api/feed-monitor';
const {baseURI} = ConfigStore; const {baseURI} = ConfigStore;
const FeedActions = { const FeedActions = {
addFeed: (options: AddFeedOptions) => addFeed: (options: AddFeedOptions) =>
axios axios.put(`${baseURI}api/feed-monitor/feeds`, options).then(() => FeedActions.fetchFeedMonitors()),
.put(`${baseURI}api/feed-monitor/feeds`, options)
.then((json) => json.data)
.then(() => {
FeedActions.fetchFeedMonitors();
}),
modifyFeed: (id: string, options: ModifyFeedOptions) => modifyFeed: (id: string, options: ModifyFeedOptions) =>
axios axios.patch(`${baseURI}api/feed-monitor/feeds/${id}`, options).then(() => FeedActions.fetchFeedMonitors()),
.patch(`${baseURI}api/feed-monitor/feeds/${id}`, options)
.then((json) => json.data)
.then(() => {
FeedActions.fetchFeedMonitors();
}),
addRule: (options: AddRuleOptions) => addRule: (options: AddRuleOptions) =>
axios axios.put(`${baseURI}api/feed-monitor/rules`, options).then(() => FeedActions.fetchFeedMonitors()),
.put(`${baseURI}api/feed-monitor/rules`, options)
.then((json) => json.data)
.then(() => {
FeedActions.fetchFeedMonitors();
}),
fetchFeedMonitors: () => fetchFeedMonitors: () =>
axios axios.get<{feeds: Array<Feed>; rules: Array<Rule>}>(`${baseURI}api/feed-monitor`).then(
.get(`${baseURI}api/feed-monitor`) ({data}) => {
.then((json) => json.data)
.then(
(data) => {
FeedStore.handleFeedMonitorsFetchSuccess(data); FeedStore.handleFeedMonitorsFetchSuccess(data);
}, },
() => { () => {
@@ -47,14 +30,13 @@ const FeedActions = {
fetchItems: ({id, search}: {id: string; search: string}) => fetchItems: ({id, search}: {id: string; search: string}) =>
axios axios
.get(`${baseURI}api/feed-monitor/feeds/${id}/items`, { .get<Item[]>(`${baseURI}api/feed-monitor/feeds/${id}/items`, {
params: { params: {
search, search,
}, },
}) })
.then((json) => json.data)
.then( .then(
(data) => { ({data}) => {
FeedStore.handleItemsFetchSuccess(data); FeedStore.handleItemsFetchSuccess(data);
}, },
() => { () => {
@@ -63,13 +45,8 @@ const FeedActions = {
), ),
removeFeedMonitor: (id: string) => removeFeedMonitor: (id: string) =>
axios axios.delete(`${baseURI}api/feed-monitor/${id}`).then(
.delete(`${baseURI}api/feed-monitor/${id}`) () => FeedActions.fetchFeedMonitors(),
.then((json) => json.data)
.then(
() => {
FeedActions.fetchFeedMonitors();
},
() => { () => {
// do nothing. // do nothing.
}, },

View File

@@ -9,8 +9,9 @@ import TorrentStore from '@client/stores/TorrentStore';
import TransferDataStore from '@client/stores/TransferDataStore'; import TransferDataStore from '@client/stores/TransferDataStore';
import UIStore from '@client/stores/UIStore'; import UIStore from '@client/stores/UIStore';
import type {DirectoryListResponse} from '@shared/types/api';
import type {HistorySnapshot} from '@shared/constants/historySnapshotTypes'; import type {HistorySnapshot} from '@shared/constants/historySnapshotTypes';
import type {NotificationFetchOptions} from '@shared/types/Notification'; import type {NotificationFetchOptions, NotificationState} from '@shared/types/Notification';
import type {ServerEvents} from '@shared/types/ServerEvents'; import type {ServerEvents} from '@shared/types/ServerEvents';
interface ActivityStreamOptions { interface ActivityStreamOptions {
@@ -70,10 +71,7 @@ const ServerEventHandlers: Record<keyof ServerEvents, (event: unknown) => void>
const FloodActions = { const FloodActions = {
clearNotifications: () => { clearNotifications: () => {
NotificationStore.clearAll(); NotificationStore.clearAll();
return axios return axios.delete(`${baseURI}api/notifications`).then(
.delete(`${baseURI}api/notifications`)
.then((json) => json.data)
.then(
() => { () => {
// do nothing. // do nothing.
}, },
@@ -101,19 +99,18 @@ const FloodActions = {
fetchDirectoryList: (path: string) => fetchDirectoryList: (path: string) =>
axios axios
.get(`${baseURI}api/directory-list`, { .get<DirectoryListResponse>(`${baseURI}api/directory-list`, {
params: {path}, params: {path},
}) })
.then((json) => json.data), .then((res) => res.data),
fetchNotifications: (options: NotificationFetchOptions) => fetchNotifications: (options: NotificationFetchOptions) =>
axios axios
.get(`${baseURI}api/notifications`, { .get<NotificationState>(`${baseURI}api/notifications`, {
params: options, params: options,
}) })
.then((json) => json.data)
.then( .then(
(data) => { ({data}) => {
NotificationStore.handleNotificationsFetchSuccess(data); NotificationStore.handleNotificationsFetchSuccess(data);
}, },
() => { () => {

View File

@@ -11,11 +11,8 @@ const {baseURI} = ConfigStore;
const SettingActions = { const SettingActions = {
fetchSettings: async (): Promise<void> => fetchSettings: async (): Promise<void> =>
axios axios.get<FloodSettings>(`${baseURI}api/settings`).then(
.get(`${baseURI}api/settings`) ({data}) => {
.then((json) => json.data)
.then(
(data) => {
SettingStore.handleSettingsFetchSuccess(data); SettingStore.handleSettingsFetchSuccess(data);
}, },
() => { () => {
@@ -27,30 +24,22 @@ const SettingActions = {
if (Object.keys(settings).length > 0) { if (Object.keys(settings).length > 0) {
SettingStore.saveFloodSettings(settings); SettingStore.saveFloodSettings(settings);
let err = false; const success = await axios.patch(`${baseURI}api/settings`, settings).then(
await axios () => true,
.patch(`${baseURI}api/settings`, settings) () => false,
.then((json) => json.data)
.then(
() => {
// do nothing.
},
() => {
err = true;
},
); );
if (options?.alert) { if (options?.alert) {
// TODO: More precise error message. // TODO: More precise error message.
AlertStore.add( AlertStore.add(
err success
? { ? {
id: 'general.error.unknown',
type: 'error',
}
: {
id: 'alert.settings.saved', id: 'alert.settings.saved',
type: 'success', type: 'success',
}
: {
id: 'general.error.unknown',
type: 'error',
}, },
); );
} }

View File

@@ -11,7 +11,7 @@ import type {
ReannounceTorrentsOptions, ReannounceTorrentsOptions,
SetTorrentsTagsOptions, SetTorrentsTagsOptions,
} from '@shared/schema/api/torrents'; } from '@shared/schema/api/torrents';
import type { import {
CheckTorrentsOptions, CheckTorrentsOptions,
CreateTorrentOptions, CreateTorrentOptions,
DeleteTorrentsOptions, DeleteTorrentsOptions,
@@ -23,6 +23,8 @@ import type {
SetTorrentsTrackersOptions, SetTorrentsTrackersOptions,
StartTorrentsOptions, StartTorrentsOptions,
StopTorrentsOptions, StopTorrentsOptions,
TorrentAddResponse,
TorrentMediainfoResponse,
} from '@shared/types/api/torrents'; } from '@shared/types/api/torrents';
import type {TorrentContent} from '@shared/types/TorrentContent'; import type {TorrentContent} from '@shared/types/TorrentContent';
import type {TorrentPeer} from '@shared/types/TorrentPeer'; import type {TorrentPeer} from '@shared/types/TorrentPeer';
@@ -56,13 +58,10 @@ const emitFailedToAddTorrentAlert = (count: number) => {
const TorrentActions = { const TorrentActions = {
addTorrentsByUrls: (options: AddTorrentByURLOptions): Promise<void> => addTorrentsByUrls: (options: AddTorrentByURLOptions): Promise<void> =>
axios axios.post<TorrentAddResponse>(`${baseURI}api/torrents/add-urls`, options).then(
.post(`${baseURI}api/torrents/add-urls`, options) ({data}) => {
.then((json) => json.data) if (data.length) {
.then( emitTorrentAddedAlert(data.length);
(response) => {
if (response.length) {
emitTorrentAddedAlert(response.length);
} else { } else {
emitRequestSentAlert(options.urls.length); emitRequestSentAlert(options.urls.length);
} }
@@ -73,13 +72,10 @@ const TorrentActions = {
), ),
addTorrentsByFiles: (options: AddTorrentByFileOptions): Promise<void> => addTorrentsByFiles: (options: AddTorrentByFileOptions): Promise<void> =>
axios axios.post<TorrentAddResponse>(`${baseURI}api/torrents/add-files`, options).then(
.post(`${baseURI}api/torrents/add-files`, options) ({data}) => {
.then((json) => json.data) if (data.length) {
.then( emitTorrentAddedAlert(data.length);
(response) => {
if (response.length) {
emitTorrentAddedAlert(response.length);
} else { } else {
emitRequestSentAlert(options.files.length); emitRequestSentAlert(options.files.length);
} }
@@ -90,9 +86,9 @@ const TorrentActions = {
), ),
createTorrent: (options: CreateTorrentOptions): Promise<void> => createTorrent: (options: CreateTorrentOptions): Promise<void> =>
axios.post(`${baseURI}api/torrents/create`, options, {responseType: 'blob'}).then( axios.post<Blob>(`${baseURI}api/torrents/create`, options, {responseType: 'blob'}).then(
(response) => { ({data}) => {
download(response.data, (options.name || `${Date.now()}`).concat('.torrent')); download(data, (options.name || `${Date.now()}`).concat('.torrent'));
emitTorrentAddedAlert(1); emitTorrentAddedAlert(1);
}, },
() => { () => {
@@ -101,31 +97,23 @@ const TorrentActions = {
), ),
deleteTorrents: (options: DeleteTorrentsOptions): Promise<void> => deleteTorrents: (options: DeleteTorrentsOptions): Promise<void> =>
axios axios.post(`${baseURI}api/torrents/delete`, options).then(
.post(`${baseURI}api/torrents/delete`, options) () =>
.then((json) => json.data)
.then(
() => {
AlertStore.add({ AlertStore.add({
id: 'alert.torrent.remove', id: 'alert.torrent.remove',
type: 'success', type: 'success',
count: options.hashes.length, count: options.hashes.length,
}); }),
}, () =>
() => {
AlertStore.add({ AlertStore.add({
id: 'alert.torrent.remove.failed', id: 'alert.torrent.remove.failed',
type: 'error', type: 'error',
count: options.hashes.length, count: options.hashes.length,
}); }),
},
), ),
checkHash: (options: CheckTorrentsOptions): Promise<void> => checkHash: (options: CheckTorrentsOptions): Promise<void> =>
axios axios.post(`${baseURI}api/torrents/check-hash`, options).then(
.post(`${baseURI}api/torrents/check-hash`, options)
.then((json) => json.data)
.then(
() => { () => {
// do nothing. // do nothing.
}, },
@@ -135,32 +123,25 @@ const TorrentActions = {
), ),
fetchMediainfo: (hash: TorrentProperties['hash'], cancelToken?: CancelToken): Promise<{output: string}> => fetchMediainfo: (hash: TorrentProperties['hash'], cancelToken?: CancelToken): Promise<{output: string}> =>
axios.get(`${baseURI}api/torrents/${hash}/mediainfo`, {cancelToken}).then<{output: string}>((json) => json.data), axios
.get<TorrentMediainfoResponse>(`${baseURI}api/torrents/${hash}/mediainfo`, {cancelToken})
.then<{output: string}>((res) => res.data),
fetchTorrentContents: (hash: TorrentProperties['hash']): Promise<Array<TorrentContent> | null> => fetchTorrentContents: (hash: TorrentProperties['hash']): Promise<Array<TorrentContent> | null> =>
axios axios.get<Array<TorrentContent>>(`${baseURI}api/torrents/${hash}/contents`).then(
.get(`${baseURI}api/torrents/${hash}/contents`) (res) => res.data,
.then<Array<TorrentContent>>((json) => json.data)
.then(
(contents) => contents,
() => null, () => null,
), ),
fetchTorrentPeers: (hash: TorrentProperties['hash']): Promise<Array<TorrentPeer> | null> => fetchTorrentPeers: (hash: TorrentProperties['hash']): Promise<Array<TorrentPeer> | null> =>
axios axios.get<Array<TorrentPeer>>(`${baseURI}api/torrents/${hash}/peers`).then(
.get(`${baseURI}api/torrents/${hash}/peers`) (res) => res.data,
.then<Array<TorrentPeer>>((json) => json.data)
.then(
(peers) => peers,
() => null, () => null,
), ),
fetchTorrentTrackers: (hash: TorrentProperties['hash']): Promise<Array<TorrentTracker> | null> => fetchTorrentTrackers: (hash: TorrentProperties['hash']): Promise<Array<TorrentTracker> | null> =>
axios axios.get<Array<TorrentTracker>>(`${baseURI}api/torrents/${hash}/trackers`).then(
.get(`${baseURI}api/torrents/${hash}/trackers`) (res) => res.data,
.then<Array<TorrentTracker>>((json) => json.data)
.then(
(trackers) => trackers,
() => null, () => null,
), ),
@@ -175,31 +156,23 @@ const TorrentActions = {
), ),
moveTorrents: (options: MoveTorrentsOptions): Promise<void> => moveTorrents: (options: MoveTorrentsOptions): Promise<void> =>
axios axios.post(`${baseURI}api/torrents/move`, options).then(
.post(`${baseURI}api/torrents/move`, options) () =>
.then((json) => json.data)
.then(
() => {
AlertStore.add({ AlertStore.add({
id: 'alert.torrent.move', id: 'alert.torrent.move',
type: 'success', type: 'success',
count: options.hashes.length, count: options.hashes.length,
}); }),
}, () =>
() => {
AlertStore.add({ AlertStore.add({
id: 'alert.torrent.move.failed', id: 'alert.torrent.move.failed',
type: 'error', type: 'error',
count: options.hashes.length, count: options.hashes.length,
}); }),
},
), ),
reannounce: (options: ReannounceTorrentsOptions): Promise<void> => reannounce: (options: ReannounceTorrentsOptions): Promise<void> =>
axios axios.post(`${baseURI}api/torrents/reannounce`, options).then(
.post(`${baseURI}api/torrents/reannounce`, options)
.then((json) => json.data)
.then(
() => { () => {
// do nothing. // do nothing.
}, },
@@ -210,10 +183,7 @@ const TorrentActions = {
startTorrents: async (options: StartTorrentsOptions): Promise<void> => { startTorrents: async (options: StartTorrentsOptions): Promise<void> => {
if (options.hashes.length > 0) { if (options.hashes.length > 0) {
return axios return axios.post(`${baseURI}api/torrents/start`, options).then(
.post(`${baseURI}api/torrents/start`, options)
.then((json) => json.data)
.then(
() => { () => {
// do nothing. // do nothing.
}, },
@@ -227,10 +197,7 @@ const TorrentActions = {
stopTorrents: async (options: StopTorrentsOptions): Promise<void> => { stopTorrents: async (options: StopTorrentsOptions): Promise<void> => {
if (options.hashes.length > 0) { if (options.hashes.length > 0) {
return axios return axios.post(`${baseURI}api/torrents/stop`, options).then(
.post(`${baseURI}api/torrents/stop`, options)
.then((json) => json.data)
.then(
() => { () => {
// do nothing. // do nothing.
}, },
@@ -243,10 +210,7 @@ const TorrentActions = {
}, },
setInitialSeeding: (options: SetTorrentsInitialSeedingOptions): Promise<void> => setInitialSeeding: (options: SetTorrentsInitialSeedingOptions): Promise<void> =>
axios axios.patch(`${baseURI}api/torrents/initial-seeding`, options).then(
.patch(`${baseURI}api/torrents/initial-seeding`, options)
.then((json) => json.data)
.then(
() => { () => {
// do nothing. // do nothing.
}, },
@@ -256,10 +220,7 @@ const TorrentActions = {
), ),
setPriority: (options: SetTorrentsPriorityOptions): Promise<void> => setPriority: (options: SetTorrentsPriorityOptions): Promise<void> =>
axios axios.patch(`${baseURI}api/torrents/priority`, options).then(
.patch(`${baseURI}api/torrents/priority`, options)
.then((json) => json.data)
.then(
() => { () => {
// do nothing. // do nothing.
}, },
@@ -269,10 +230,7 @@ const TorrentActions = {
), ),
setSequential: (options: SetTorrentsSequentialOptions): Promise<void> => setSequential: (options: SetTorrentsSequentialOptions): Promise<void> =>
axios axios.patch(`${baseURI}api/torrents/sequential`, options).then(
.patch(`${baseURI}api/torrents/sequential`, options)
.then((json) => json.data)
.then(
() => { () => {
// do nothing. // do nothing.
}, },
@@ -282,10 +240,7 @@ const TorrentActions = {
), ),
setFilePriority: (hash: TorrentProperties['hash'], options: SetTorrentContentsPropertiesOptions): Promise<void> => setFilePriority: (hash: TorrentProperties['hash'], options: SetTorrentContentsPropertiesOptions): Promise<void> =>
axios axios.patch(`${baseURI}api/torrents/${hash}/contents`, options).then(
.patch(`${baseURI}api/torrents/${hash}/contents`, options)
.then((json) => json.data)
.then(
() => { () => {
// do nothing. // do nothing.
}, },
@@ -295,23 +250,15 @@ const TorrentActions = {
), ),
setTags: (options: SetTorrentsTagsOptions): Promise<void> => setTags: (options: SetTorrentsTagsOptions): Promise<void> =>
axios axios.patch(`${baseURI}api/torrents/tags`, options).then(
.patch(`${baseURI}api/torrents/tags`, options) () => UIStore.handleSetTaxonomySuccess(),
.then((json) => json.data)
.then(
() => {
UIStore.handleSetTaxonomySuccess();
},
() => { () => {
// do nothing. // do nothing.
}, },
), ),
setTrackers: (options: SetTorrentsTrackersOptions): Promise<void> => setTrackers: (options: SetTorrentsTrackersOptions): Promise<void> =>
axios axios.patch(`${baseURI}api/torrents/trackers`, options).then(
.patch(`${baseURI}api/torrents/trackers`, options)
.then((json) => json.data)
.then(
() => { () => {
// do nothing. // do nothing.
}, },

View File

@@ -1,5 +1,5 @@
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import {forwardRef, MutableRefObject, ReactNode, useEffect, useRef, useState} from 'react'; import {forwardRef, MutableRefObject, ReactElement, ReactNode, useEffect, useRef, useState} from 'react';
import {Trans, useLingui} from '@lingui/react'; import {Trans, useLingui} from '@lingui/react';
import {useEnsuredForwardedRef} from 'react-use'; import {useEnsuredForwardedRef} from 'react-use';
@@ -67,7 +67,7 @@ const FilesystemBrowserTextbox = forwardRef<HTMLInputElement, FilesystemBrowserT
}; };
}, []); }, []);
const toggles: React.ReactNodeArray = []; const toggles: Array<ReactElement> = [];
if (showBasePathToggle) { if (showBasePathToggle) {
toggles.push( toggles.push(
<Checkbox grow={false} id="isBasePath" key="isBasePath"> <Checkbox grow={false} id="isBasePath" key="isBasePath">

View File

@@ -1,5 +1,5 @@
import classnames from 'classnames'; import classnames from 'classnames';
import {FC, ReactNode, ReactNodeArray, useEffect, useRef, useState} from 'react'; import {FC, ReactElement, ReactNode, useEffect, useRef, useState} from 'react';
import {sort} from 'fast-sort'; import {sort} from 'fast-sort';
import {Trans} from '@lingui/react'; import {Trans} from '@lingui/react';
import {useKeyPressEvent} from 'react-use'; import {useKeyPressEvent} from 'react-use';
@@ -114,7 +114,7 @@ const TagSelect: FC<TagSelectProps> = ({defaultValue, placeholder, id, label, on
...sort(Object.keys(TorrentFilterStore.taxonomy.tagCounts)).asc(), ...sort(Object.keys(TorrentFilterStore.taxonomy.tagCounts)).asc(),
...selectedTags, ...selectedTags,
]), ]),
].reduce((accumulator: ReactNodeArray, tag) => { ].reduce((accumulator: Array<ReactElement>, tag) => {
if (tag === '') { if (tag === '') {
return accumulator; return accumulator;
} }

View File

@@ -1,5 +1,5 @@
import classnames from 'classnames'; import classnames from 'classnames';
import {FC, ReactNode, ReactNodeArray} from 'react'; import {FC, ReactNode} from 'react';
export interface Tab { export interface Tab {
id?: string; id?: string;
@@ -18,7 +18,9 @@ interface ModalTabsProps {
const ModalTabs: FC<ModalTabsProps> = (props: ModalTabsProps) => { const ModalTabs: FC<ModalTabsProps> = (props: ModalTabsProps) => {
const {activeTabId, tabs = {}, onTabChange} = props; const {activeTabId, tabs = {}, onTabChange} = props;
const tabNodes: ReactNodeArray = Object.keys(tabs).map((tabId) => { return (
<ul className="modal__tabs">
{Object.keys(tabs).map((tabId) => {
const currentTab = tabs[tabId]; const currentTab = tabs[tabId];
currentTab.id = tabId; currentTab.id = tabId;
@@ -50,9 +52,9 @@ const ModalTabs: FC<ModalTabsProps> = (props: ModalTabsProps) => {
</button> </button>
</li> </li>
); );
}); })}
</ul>
return <ul className="modal__tabs">{tabNodes}</ul>; );
}; };
ModalTabs.defaultProps = { ModalTabs.defaultProps = {

View File

@@ -1,4 +1,4 @@
import {FC, ReactNodeArray, useRef, useState} from 'react'; import {FC, ReactElement, useRef, useState} from 'react';
import {Trans, useLingui} from '@lingui/react'; import {Trans, useLingui} from '@lingui/react';
import {Button, Form, FormError, FormRow, FormRowItem} from '@client/ui'; import {Button, Form, FormError, FormRow, FormRowItem} from '@client/ui';
@@ -164,7 +164,7 @@ const DownloadRulesTab: FC = () => {
<ModalFormSectionHeader> <ModalFormSectionHeader>
<Trans id="feeds.existing.rules" /> <Trans id="feeds.existing.rules" />
</ModalFormSectionHeader> </ModalFormSectionHeader>
{Object.keys(errors).reduce((memo: ReactNodeArray, key) => { {Object.keys(errors).reduce((memo: Array<ReactElement>, key) => {
if (errors[key as ValidatedField] != null) { if (errors[key as ValidatedField] != null) {
memo.push( memo.push(
<FormRow key={`error-${key}`}> <FormRow key={`error-${key}`}>

View File

@@ -1,4 +1,4 @@
import {FC, ReactNodeArray} from 'react'; import {FC, ReactElement} from 'react';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import {Trans} from '@lingui/react'; import {Trans} from '@lingui/react';
@@ -13,7 +13,7 @@ interface FeedItemsProps {
const FeedItems: FC<FeedItemsProps> = observer(({selectedFeedID}: FeedItemsProps) => { const FeedItems: FC<FeedItemsProps> = observer(({selectedFeedID}: FeedItemsProps) => {
const {items} = FeedStore; const {items} = FeedStore;
const itemElements: ReactNodeArray = []; const itemElements: Array<ReactElement> = [];
if (selectedFeedID) { if (selectedFeedID) {
const titleOccurrences: Record<string, number> = {}; const titleOccurrences: Record<string, number> = {};
items.forEach((item, index) => { items.forEach((item, index) => {

View File

@@ -1,4 +1,4 @@
import {FC, ReactNodeArray, useRef, useState} from 'react'; import {FC, ReactElement, useRef, useState} from 'react';
import {Trans, useLingui} from '@lingui/react'; import {Trans, useLingui} from '@lingui/react';
import {Button, Form, FormError, FormRow, FormRowItem} from '@client/ui'; import {Button, Form, FormError, FormRow, FormRowItem} from '@client/ui';
@@ -137,7 +137,7 @@ const FeedsTab: FC = () => {
<ModalFormSectionHeader> <ModalFormSectionHeader>
<Trans id="feeds.existing.feeds" /> <Trans id="feeds.existing.feeds" />
</ModalFormSectionHeader> </ModalFormSectionHeader>
{Object.keys(errors).reduce((memo: ReactNodeArray, key) => { {Object.keys(errors).reduce((memo: Array<ReactElement>, key) => {
if (errors[key as ValidatedField] != null) { if (errors[key as ValidatedField] != null) {
memo.push( memo.push(
<FormRow key={`error-${key}`}> <FormRow key={`error-${key}`}>

View File

@@ -1,5 +1,5 @@
import classnames from 'classnames'; import classnames from 'classnames';
import {forwardRef, MutableRefObject, ReactNodeArray, useRef, useState} from 'react'; import {forwardRef, MutableRefObject, ReactElement, useRef, useState} from 'react';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import {Trans, useLingui} from '@lingui/react'; import {Trans, useLingui} from '@lingui/react';
import {useEnsuredForwardedRef} from 'react-use'; import {useEnsuredForwardedRef} from 'react-use';
@@ -76,7 +76,7 @@ const TableHeading = observer(
return ( return (
<div className="table__row table__row--heading" role="row" ref={tableHeading}> <div className="table__row table__row--heading" role="row" ref={tableHeading}>
{SettingStore.floodSettings.torrentListColumns.reduce((accumulator: ReactNodeArray, {id, visible}) => { {SettingStore.floodSettings.torrentListColumns.reduce((accumulator: Array<ReactElement>, {id, visible}) => {
if (!visible) { if (!visible) {
return accumulator; return accumulator;
} }

View File

@@ -1,4 +1,4 @@
import {CSSProperties, forwardRef, KeyboardEvent, MouseEvent, ReactNodeArray, TouchEvent} from 'react'; import {CSSProperties, forwardRef, KeyboardEvent, MouseEvent, ReactElement, TouchEvent} from 'react';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import SettingStore from '../../stores/SettingStore'; import SettingStore from '../../stores/SettingStore';
@@ -34,7 +34,7 @@ const TorrentListRowCondensed = observer(
ref, ref,
) => { ) => {
const torrentListColumns = SettingStore.floodSettings.torrentListColumns.reduce( const torrentListColumns = SettingStore.floodSettings.torrentListColumns.reduce(
(accumulator: ReactNodeArray, {id, visible}) => { (accumulator: Array<ReactElement>, {id, visible}) => {
if (TorrentListColumns[id] == null) { if (TorrentListColumns[id] == null) {
return accumulator; return accumulator;
} }

View File

@@ -1,4 +1,4 @@
import {CSSProperties, FC, forwardRef, KeyboardEvent, MouseEvent, ReactNodeArray, TouchEvent} from 'react'; import {CSSProperties, FC, forwardRef, KeyboardEvent, MouseEvent, ReactElement, TouchEvent} from 'react';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import {useLingui} from '@lingui/react'; import {useLingui} from '@lingui/react';
@@ -54,7 +54,7 @@ const TorrentListRowExpanded = observer(
) => { ) => {
const columns = SettingStore.floodSettings.torrentListColumns; const columns = SettingStore.floodSettings.torrentListColumns;
const primarySection: ReactNodeArray = [ const primarySection: Array<ReactElement> = [
<TorrentListCell <TorrentListCell
key="name" key="name"
hash={hash} hash={hash}
@@ -62,12 +62,12 @@ const TorrentListRowExpanded = observer(
className="torrent__details__section torrent__details__section--primary" className="torrent__details__section torrent__details__section--primary"
/>, />,
]; ];
const secondarySection: ReactNodeArray = [ const secondarySection: Array<ReactElement> = [
<TorrentListCell key="eta" hash={hash} column="eta" showIcon />, <TorrentListCell key="eta" hash={hash} column="eta" showIcon />,
<TorrentListCell key="downRate" hash={hash} column="downRate" showIcon />, <TorrentListCell key="downRate" hash={hash} column="downRate" showIcon />,
<TorrentListCell key="upRate" hash={hash} column="upRate" showIcon />, <TorrentListCell key="upRate" hash={hash} column="upRate" showIcon />,
]; ];
const tertiarySection: ReactNodeArray = [ const tertiarySection: Array<ReactElement> = [
<TorrentListCell <TorrentListCell
key="percentComplete" key="percentComplete"
hash={hash} hash={hash}
@@ -76,7 +76,7 @@ const TorrentListRowExpanded = observer(
showIcon showIcon
/>, />,
]; ];
const quaternarySection: ReactNodeArray = [ const quaternarySection: Array<ReactElement> = [
<TorrentListCell <TorrentListCell
key="percentBar" key="percentBar"
hash={hash} hash={hash}

View File

@@ -4,7 +4,11 @@ import FloodActions from '@client/actions/FloodActions';
import {AccessLevel} from '@shared/schema/constants/Auth'; import {AccessLevel} from '@shared/schema/constants/Auth';
import type {AuthAuthenticationResponse, AuthVerificationResponse} from '@shared/schema/api/auth'; import type {
AuthAuthenticationResponse,
AuthRegistrationResponse,
AuthVerificationResponse,
} from '@shared/schema/api/auth';
import type {Credentials} from '@shared/schema/Auth'; import type {Credentials} from '@shared/schema/Auth';
class AuthStore { class AuthStore {
@@ -26,7 +30,7 @@ class AuthStore {
makeAutoObservable(this); makeAutoObservable(this);
} }
handleCreateUserSuccess({username}: {username: Credentials['username']}): void { handleCreateUserSuccess({username}: AuthRegistrationResponse): void {
this.optimisticUsers.push({username}); this.optimisticUsers.push({username});
} }
@@ -37,9 +41,9 @@ class AuthStore {
this.users = nextUserList; this.users = nextUserList;
} }
handleLoginSuccess(response: AuthAuthenticationResponse): void { handleLoginSuccess({username, level}: AuthAuthenticationResponse): void {
this.currentUser.username = response.username; this.currentUser.username = username;
this.currentUser.isAdmin = response.level === AccessLevel.ADMINISTRATOR; this.currentUser.isAdmin = level === AccessLevel.ADMINISTRATOR;
this.currentUser.isInitialUser = false; this.currentUser.isInitialUser = false;
this.isAuthenticating = true; this.isAuthenticating = true;
this.isAuthenticated = true; this.isAuthenticated = true;
@@ -50,21 +54,23 @@ class AuthStore {
this.isAuthenticating = true; this.isAuthenticating = true;
} }
handleRegisterSuccess(response: AuthAuthenticationResponse): void { handleRegisterSuccess({username, level}: AuthRegistrationResponse): void {
this.currentUser.username = response.username; this.currentUser.username = username;
this.currentUser.isAdmin = response.level === AccessLevel.ADMINISTRATOR; this.currentUser.isAdmin = level === AccessLevel.ADMINISTRATOR;
this.currentUser.isInitialUser = false; this.currentUser.isInitialUser = false;
FloodActions.restartActivityStream(); FloodActions.restartActivityStream();
} }
handleAuthVerificationSuccess(response: AuthVerificationResponse): void { handleAuthVerificationSuccess(response: AuthVerificationResponse): void {
if (response.initialUser === true) { if (response.initialUser === true) {
this.currentUser.isInitialUser = response.initialUser; this.currentUser.isInitialUser = true;
} else { } else {
const {username, level} = response;
this.currentUser = { this.currentUser = {
username: response.username, username: username,
isAdmin: response.level === AccessLevel.ADMINISTRATOR, isAdmin: level === AccessLevel.ADMINISTRATOR,
isInitialUser: response.initialUser, isInitialUser: false,
}; };
} }

View File

@@ -1,4 +1,4 @@
import {ButtonHTMLAttributes, Children, cloneElement, FC, ReactElement, ReactNode, ReactNodeArray, Ref} from 'react'; import {ButtonHTMLAttributes, Children, cloneElement, FC, ReactElement, ReactNode, Ref} from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import {LoadingRing} from '@client/ui/icons'; import {LoadingRing} from '@client/ui/icons';
@@ -42,8 +42,8 @@ const Button: FC<ButtonProps> = ({
grow, grow,
onClick, onClick,
}: ButtonProps) => { }: ButtonProps) => {
const addonNodes: ReactNodeArray = []; const addonNodes: Array<ReactElement> = [];
const childNodes: ReactNodeArray = []; const childNodes: Array<ReactElement> = [];
Children.toArray(children).forEach((child) => { Children.toArray(children).forEach((child) => {
const childAsElement = child as ReactElement; const childAsElement = child as ReactElement;
@@ -56,7 +56,7 @@ const Button: FC<ButtonProps> = ({
}), }),
); );
} else { } else {
childNodes.push(child); childNodes.push(childAsElement);
} }
}); });

3417
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -66,7 +66,7 @@
"test:client": "FLOOD_OPTION_port=4200 start-server-and-test start 4200 'cypress run'" "test:client": "FLOOD_OPTION_port=4200 start-server-and-test start 4200 'cypress run'"
}, },
"dependencies": { "dependencies": {
"geoip-country": "^4.0.89" "geoip-country": "^4.0.91"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.15.8", "@babel/core": "^7.15.8",
@@ -78,8 +78,8 @@
"@babel/preset-react": "^7.14.5", "@babel/preset-react": "^7.14.5",
"@babel/preset-typescript": "^7.15.0", "@babel/preset-typescript": "^7.15.0",
"@emotion/babel-plugin": "^11.3.0", "@emotion/babel-plugin": "^11.3.0",
"@emotion/css": "^11.1.3", "@emotion/css": "^11.5.0",
"@emotion/react": "^11.4.1", "@emotion/react": "^11.5.0",
"@lingui/loader": "^3.12.1", "@lingui/loader": "^3.12.1",
"@lingui/react": "^3.12.1", "@lingui/react": "^3.12.1",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.1", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.1",
@@ -89,7 +89,7 @@
"@types/content-disposition": "^0.5.4", "@types/content-disposition": "^0.5.4",
"@types/cookie-parser": "^1.4.2", "@types/cookie-parser": "^1.4.2",
"@types/create-torrent": "^5.0.0", "@types/create-torrent": "^5.0.0",
"@types/d3": "^7.0.0", "@types/d3": "^7.1.0",
"@types/debug": "^4.1.7", "@types/debug": "^4.1.7",
"@types/express": "^4.17.13", "@types/express": "^4.17.13",
"@types/express-rate-limit": "^5.1.3", "@types/express-rate-limit": "^5.1.3",
@@ -98,29 +98,28 @@
"@types/http-errors": "^1.8.1", "@types/http-errors": "^1.8.1",
"@types/jest": "^27.0.2", "@types/jest": "^27.0.2",
"@types/jsonwebtoken": "^8.5.5", "@types/jsonwebtoken": "^8.5.5",
"@types/lodash": "^4.14.175", "@types/lodash": "^4.14.176",
"@types/morgan": "^1.9.3", "@types/morgan": "^1.9.3",
"@types/node": "^12.20.28", "@types/node": "^12.20.34",
"@types/overlayscrollbars": "^1.12.1", "@types/overlayscrollbars": "^1.12.1",
"@types/parse-torrent": "^5.8.4", "@types/parse-torrent": "^5.8.4",
"@types/passport": "^1.0.7", "@types/passport": "^1.0.7",
"@types/passport-jwt": "^3.0.6", "@types/passport-jwt": "^3.0.6",
"@types/react": "^17.0.27", "@types/react": "^17.0.31",
"@types/react-dom": "^17.0.9", "@types/react-dom": "^17.0.10",
"@types/react-measure": "^2.0.7", "@types/react-measure": "^2.0.8",
"@types/react-router-dom": "^5.3.1", "@types/react-router-dom": "^5.3.1",
"@types/react-transition-group": "^4.4.3", "@types/react-transition-group": "^4.4.4",
"@types/react-window": "^1.8.5", "@types/react-window": "^1.8.5",
"@types/spdy": "^3.4.5", "@types/spdy": "^3.4.5",
"@types/supertest": "^2.0.11", "@types/supertest": "^2.0.11",
"@types/tar-fs": "^2.0.1", "@types/tar-fs": "^2.0.1",
"@types/tar-stream": "^2.2.1",
"@typescript-eslint/eslint-plugin": "^4.33.0", "@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^4.33.0", "@typescript-eslint/parser": "^4.33.0",
"@vercel/ncc": "^0.31.1", "@vercel/ncc": "^0.31.1",
"autoprefixer": "^10.3.7", "autoprefixer": "^10.3.7",
"axios": "^0.21.4", "axios": "^0.23.0",
"babel-loader": "^8.2.2", "babel-loader": "^8.2.3",
"bencode": "^2.0.2", "bencode": "^2.0.2",
"body-parser": "^1.19.0", "body-parser": "^1.19.0",
"case-sensitive-paths-webpack-plugin": "2.4.0", "case-sensitive-paths-webpack-plugin": "2.4.0",
@@ -130,7 +129,7 @@
"content-disposition": "^0.5.3", "content-disposition": "^0.5.3",
"cookie-parser": "^1.4.5", "cookie-parser": "^1.4.5",
"create-torrent": "^5.0.1", "create-torrent": "^5.0.1",
"css-loader": "^6.3.0", "css-loader": "^6.4.0",
"css-minimizer-webpack-plugin": "^3.1.1", "css-minimizer-webpack-plugin": "^3.1.1",
"d3-array": "^3.1.1", "d3-array": "^3.1.1",
"d3-scale": "^4.0.2", "d3-scale": "^4.0.2",
@@ -141,16 +140,11 @@
"eslint-config-airbnb": "^18.2.1", "eslint-config-airbnb": "^18.2.1",
"eslint-config-airbnb-typescript": "^14.0.1", "eslint-config-airbnb-typescript": "^14.0.1",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-config-react-app": "^6.0.0", "eslint-config-react-app": "^7.0.0-next.91",
"eslint-import-resolver-webpack": "^0.13.1", "eslint-import-resolver-webpack": "^0.13.2",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-jest": "^24.5.2",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-webpack-plugin": "^3.0.1", "eslint-webpack-plugin": "^3.0.1",
"express": "^4.17.1", "express": "^4.17.1",
"express-rate-limit": "^5.4.1", "express-rate-limit": "^5.5.0",
"fast-json-patch": "^3.1.0", "fast-json-patch": "^3.1.0",
"fast-sort": "^3.0.3", "fast-sort": "^3.0.3",
"feedsub": "^0.7.7", "feedsub": "^0.7.7",
@@ -160,15 +154,15 @@
"fs-extra": "^10.0.0", "fs-extra": "^10.0.0",
"get-user-locale": "^1.4.0", "get-user-locale": "^1.4.0",
"hash-wasm": "^4.9.0", "hash-wasm": "^4.9.0",
"html-webpack-plugin": "^5.3.2", "html-webpack-plugin": "^5.4.0",
"http-errors": "^1.8.0", "http-errors": "^1.8.0",
"jest": "^27.2.5", "jest": "^27.3.1",
"js-file-download": "^0.4.12", "js-file-download": "^0.4.12",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mini-css-extract-plugin": "^2.4.2", "mini-css-extract-plugin": "^2.4.3",
"mobx": "^6.3.3", "mobx": "^6.3.5",
"mobx-react": "^7.2.0", "mobx-react": "^7.2.1",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"nedb-promises": "^5.0.1", "nedb-promises": "^5.0.1",
"overlayscrollbars": "^1.13.1", "overlayscrollbars": "^1.13.1",
@@ -177,8 +171,8 @@
"passport": "^0.5.0", "passport": "^0.5.0",
"passport-jwt": "^4.0.0", "passport-jwt": "^4.0.0",
"polished": "^4.1.3", "polished": "^4.1.3",
"postcss": "^8.3.9", "postcss": "^8.3.11",
"postcss-loader": "^6.1.1", "postcss-loader": "^6.2.0",
"prettier": "^2.4.1", "prettier": "^2.4.1",
"promise": "^8.1.0", "promise": "^8.1.0",
"query-string": "^6.14.1", "query-string": "^6.14.1",
@@ -200,29 +194,27 @@
"react-window": "^1.8.6", "react-window": "^1.8.6",
"ress": "^4.0.0", "ress": "^4.0.0",
"sanitize-filename": "^1.6.3", "sanitize-filename": "^1.6.3",
"sass": "^1.42.1", "sass": "^1.43.3",
"sass-loader": "^12.1.0", "sass-loader": "^12.2.0",
"saxen": "^8.1.2", "saxen": "^8.1.2",
"source-map-loader": "^3.0.0", "source-map-loader": "^3.0.0",
"spdy": "^4.0.2", "spdy": "^4.0.2",
"style-loader": "^3.3.0", "style-loader": "^3.3.1",
"supertest": "^6.1.6", "supertest": "^6.1.6",
"tar-fs": "^2.1.1", "tar-fs": "^2.1.1",
"tar-stream": "^2.2.0",
"terser-webpack-plugin": "^5.2.4", "terser-webpack-plugin": "^5.2.4",
"tldts": "^5.7.47", "tldts": "^5.7.49",
"ts-jest": "^27.0.5", "ts-jest": "^27.0.7",
"ts-node-dev": "^1.1.8", "ts-node-dev": "^1.1.8",
"tsconfig-paths": "^3.11.0", "tsconfig-paths": "^3.11.0",
"typed-emitter": "^1.3.1", "typed-emitter": "^1.4.0",
"typescript": "~4.4.3", "typescript": "~4.4.4",
"url-loader": "^4.1.1",
"use-query-params": "^1.2.3", "use-query-params": "^1.2.3",
"webpack": "^5.58.1", "webpack": "^5.59.1",
"webpack-dev-server": "^4.3.1", "webpack-dev-server": "^4.3.1",
"webpackbar": "^5.0.0-3", "webpackbar": "^5.0.0-3",
"yargs": "^17.2.1", "yargs": "^17.2.1",
"zod": "^3.9.8" "zod": "^3.11.2"
}, },
"engines": { "engines": {
"node": ">=12.0.0", "node": ">=12.0.0",

View File

@@ -9,7 +9,7 @@ import {contentTokenSchema} from '@shared/schema/api/torrents';
import type {FloodSettings} from '@shared/types/FloodSettings'; import type {FloodSettings} from '@shared/types/FloodSettings';
import type {HistorySnapshot} from '@shared/constants/historySnapshotTypes'; import type {HistorySnapshot} from '@shared/constants/historySnapshotTypes';
import type {NotificationFetchOptions, NotificationState} from '@shared/types/Notification'; import type {NotificationFetchOptions, NotificationState} from '@shared/types/Notification';
import type {SetFloodSettingsOptions} from '@shared/types/api/index'; import type {DirectoryListQuery, DirectoryListResponse, SetFloodSettingsOptions} from '@shared/types/api/index';
import {accessDeniedError, isAllowedPath, sanitizePath} from '../../util/fileUtil'; import {accessDeniedError, isAllowedPath, sanitizePath} from '../../util/fileUtil';
import appendUserServices from '../../middleware/appendUserServices'; import appendUserServices from '../../middleware/appendUserServices';
@@ -100,9 +100,9 @@ router.get('/activity-stream', eventStream, clientActivityStream);
* @return {Error} 422 - invalid argument - application/json * @return {Error} 422 - invalid argument - application/json
* @return {Error} 500 - other errors - application/json * @return {Error} 500 - other errors - application/json
*/ */
router.get<unknown, unknown, unknown, {path: string}>( router.get<unknown, unknown, unknown, DirectoryListQuery>(
'/directory-list', '/directory-list',
async (req, res): Promise<Response<unknown>> => { async (req, res): Promise<Response<DirectoryListResponse>> => {
const {path: inputPath} = req.query; const {path: inputPath} = req.query;
if (typeof inputPath !== 'string' || !inputPath) { if (typeof inputPath !== 'string' || !inputPath) {

View File

@@ -907,13 +907,9 @@ router.get<{hash: string}>(
'mediainfo', 'mediainfo',
torrentContentPaths, torrentContentPaths,
{maxBuffer: 1024 * 2000, timeout: 1000 * 10}, {maxBuffer: 1024 * 2000, timeout: 1000 * 10},
(error, stdout, stderr) => { (error, stdout) => {
if (error) { if (error) {
return res.status(500).json({message: error.message}); return res.status(500).json({code: error.code, message: error.message});
}
if (stderr) {
return res.status(500).json({message: stderr});
} }
return res.status(200).json({output: stdout}); return res.status(200).json({output: stdout});

View File

@@ -18,49 +18,50 @@ import {
TransmissionTorrentsSetLocationArguments, TransmissionTorrentsSetLocationArguments,
} from './types/TransmissionTorrentsMethods'; } from './types/TransmissionTorrentsMethods';
type TransmissionRPCResponse<T = undefined> = {
result: 'success';
arguments: T;
} & {
result: string;
};
class ClientRequestManager { class ClientRequestManager {
private rpcURL: string; private rpcURL: string;
private authHeader: string; private authHeader: string;
private sessionID?: Promise<string | undefined>; private sessionID?: Promise<string | undefined>;
async fetchSessionID(url = this.rpcURL, authHeader = this.authHeader): Promise<string | undefined> { async fetchSessionID(url = this.rpcURL, authHeader = this.authHeader): Promise<string | undefined> {
return axios let id: string | undefined = undefined;
await axios
.get(url, { .get(url, {
headers: { headers: {
Authorization: authHeader, Authorization: authHeader,
}, },
}) })
.then<string | undefined>( .catch((err: AxiosError) => {
() => { if (err.response?.status !== 409) {
return undefined;
},
(err: AxiosError) => {
if (err.response?.status === 409) {
return err.response?.headers['x-transmission-session-id'];
}
throw err; throw err;
}, }
); id = err.response?.headers['x-transmission-session-id'];
});
return id;
} }
async updateSessionID(url = this.rpcURL, authHeader = this.authHeader): Promise<void> { async updateSessionID(url = this.rpcURL, authHeader = this.authHeader): Promise<void> {
let authFailed = false; let authFailed = false;
this.sessionID = new Promise((resolve) => { this.sessionID = this.fetchSessionID(url, authHeader).catch(() => {
this.fetchSessionID(url, authHeader).then(
(sessionID) => {
resolve(sessionID);
},
() => {
authFailed = true; authFailed = true;
resolve(undefined); return undefined;
},
);
}); });
await this.sessionID; await this.sessionID;
return authFailed ? Promise.reject(new Error()) : Promise.resolve(); if (authFailed) {
throw new Error();
}
} }
async getRequestHeaders(): Promise<Record<string, string>> { async getRequestHeaders(): Promise<Record<string, string>> {
@@ -74,18 +75,18 @@ class ClientRequestManager {
async getSessionStats(): Promise<TransmissionSessionStats> { async getSessionStats(): Promise<TransmissionSessionStats> {
return axios return axios
.post( .post<TransmissionRPCResponse<TransmissionSessionStats>>(
this.rpcURL, this.rpcURL,
{method: 'session-stats'}, {method: 'session-stats'},
{ {
headers: await this.getRequestHeaders(), headers: await this.getRequestHeaders(),
}, },
) )
.then((res) => { .then(({data}) => {
if (res.data.result !== 'success') { if (data.result !== 'success') {
throw new Error(); throw new Error();
} }
return res.data.arguments; return data.arguments;
}); });
} }
@@ -95,32 +96,32 @@ class ClientRequestManager {
const sessionGetArguments: TransmissionSessionGetArguments = {fields}; const sessionGetArguments: TransmissionSessionGetArguments = {fields};
return axios return axios
.post( .post<TransmissionRPCResponse<Pick<TransmissionSessionProperties, T[number]>>>(
this.rpcURL, this.rpcURL,
{method: 'session-get', arguments: sessionGetArguments}, {method: 'session-get', arguments: sessionGetArguments},
{ {
headers: await this.getRequestHeaders(), headers: await this.getRequestHeaders(),
}, },
) )
.then((res) => { .then(({data}) => {
if (res.data.result !== 'success') { if (data.result !== 'success') {
throw new Error(); throw new Error();
} }
return res.data.arguments; return data.arguments;
}); });
} }
async setSessionProperties(properties: TransmissionSessionSetArguments): Promise<void> { async setSessionProperties(properties: TransmissionSessionSetArguments): Promise<void> {
return axios return axios
.post( .post<TransmissionRPCResponse>(
this.rpcURL, this.rpcURL,
{method: 'session-set', arguments: properties}, {method: 'session-set', arguments: properties},
{ {
headers: await this.getRequestHeaders(), headers: await this.getRequestHeaders(),
}, },
) )
.then((res) => { .then(({data}) => {
if (res.data.result !== 'success') { if (data.result !== 'success') {
throw new Error(); throw new Error();
} }
}); });
@@ -137,18 +138,18 @@ class ClientRequestManager {
}; };
return axios return axios
.post( .post<TransmissionRPCResponse<{torrents: Array<Pick<TransmissionTorrentProperties, T[number]>>}>>(
this.rpcURL, this.rpcURL,
{method: 'torrent-get', arguments: torrentsGetArguments}, {method: 'torrent-get', arguments: torrentsGetArguments},
{ {
headers: await this.getRequestHeaders(), headers: await this.getRequestHeaders(),
}, },
) )
.then((res) => { .then(({data}) => {
if (res.data.result !== 'success' || res.data.arguments.torrents == null) { if (data.result !== 'success' || data.arguments.torrents == null) {
throw new Error(); throw new Error();
} }
return res.data.arguments.torrents; return data.arguments.torrents;
}); });
} }
@@ -156,32 +157,34 @@ class ClientRequestManager {
args: TransmissionTorrentAddArguments, args: TransmissionTorrentAddArguments,
): Promise<Pick<TransmissionTorrentProperties, 'id' | 'name' | 'hashString'>> { ): Promise<Pick<TransmissionTorrentProperties, 'id' | 'name' | 'hashString'>> {
return axios return axios
.post( .post<
TransmissionRPCResponse<{'torrent-added'?: Pick<TransmissionTorrentProperties, 'id' | 'name' | 'hashString'>}>
>(
this.rpcURL, this.rpcURL,
{method: 'torrent-add', arguments: args}, {method: 'torrent-add', arguments: args},
{ {
headers: await this.getRequestHeaders(), headers: await this.getRequestHeaders(),
}, },
) )
.then((res) => { .then(({data}) => {
if (res.data.result !== 'success') { if (data.result !== 'success' || !data.arguments['torrent-added']) {
throw new Error(); throw new Error();
} }
return res.data.arguments['torrent-added']; return data.arguments['torrent-added'];
}); });
} }
async reannounceTorrents(ids: TransmissionTorrentIDs): Promise<void> { async reannounceTorrents(ids: TransmissionTorrentIDs): Promise<void> {
return axios return axios
.post( .post<TransmissionRPCResponse>(
this.rpcURL, this.rpcURL,
{method: 'torrent-reannounce', arguments: {ids}}, {method: 'torrent-reannounce', arguments: {ids}},
{ {
headers: await this.getRequestHeaders(), headers: await this.getRequestHeaders(),
}, },
) )
.then((res) => { .then(({data}) => {
if (res.data.result !== 'success') { if (data.result !== 'success') {
throw new Error(); throw new Error();
} }
}); });
@@ -189,15 +192,15 @@ class ClientRequestManager {
async setTorrentsProperties(args: TransmissionTorrentsSetArguments): Promise<void> { async setTorrentsProperties(args: TransmissionTorrentsSetArguments): Promise<void> {
return axios return axios
.post( .post<TransmissionRPCResponse>(
this.rpcURL, this.rpcURL,
{method: 'torrent-set', arguments: args}, {method: 'torrent-set', arguments: args},
{ {
headers: await this.getRequestHeaders(), headers: await this.getRequestHeaders(),
}, },
) )
.then((res) => { .then(({data}) => {
if (res.data.result !== 'success') { if (data.result !== 'success') {
throw new Error(); throw new Error();
} }
}); });
@@ -205,15 +208,15 @@ class ClientRequestManager {
async startTorrents(ids: TransmissionTorrentIDs): Promise<void> { async startTorrents(ids: TransmissionTorrentIDs): Promise<void> {
return axios return axios
.post( .post<TransmissionRPCResponse>(
this.rpcURL, this.rpcURL,
{method: 'torrent-start-now', arguments: {ids}}, {method: 'torrent-start-now', arguments: {ids}},
{ {
headers: await this.getRequestHeaders(), headers: await this.getRequestHeaders(),
}, },
) )
.then((res) => { .then(({data}) => {
if (res.data.result !== 'success') { if (data.result !== 'success') {
throw new Error(); throw new Error();
} }
}); });
@@ -221,15 +224,15 @@ class ClientRequestManager {
async stopTorrents(ids: TransmissionTorrentIDs): Promise<void> { async stopTorrents(ids: TransmissionTorrentIDs): Promise<void> {
return axios return axios
.post( .post<TransmissionRPCResponse>(
this.rpcURL, this.rpcURL,
{method: 'torrent-stop', arguments: {ids}}, {method: 'torrent-stop', arguments: {ids}},
{ {
headers: await this.getRequestHeaders(), headers: await this.getRequestHeaders(),
}, },
) )
.then((res) => { .then(({data}) => {
if (res.data.result !== 'success') { if (data.result !== 'success') {
throw new Error(); throw new Error();
} }
}); });
@@ -237,15 +240,15 @@ class ClientRequestManager {
async verifyTorrents(ids: TransmissionTorrentIDs): Promise<void> { async verifyTorrents(ids: TransmissionTorrentIDs): Promise<void> {
return axios return axios
.post( .post<TransmissionRPCResponse>(
this.rpcURL, this.rpcURL,
{method: 'torrent-verify', arguments: {ids}}, {method: 'torrent-verify', arguments: {ids}},
{ {
headers: await this.getRequestHeaders(), headers: await this.getRequestHeaders(),
}, },
) )
.then((res) => { .then(({data}) => {
if (res.data.result !== 'success') { if (data.result !== 'success') {
throw new Error(); throw new Error();
} }
}); });
@@ -258,15 +261,15 @@ class ClientRequestManager {
}; };
return axios return axios
.post( .post<TransmissionRPCResponse>(
this.rpcURL, this.rpcURL,
{method: 'torrent-remove', arguments: removeTorrentsArguments}, {method: 'torrent-remove', arguments: removeTorrentsArguments},
{ {
headers: await this.getRequestHeaders(), headers: await this.getRequestHeaders(),
}, },
) )
.then((res) => { .then(({data}) => {
if (res.data.result !== 'success') { if (data.result !== 'success') {
throw new Error(); throw new Error();
} }
}); });
@@ -280,7 +283,7 @@ class ClientRequestManager {
}; };
return axios return axios
.post( .post<TransmissionRPCResponse>(
this.rpcURL, this.rpcURL,
{ {
method: 'torrent-set-location', method: 'torrent-set-location',
@@ -290,8 +293,8 @@ class ClientRequestManager {
headers: await this.getRequestHeaders(), headers: await this.getRequestHeaders(),
}, },
) )
.then((res) => { .then(({data}) => {
if (res.data.result !== 'success') { if (data.result !== 'success') {
throw new Error(); throw new Error();
} }
}); });

View File

@@ -34,7 +34,7 @@ const EMPTY_SERVER_STATE = {
class ClientRequestManager { class ClientRequestManager {
private connectionSettings: QBittorrentConnectionSettings; private connectionSettings: QBittorrentConnectionSettings;
private apiBase: string; private apiBase: string;
private authCookie?: Promise<string | undefined>; private authCookie: Promise<string | undefined> = Promise.resolve(undefined);
private isMainDataPending = false; private isMainDataPending = false;
private syncRids: { private syncRids: {
@@ -66,7 +66,7 @@ class ClientRequestManager {
}, },
}) })
.then((res) => { .then((res) => {
const cookies: Array<string> = res.headers['set-cookie']; const cookies = res.headers['set-cookie'];
if (Array.isArray(cookies)) { if (Array.isArray(cookies)) {
return cookies.filter((cookie) => cookie.includes('SID='))[0]; return cookies.filter((cookie) => cookie.includes('SID='))[0];
@@ -79,35 +79,37 @@ class ClientRequestManager {
async updateAuthCookie(connectionSettings?: QBittorrentConnectionSettings): Promise<void> { async updateAuthCookie(connectionSettings?: QBittorrentConnectionSettings): Promise<void> {
let authFailed = false; let authFailed = false;
this.authCookie = new Promise((resolve) => { this.authCookie = this.authenticate(connectionSettings).catch(() => {
this.authenticate(connectionSettings).then(
(authCookie) => {
resolve(authCookie);
},
() => {
authFailed = true; authFailed = true;
resolve(undefined); return undefined;
},
);
}); });
await this.authCookie; await this.authCookie;
return authFailed ? Promise.reject(new Error()) : Promise.resolve(); if (authFailed) {
throw new Error();
}
}
async getRequestHeaders(): Promise<Record<string, string>> {
const Cookie = await this.authCookie;
return {
...(Cookie == null ? {} : {Cookie}),
};
} }
async getAppPreferences(): Promise<QBittorrentAppPreferences> { async getAppPreferences(): Promise<QBittorrentAppPreferences> {
return axios return axios
.get(`${this.apiBase}/app/preferences`, { .get<QBittorrentAppPreferences>(`${this.apiBase}/app/preferences`, {
headers: {Cookie: await this.authCookie}, headers: await this.getRequestHeaders(),
}) })
.then((json) => json.data); .then((res) => res.data);
} }
async setAppPreferences(preferences: Partial<QBittorrentAppPreferences>): Promise<void> { async setAppPreferences(preferences: Partial<QBittorrentAppPreferences>): Promise<void> {
return axios return axios
.post(`${this.apiBase}/app/setPreferences`, `json=${JSON.stringify(preferences)}`, { .post(`${this.apiBase}/app/setPreferences`, `json=${JSON.stringify(preferences)}`, {
headers: {Cookie: await this.authCookie}, headers: await this.getRequestHeaders(),
}) })
.then(() => { .then(() => {
// returns nothing // returns nothing
@@ -116,55 +118,55 @@ class ClientRequestManager {
async getTorrentInfos(): Promise<QBittorrentTorrentInfos> { async getTorrentInfos(): Promise<QBittorrentTorrentInfos> {
return axios return axios
.get(`${this.apiBase}/torrents/info`, { .get<QBittorrentTorrentInfos>(`${this.apiBase}/torrents/info`, {
headers: {Cookie: await this.authCookie}, headers: await this.getRequestHeaders(),
}) })
.then((json) => json.data); .then((res) => res.data);
} }
async getTorrentContents(hash: string): Promise<QBittorrentTorrentContents> { async getTorrentContents(hash: string): Promise<QBittorrentTorrentContents> {
return axios return axios
.get(`${this.apiBase}/torrents/files`, { .get<QBittorrentTorrentContents>(`${this.apiBase}/torrents/files`, {
params: { params: {
hash: hash.toLowerCase(), hash: hash.toLowerCase(),
}, },
headers: {Cookie: await this.authCookie}, headers: await this.getRequestHeaders(),
}) })
.then((json) => json.data); .then((res) => res.data);
} }
async getTorrentProperties(hash: string): Promise<QBittorrentTorrentProperties> { async getTorrentProperties(hash: string): Promise<QBittorrentTorrentProperties> {
return axios return axios
.get(`${this.apiBase}/torrents/properties`, { .get<QBittorrentTorrentProperties>(`${this.apiBase}/torrents/properties`, {
params: { params: {
hash: hash.toLowerCase(), hash: hash.toLowerCase(),
}, },
headers: {Cookie: await this.authCookie}, headers: await this.getRequestHeaders(),
}) })
.then((json) => json.data); .then((res) => res.data);
} }
async getTorrentTrackers(hash: string): Promise<QBittorrentTorrentTrackers> { async getTorrentTrackers(hash: string): Promise<QBittorrentTorrentTrackers> {
return axios return axios
.get(`${this.apiBase}/torrents/trackers`, { .get<QBittorrentTorrentTrackers>(`${this.apiBase}/torrents/trackers`, {
params: { params: {
hash: hash.toLowerCase(), hash: hash.toLowerCase(),
}, },
headers: {Cookie: await this.authCookie}, headers: await this.getRequestHeaders(),
}) })
.then((json) => json.data); .then((res) => res.data);
} }
async getTransferInfo(): Promise<QBittorrentTransferInfo> { async getTransferInfo(): Promise<QBittorrentTransferInfo> {
return axios return axios
.get(`${this.apiBase}/transfer/info`, { .get<QBittorrentTransferInfo>(`${this.apiBase}/transfer/info`, {
headers: {Cookie: await this.authCookie}, headers: await this.getRequestHeaders(),
}) })
.then((json) => json.data); .then((res) => res.data);
} }
async syncMainData(): Promise<QBittorrentMainData> { async syncMainData(): Promise<QBittorrentMainData> {
const Cookie = await this.authCookie; const headers = await this.getRequestHeaders();
if (this.isMainDataPending == false) { if (this.isMainDataPending == false) {
this.isMainDataPending = true; this.isMainDataPending = true;
@@ -174,7 +176,7 @@ class ClientRequestManager {
params: { params: {
rid, rid,
}, },
headers: {Cookie}, headers,
}) })
.then(({data}) => { .then(({data}) => {
const { const {
@@ -268,9 +270,9 @@ class ClientRequestManager {
hash: hash.toLowerCase(), hash: hash.toLowerCase(),
rid: 0, rid: 0,
}, },
headers: {Cookie: await this.authCookie}, headers: await this.getRequestHeaders(),
}) })
.then(({data}) => data.peers as QBittorrentTorrentPeers); .then(({data}) => data.peers);
} }
async torrentsPause(hashes: Array<string>): Promise<void> { async torrentsPause(hashes: Array<string>): Promise<void> {
@@ -279,7 +281,7 @@ class ClientRequestManager {
params: { params: {
hashes: hashes.join('|').toLowerCase(), hashes: hashes.join('|').toLowerCase(),
}, },
headers: {Cookie: await this.authCookie}, headers: await this.getRequestHeaders(),
}) })
.then(() => { .then(() => {
// returns nothing // returns nothing
@@ -292,7 +294,7 @@ class ClientRequestManager {
params: { params: {
hashes: hashes.join('|').toLowerCase(), hashes: hashes.join('|').toLowerCase(),
}, },
headers: {Cookie: await this.authCookie}, headers: await this.getRequestHeaders(),
}) })
.then(() => { .then(() => {
// returns nothing // returns nothing
@@ -306,7 +308,7 @@ class ClientRequestManager {
hashes: hashes.join('|').toLowerCase(), hashes: hashes.join('|').toLowerCase(),
deleteFiles: deleteFiles ? 'true' : 'false', deleteFiles: deleteFiles ? 'true' : 'false',
}, },
headers: {Cookie: await this.authCookie}, headers: await this.getRequestHeaders(),
}) })
.then(() => { .then(() => {
// returns nothing // returns nothing
@@ -319,7 +321,7 @@ class ClientRequestManager {
params: { params: {
hashes: hashes.join('|').toLowerCase(), hashes: hashes.join('|').toLowerCase(),
}, },
headers: {Cookie: await this.authCookie}, headers: await this.getRequestHeaders(),
}) })
.then(() => { .then(() => {
// returns nothing // returns nothing
@@ -333,7 +335,7 @@ class ClientRequestManager {
hashes: hashes.join('|').toLowerCase(), hashes: hashes.join('|').toLowerCase(),
location, location,
}, },
headers: {Cookie: await this.authCookie}, headers: await this.getRequestHeaders(),
}) })
.then(() => { .then(() => {
// returns nothing // returns nothing
@@ -346,7 +348,7 @@ class ClientRequestManager {
params: { params: {
hashes: hashes.join('|').toLowerCase(), hashes: hashes.join('|').toLowerCase(),
}, },
headers: {Cookie: await this.authCookie}, headers: await this.getRequestHeaders(),
}) })
.then(() => { .then(() => {
// returns nothing // returns nothing
@@ -359,7 +361,7 @@ class ClientRequestManager {
params: { params: {
hashes: hashes.join('|').toLowerCase(), hashes: hashes.join('|').toLowerCase(),
}, },
headers: {Cookie: await this.authCookie}, headers: await this.getRequestHeaders(),
}) })
.then(() => { .then(() => {
// returns nothing // returns nothing
@@ -426,7 +428,7 @@ class ClientRequestManager {
hashes: hashes.join('|').toLowerCase(), hashes: hashes.join('|').toLowerCase(),
tags: tags.join(','), tags: tags.join(','),
}, },
headers: {Cookie: await this.authCookie}, headers: await this.getRequestHeaders(),
}) })
.then(() => { .then(() => {
// returns nothing // returns nothing
@@ -440,7 +442,7 @@ class ClientRequestManager {
hashes: hashes.join('|').toLowerCase(), hashes: hashes.join('|').toLowerCase(),
tags: tags?.join(','), tags: tags?.join(','),
}, },
headers: {Cookie: await this.authCookie}, headers: await this.getRequestHeaders(),
}) })
.then(() => { .then(() => {
// returns nothing // returns nothing
@@ -455,7 +457,7 @@ class ClientRequestManager {
hash: hash.toLowerCase(), hash: hash.toLowerCase(),
urls: urls.join('\n'), urls: urls.join('\n'),
}, },
headers: {Cookie: await this.authCookie}, headers: await this.getRequestHeaders(),
}) })
.then(() => { .then(() => {
// returns nothing // returns nothing
@@ -470,7 +472,7 @@ class ClientRequestManager {
params: { params: {
hashes: hashes.join('|').toLowerCase(), hashes: hashes.join('|').toLowerCase(),
}, },
headers: {Cookie: await this.authCookie}, headers: await this.getRequestHeaders(),
}) })
.then(() => { .then(() => {
// returns nothing // returns nothing
@@ -486,7 +488,7 @@ class ClientRequestManager {
hash: hash.toLowerCase(), hash: hash.toLowerCase(),
urls: urls.join('|'), urls: urls.join('|'),
}, },
headers: {Cookie: await this.authCookie}, headers: await this.getRequestHeaders(),
}) })
.then(() => { .then(() => {
// returns nothing // returns nothing
@@ -502,7 +504,7 @@ class ClientRequestManager {
hashes: hashes.join('|').toLowerCase(), hashes: hashes.join('|').toLowerCase(),
value: value ? 'true' : 'false', value: value ? 'true' : 'false',
}, },
headers: {Cookie: await this.authCookie}, headers: await this.getRequestHeaders(),
}) })
.then(() => { .then(() => {
// returns nothing // returns nothing
@@ -517,7 +519,7 @@ class ClientRequestManager {
params: { params: {
hashes: hashes.join('|').toLowerCase(), hashes: hashes.join('|').toLowerCase(),
}, },
headers: {Cookie: await this.authCookie}, headers: await this.getRequestHeaders(),
}) })
.then(() => { .then(() => {
// returns nothing // returns nothing
@@ -533,7 +535,7 @@ class ClientRequestManager {
id: ids.join('|'), id: ids.join('|'),
priority, priority,
}, },
headers: {Cookie: await this.authCookie}, headers: await this.getRequestHeaders(),
}) })
.then(() => { .then(() => {
// returns nothing // returns nothing

View File

@@ -50,7 +50,7 @@ export type QBittorrentMainData = Required<
export interface QBittorrentSyncTorrentPeers { export interface QBittorrentSyncTorrentPeers {
rid: number; rid: number;
peers?: { peers: {
[ip_and_port: string]: QBittorrentTorrentPeer; [ip_and_port: string]: QBittorrentTorrentPeer;
}; };
peers_removed?: string[]; peers_removed?: string[];

View File

@@ -1,4 +1,4 @@
import axios, {AxiosError, AxiosResponse} from 'axios'; import axios, {AxiosError} from 'axios';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
@@ -26,7 +26,7 @@ export const fetchUrls = async (
} }
: undefined, : undefined,
}).then( }).then(
(res: AxiosResponse) => res.data, (res) => res.data,
(e: AxiosError) => console.error(e), (e: AxiosError) => console.error(e),
); );

View File

@@ -25,6 +25,12 @@ export interface AuthAuthenticationResponse {
export const authRegistrationSchema = credentialsSchema; export const authRegistrationSchema = credentialsSchema;
export type AuthRegistrationOptions = Required<zodInfer<typeof authRegistrationSchema>>; export type AuthRegistrationOptions = Required<zodInfer<typeof authRegistrationSchema>>;
// POST /api/auth/register - success response
export interface AuthRegistrationResponse {
username: string;
level: AccessLevel;
}
// PATCH /api/auth/users/{username} // PATCH /api/auth/users/{username}
export const authUpdateUserSchema = credentialsSchema.partial(); export const authUpdateUserSchema = credentialsSchema.partial();
export type AuthUpdateUserOptions = zodInfer<typeof authUpdateUserSchema>; export type AuthUpdateUserOptions = zodInfer<typeof authUpdateUserSchema>;

View File

@@ -2,3 +2,16 @@ import type {FloodSettings} from '../FloodSettings';
// PATCH /api/settings // PATCH /api/settings
export type SetFloodSettingsOptions = Partial<FloodSettings>; export type SetFloodSettingsOptions = Partial<FloodSettings>;
// GET /api/directory-list
export interface DirectoryListQuery {
path: string;
}
// GET /api/directory-list - success response
export interface DirectoryListResponse {
path: string;
separator: string;
directories: string[];
files: string[];
}

View File

@@ -104,3 +104,13 @@ export interface SetTorrentContentsPropertiesOptions {
// Number representing priority // Number representing priority
priority: TorrentContentPriority; priority: TorrentContentPriority;
} }
// GET /api/torrents/{hash}/mediainfo - success response
export interface TorrentMediainfoResponse {
output: string;
}
// POST /api/torrents/add-urls - success response
// POST /api/torrents/add-files - success response
// hashes of torrents added
export type TorrentAddResponse = string[];