Compare commits

..

29 Commits

Author SHA1 Message Date
0xsysr3ll
275d6aaf08 fix(webpush): rework web push notification status verification logic
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
2025-12-09 00:04:28 +01:00
0xsysr3ll
9d41ecfecc fix(webpush): ensure the old endpoint is cleared only when necessary
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
2025-12-08 23:51:41 +01:00
0xsysr3ll
3e0e02a7ea feat(push-subscription): add unique constraint on endpoint and userId
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
2025-12-08 23:50:52 +01:00
0xsysr3ll
002f4aeadd fix(webpush): only remove the current browser's subscription
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
2025-12-08 22:10:04 +01:00
0xsysr3ll
9f97ab1d60 fix(webpush): remove error throw
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
2025-12-08 22:10:04 +01:00
0xsysr3ll
a47b8db48f fix(webpush): ensure the local storage reflects the correct notification status
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
2025-12-08 22:10:04 +01:00
0xsysr3ll
bd52d1fa9d refactor(webpush): remove redundant checks
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
2025-12-08 22:10:04 +01:00
0xsysr3ll
07938a6fe9 refactor(webpush): remove redundant try-catch
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
2025-12-08 22:10:04 +01:00
0xsysr3ll
9180d178ba fix(webpush): throw error after notification failure
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
2025-12-08 22:10:04 +01:00
0xsysr3ll
94219195e6 fix(webpush): notification must reflect the actual outcome
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
2025-12-08 22:10:04 +01:00
0xsysr3ll
b41a0b3b95 fix(webpush): remove backend checks
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
2025-12-08 22:10:04 +01:00
0xsysr3ll
f606a64684 fix(webpush): delete push subscriptions for multiple devices
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
2025-12-08 22:10:04 +01:00
0xsysr3ll
3886e649f9 fix(webpush): remove redundant backend subscription checks
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
2025-12-08 22:10:04 +01:00
0xsysr3ll
b6373498c3 fix(webpush): remove unnecessary dependency for user ID verification
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
2025-12-08 22:10:04 +01:00
0xsysr3ll
8f1b81becc fix(webpush): update localStorage handling for push notification status
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
2025-12-08 22:10:04 +01:00
0xsysr3ll
44b34a0081 fix(webpush): update existing subscriptions with new keys only if the endpoint matches and the auth differs
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
2025-12-08 22:10:04 +01:00
0xsysr3ll
194e33a19a fix(webpush): remove the redundant userId check
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
2025-12-08 22:10:04 +01:00
0xsysr3ll
2447c385f4 fix(webpush): add user ID validation to push subscription verification
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
2025-12-08 22:10:04 +01:00
0xsysr3ll
432e970de4 refactor(webpush): Remove nested error checks
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
2025-12-08 22:10:04 +01:00
0xsysr3ll
13edfe36a6 fix(webpush): add backend subscription check to determine if a valid push subscription exists.
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
2025-12-08 22:10:04 +01:00
0xsysr3ll
4dbb7cdf2d fix(webpush): store push notification status in localStorage
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
2025-12-08 22:10:04 +01:00
0xsysr3ll
bde07e02c1 fix(webpush): use transaction for race condition prevention
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
2025-12-08 22:10:04 +01:00
0xsysr3ll
8c1ce8565d fix(webpush): preserve original creation timestamp when updating subscriptions
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
2025-12-08 22:10:04 +01:00
0xsysr3ll
13c0f33c0a fix(webpush): cleanup is too agressive - avoid removing active subscriptions
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
2025-12-08 22:10:04 +01:00
0xsysr3ll
4e9264a31d fix(webpush): clean up stale push subscriptions for the same device
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
2025-12-08 22:10:04 +01:00
0xsysr3ll
3a9f6cd669 fix(webpush): update existing subscriptions with new keys
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
2025-12-08 22:10:04 +01:00
0xsysr3ll
f1f7d6af3a fix(webpush): add logs for AggregateError error
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
2025-12-08 22:10:04 +01:00
0xsysr3ll
caa1716374 fix(webpush): improve push notification error handling
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
2025-12-08 22:10:04 +01:00
0xsysr3ll
036c006aab fix(webpush): improve iOS push subscription endpoint cleanup
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
2025-12-08 22:10:04 +01:00
13 changed files with 1911 additions and 2534 deletions

View File

@@ -8,7 +8,7 @@
<p align="center">
<a href="https://discord.gg/seerr"><img src="https://img.shields.io/discord/783137440809746482" alt="Discord"></a>
<a href="https://hub.docker.com/r/seerr/seerr"><img src="https://img.shields.io/docker/pulls/seerr/seerr" alt="Docker pulls"></a>
<a href="https://translate.seerr.dev/engage/seerr/"><img src="https://translate.seerr.dev/widget/seerr/svg-badge.svg" alt="Translation status" /></a>
<a href="https://translate.seerr.dev/engage/seerr/"><img src="https://translate.seerr.dev/widget/seerr/seerr-frontend/svg-badge.svg" alt="Translation status" /></a>
<a href="https://github.com/seerr-team/seerr/blob/develop/LICENSE"><img alt="GitHub" src="https://img.shields.io/github/license/seerr-team/seerr"></a>
**Seerr** is a free and open source software application for managing requests for your media library. It integrates with the media server of your choice: [Jellyfin](https://jellyfin.org), [Plex](https://plex.tv), and [Emby](https://emby.media/). In addition, it integrates with your existing services, such as **[Sonarr](https://sonarr.tv/)**, **[Radarr](https://radarr.video/)**.

View File

@@ -174,36 +174,4 @@ This can happen if you have a new installation of Jellyfin/Emby or if you have c
This process should restore your admin privileges while preserving your settings.
## Failed to enable web push notifications
### Option 1: You are using Pi-hole
When using Pi-hole, you need to whitelist the proper domains in order for the queries to not be intercepted and blocked by Pi-hole.
If you are using a chromium based browser (eg: Chrome, Brave, Edge...), the domain you need to whitelist is `fcm.googleapis.com`
If you are using Firefox, the domain you need to whitelist is `push.services.mozilla.com`
1. Log into your Pi-hole through the admin interface, then click on Domains situated under GROUP MANAGEMENT.
2. Add the domain corresponding to your browser in the `Domain to be added` field and then click on Add to allowed domains.
3. Now in order for those changes to be used you need to flush your current dns cache.
4. You can do so by using this command line in your Pi-hole terminal:
```bash
pihole restartdns
```
If this command fails (which is unlikely), use this equivalent:
```bash
pihole -f && pihole restartdns
```
5. Then restart your Seerr instance and try to enable the web push notifications again.
### Option 2: You are using Brave browser
Brave is a "De-Googled" browser. So by default or if you refused a prompt in the past, it cuts the access to the FCM (Firebase Cloud Messaging) service, which is mandatory for the web push notifications on Chromium based browsers.
1. Open Brave and paste this address in the url bar: `brave://settings/privacy`
2. Look for the option: "Use Google services for push messaging"
3. Activate this option
4. Relaunch Brave completely
5. You should now see the notifications prompt appearing instead of an error message.
If you still encounter issues, please reach out on our support channels.

View File

@@ -2,7 +2,7 @@
"name": "seerr",
"version": "0.1.0",
"private": true,
"packageManager": "pnpm@10.24.0",
"packageManager": "pnpm@10.17.1",
"scripts": {
"preinstall": "npx only-allow pnpm",
"postinstall": "node postinstall-win.js",
@@ -33,38 +33,38 @@
},
"license": "MIT",
"dependencies": {
"@dr.pogodin/csurf": "^1.16.6",
"@formatjs/intl-displaynames": "6.8.13",
"@dr.pogodin/csurf": "^1.14.1",
"@formatjs/intl-displaynames": "6.2.6",
"@formatjs/intl-locale": "3.1.1",
"@formatjs/intl-pluralrules": "5.4.6",
"@formatjs/intl-pluralrules": "5.1.10",
"@formatjs/intl-utils": "3.8.4",
"@formatjs/swc-plugin-experimental": "^0.4.0",
"@headlessui/react": "1.7.12",
"@heroicons/react": "2.2.0",
"@heroicons/react": "2.0.16",
"@supercharge/request-ip": "1.2.0",
"@svgr/webpack": "6.5.1",
"@tanem/react-nprogress": "5.0.56",
"@tanem/react-nprogress": "5.0.30",
"@types/ua-parser-js": "^0.7.36",
"@types/wink-jaro-distance": "^2.0.2",
"ace-builds": "1.43.4",
"axios": "1.13.2",
"axios-rate-limit": "1.4.0",
"ace-builds": "1.15.2",
"axios": "1.10.0",
"axios-rate-limit": "1.3.0",
"bcrypt": "5.1.0",
"bowser": "2.13.1",
"bowser": "2.11.0",
"connect-typeorm": "1.1.4",
"cookie-parser": "1.4.7",
"copy-to-clipboard": "3.3.3",
"country-flag-icons": "1.6.4",
"country-flag-icons": "1.5.5",
"cronstrue": "2.23.0",
"date-fns": "2.29.3",
"dayjs": "1.11.19",
"dayjs": "1.11.7",
"dns-caching": "^0.2.7",
"email-templates": "12.0.3",
"email-templates": "12.0.1",
"express": "4.21.2",
"express-openapi-validator": "4.13.8",
"express-rate-limit": "6.7.0",
"express-session": "1.18.2",
"formik": "^2.4.9",
"express-session": "1.17.3",
"formik": "^2.4.6",
"gravatar-url": "3.1.0",
"http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^7.0.6",
@@ -76,19 +76,19 @@
"node-schedule": "2.1.1",
"nodemailer": "6.10.0",
"openpgp": "5.11.2",
"pg": "8.16.3",
"pg": "8.11.0",
"plex-api": "5.3.2",
"pug": "3.0.3",
"react": "^18.3.1",
"react-ace": "10.1.0",
"react-animate-height": "2.1.2",
"react-aria": "3.44.0",
"react-aria": "3.23.0",
"react-dom": "^18.3.1",
"react-intersection-observer": "9.4.3",
"react-intl": "^6.6.8",
"react-markdown": "8.0.5",
"react-popper-tooltip": "4.4.2",
"react-select": "5.10.2",
"react-select": "5.7.0",
"react-spring": "9.7.1",
"react-tailwindcss-datepicker-sct": "1.3.4",
"react-toast-notifications": "2.5.1",
@@ -97,19 +97,19 @@
"react-use-clipboard": "1.0.9",
"reflect-metadata": "0.1.13",
"secure-random-password": "0.2.3",
"semver": "7.7.3",
"semver": "7.7.1",
"sharp": "^0.33.4",
"sqlite3": "5.1.7",
"swagger-ui-express": "4.6.2",
"swr": "2.3.7",
"swr": "2.2.5",
"tailwind-merge": "^2.6.0",
"typeorm": "0.3.12",
"ua-parser-js": "^1.0.35",
"undici": "^7.16.0",
"validator": "^13.15.23",
"web-push": "3.6.7",
"undici": "^7.3.0",
"validator": "^13.15.15",
"web-push": "3.5.0",
"wink-jaro-distance": "^2.0.0",
"winston": "3.18.3",
"winston": "3.8.2",
"winston-daily-rotate-file": "4.7.1",
"xml2js": "0.4.23",
"yamljs": "0.3.0",
@@ -123,33 +123,32 @@
"@tailwindcss/forms": "0.5.10",
"@tailwindcss/typography": "0.5.16",
"@types/bcrypt": "5.0.0",
"@types/cookie-parser": "1.4.10",
"@types/country-flag-icons": "1.2.2",
"@types/csurf": "1.11.5",
"@types/cookie-parser": "1.4.3",
"@types/country-flag-icons": "1.2.0",
"@types/csurf": "1.11.2",
"@types/email-templates": "8.0.4",
"@types/express": "4.17.17",
"@types/express-session": "1.18.2",
"@types/lodash": "4.17.21",
"@types/express-session": "1.17.6",
"@types/lodash": "4.14.191",
"@types/mime": "3",
"@types/node": "22.10.5",
"@types/node-schedule": "2.1.8",
"@types/node-schedule": "2.1.0",
"@types/nodemailer": "6.4.7",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/react-transition-group": "4.4.12",
"@types/react-transition-group": "4.4.5",
"@types/secure-random-password": "0.2.1",
"@types/semver": "7.7.1",
"@types/swagger-ui-express": "4.1.8",
"@types/validator": "^13.15.10",
"@types/web-push": "3.6.4",
"@types/semver": "7.3.13",
"@types/swagger-ui-express": "4.1.3",
"@types/validator": "^13.15.3",
"@types/web-push": "3.3.2",
"@types/xml2js": "0.4.11",
"@types/yamljs": "0.2.31",
"@types/yup": "0.29.14",
"@typescript-eslint/eslint-plugin": "5.54.0",
"@typescript-eslint/parser": "5.54.0",
"autoprefixer": "10.4.22",
"baseline-browser-mapping": "^2.8.32",
"commitizen": "4.3.1",
"autoprefixer": "10.4.13",
"commitizen": "4.3.0",
"copyfiles": "2.4.1",
"cy-mobile-commands": "0.3.0",
"cypress": "14.1.0",
@@ -158,22 +157,22 @@
"eslint-config-next": "^14.2.4",
"eslint-config-prettier": "8.6.0",
"eslint-plugin-formatjs": "4.9.0",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-no-relative-import-paths": "1.6.1",
"eslint-plugin-jsx-a11y": "6.7.1",
"eslint-plugin-no-relative-import-paths": "1.5.2",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-react": "7.37.5",
"eslint-plugin-react": "7.32.2",
"eslint-plugin-react-hooks": "4.6.0",
"husky": "8.0.3",
"lint-staged": "13.1.2",
"nodemon": "3.1.11",
"postcss": "8.5.6",
"nodemon": "3.1.9",
"postcss": "8.4.31",
"prettier": "2.8.4",
"prettier-plugin-organize-imports": "3.2.2",
"prettier-plugin-tailwindcss": "0.2.3",
"tailwindcss": "3.2.7",
"ts-node": "10.9.2",
"tsc-alias": "1.8.16",
"tsconfig-paths": "4.2.0",
"ts-node": "10.9.1",
"tsc-alias": "1.8.2",
"tsconfig-paths": "4.1.2",
"typescript": "4.9.5"
},
"engines": {
@@ -182,7 +181,7 @@
},
"overrides": {
"sqlite3/node-gyp": "8.4.1",
"@types/express-session": "1.18.2"
"@types/express-session": "1.17.6"
},
"config": {
"commitizen": {
@@ -205,11 +204,8 @@
},
"pnpm": {
"onlyBuiltDependencies": [
"@swc/core",
"bcrypt",
"cypress",
"sharp",
"sqlite3"
"sqlite3",
"bcrypt"
]
}
}

4118
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -112,10 +112,6 @@ export interface JellyfinLibraryItemExtended extends JellyfinLibraryItem {
DateCreated?: string;
}
type EpisodeReturn<T> = T extends { includeMediaInfo: true }
? JellyfinLibraryItemExtended[]
: JellyfinLibraryItem[];
export interface JellyfinItemsReponse {
Items: JellyfinLibraryItemExtended[];
TotalRecordCount: number;
@@ -419,22 +415,13 @@ class JellyfinAPI extends ExternalAPI {
}
}
public async getEpisodes<
T extends { includeMediaInfo?: boolean } | undefined = undefined
>(
public async getEpisodes(
seriesID: string,
seasonID: string,
options?: T
): Promise<EpisodeReturn<T>> {
seasonID: string
): Promise<JellyfinLibraryItem[]> {
try {
const episodeResponse = await this.get<any>(
`/Shows/${seriesID}/Episodes`,
{
params: {
seasonId: seasonID,
...(options?.includeMediaInfo && { fields: 'MediaSources' }),
},
}
`/Shows/${seriesID}/Episodes?seasonId=${seasonID}`
);
return episodeResponse.Items.filter(

View File

@@ -374,10 +374,9 @@ class JellyfinScanner {
) ?? []
).length;
const jellyfinSeasons = await this.jfClient.getSeasons(Id);
for (const season of seasons) {
const matchedJellyfinSeason = jellyfinSeasons.find((md) => {
const JellyfinSeasons = await this.jfClient.getSeasons(Id);
const matchedJellyfinSeason = JellyfinSeasons.find((md) => {
if (tvdbSeasonFromAnidb) {
// In AniDB we don't have the concept of seasons,
// we have multiple shows with only Season 1 (and sometimes a season with index 0 for specials).
@@ -398,52 +397,38 @@ class JellyfinScanner {
// Check if we found the matching season and it has all the available episodes
if (matchedJellyfinSeason) {
// If we have a matched Jellyfin season, get its children metadata so we can check details
const episodes = await this.jfClient.getEpisodes(
Id,
matchedJellyfinSeason.Id
);
//Get count of episodes that are HD and 4K
let totalStandard = 0;
let total4k = 0;
if (!this.enable4kShow) {
const episodes = await this.jfClient.getEpisodes(
Id,
matchedJellyfinSeason.Id
);
//use for loop to make sure this loop _completes_ in full
//before the next section
for (const episode of episodes) {
let episodeCount = 1;
for (const episode of episodes) {
let episodeCount = 1;
// count number of combined episodes
if (
episode.IndexNumber !== undefined &&
episode.IndexNumberEnd !== undefined
) {
episodeCount =
episode.IndexNumberEnd - episode.IndexNumber + 1;
}
totalStandard += episodeCount;
// count number of combined episodes
if (
episode.IndexNumber !== undefined &&
episode.IndexNumberEnd !== undefined
) {
episodeCount =
episode.IndexNumberEnd - episode.IndexNumber + 1;
}
} else {
// 4K detection enabled - request media info to check resolution
const episodes = await this.jfClient.getEpisodes(
Id,
matchedJellyfinSeason.Id,
{ includeMediaInfo: true }
);
for (const episode of episodes) {
let episodeCount = 1;
if (!this.enable4kShow) {
totalStandard += episodeCount;
} else {
const ExtendedEpisodeData = await this.jfClient.getItemData(
episode.Id
);
// count number of combined episodes
if (
episode.IndexNumber !== undefined &&
episode.IndexNumberEnd !== undefined
) {
episodeCount =
episode.IndexNumberEnd - episode.IndexNumber + 1;
}
// MediaSources field is included in response when includeMediaInfo is true
// We iterate all MediaSources to detect if episode has both standard AND 4K versions
episode.MediaSources?.some((MediaSource) => {
ExtendedEpisodeData?.MediaSources?.some((MediaSource) => {
return MediaSource.MediaStreams.some((MediaStream) => {
if (MediaStream.Type === 'Video') {
if ((MediaStream.Width ?? 0) >= 2000) {

View File

@@ -626,6 +626,76 @@ authRoutes.post('/local', async (req, res, next) => {
});
}
const mainUser = await userRepository.findOneOrFail({
select: { id: true, plexToken: true, plexId: true },
where: { id: 1 },
});
const mainPlexTv = new PlexTvAPI(mainUser.plexToken ?? '');
if (!user.plexId) {
try {
const plexUsersResponse = await mainPlexTv.getUsers();
const account = plexUsersResponse.MediaContainer.User.find(
(account) =>
account.$.email &&
account.$.email.toLowerCase() === user.email.toLowerCase()
)?.$;
if (
account &&
(await mainPlexTv.checkUserAccess(parseInt(account.id)))
) {
logger.info(
'Found matching Plex user; updating user with Plex data',
{
label: 'API',
ip: req.ip,
email: body.email,
userId: user.id,
plexId: account.id,
plexUsername: account.username,
}
);
user.plexId = parseInt(account.id);
user.avatar = account.thumb;
user.email = account.email;
user.plexUsername = account.username;
user.userType = UserType.PLEX;
await userRepository.save(user);
}
} catch (e) {
logger.error('Something went wrong fetching Plex users', {
label: 'API',
errorMessage: e.message,
});
}
}
if (
user.plexId &&
user.plexId !== mainUser.plexId &&
!(await mainPlexTv.checkUserAccess(user.plexId))
) {
logger.warn(
'Failed sign-in attempt from Plex user without access to the media server',
{
label: 'API',
account: {
ip: req.ip,
email: body.email,
userId: user.id,
plexId: user.plexId,
},
}
);
return next({
status: 403,
message: 'Access denied.',
});
}
// Set logged in session
if (user && req.session) {
req.session.userId = user.id;
@@ -705,7 +775,7 @@ authRoutes.post('/logout', async (req, res, next) => {
});
return next({ status: 500, message: 'Failed to destroy session.' });
}
logger.debug('Successfully logged out user', {
logger.info('Successfully logged out user', {
label: 'Auth',
userId,
});

View File

@@ -25,7 +25,7 @@ const LabeledCheckbox: React.FC<LabeledCheckboxProps> = ({
<Field type="checkbox" id={id} name={id} onChange={onChange} />
</div>
<div className="ml-3 text-sm leading-6">
<label htmlFor="localLogin" className="block" aria-label={label}>
<label htmlFor="localLogin" className="block">
<div className="flex flex-col">
<span className="font-medium text-white">{label}</span>
<span className="font-normal text-gray-400">{description}</span>

View File

@@ -46,7 +46,7 @@ const NotificationType = ({
/>
</div>
<div className="ml-3 text-sm leading-6">
<label htmlFor={option.id} className="block" aria-label={option.name}>
<label htmlFor={option.id} className="block">
<div className="flex flex-col">
<span className="font-medium text-white">{option.name}</span>
<span className="font-normal text-gray-400">

View File

@@ -123,7 +123,7 @@ const PermissionOption = ({
/>
</div>
<div className="ml-3 text-sm leading-6">
<label htmlFor={option.id} className="block" aria-label={option.name}>
<label htmlFor={option.id} className="block">
<div className="flex flex-col">
<span className="font-medium text-white">{option.name}</span>
<span className="font-normal text-gray-400">

View File

@@ -185,18 +185,16 @@ const UserWebPushSettings = () => {
(device) => device.userAgent === currentUserAgent
);
if (hasMatchingDevice) {
if (hasMatchingDevice || dataDevices.length === 1) {
isEnabled = true;
}
}
setWebPushEnabled(isEnabled);
if (localStorage.getItem('pushNotificationsEnabled') === null) {
localStorage.setItem(
'pushNotificationsEnabled',
isEnabled ? 'true' : 'false'
);
}
localStorage.setItem(
'pushNotificationsEnabled',
isEnabled ? 'true' : 'false'
);
};
if (user?.id) {

View File

@@ -2,7 +2,6 @@ import { UserType } from '@server/constants/user';
import type { PermissionCheckOptions } from '@server/lib/permissions';
import { hasPermission, Permission } from '@server/lib/permissions';
import type { NotificationAgentKey } from '@server/lib/settings';
import { useRouter } from 'next/router';
import type { MutatorCallback } from 'swr';
import useSWR from 'swr';
@@ -57,21 +56,13 @@ export const useUser = ({
id,
initialData,
}: { id?: number; initialData?: User } = {}): UserHookResponse => {
const router = useRouter();
const isAuthPage = /^\/(login|setup|resetpassword(?:\/|$))/.test(
router.pathname
);
const {
data,
error,
mutate: revalidate,
} = useSWR<User>(id ? `/api/v1/user/${id}` : `/api/v1/auth/me`, {
fallbackData: initialData,
refreshInterval: !isAuthPage ? 30000 : 0,
revalidateOnFocus: !isAuthPage,
revalidateOnMount: !isAuthPage,
revalidateOnReconnect: !isAuthPage,
refreshInterval: 30000,
errorRetryInterval: 30000,
shouldRetryOnError: false,
});

View File

@@ -63,7 +63,7 @@ class PlexOAuth {
'X-Plex-Client-Identifier': clientId,
'X-Plex-Model': 'Plex OAuth',
'X-Plex-Platform': browser.getBrowserName(),
'X-Plex-Platform-Version': browser.getBrowserVersion() || 'Unknown',
'X-Plex-Platform-Version': browser.getBrowserVersion(),
'X-Plex-Device': browser.getOSName(),
'X-Plex-Device-Name': `${browser.getBrowserName()} (Seerr)`,
'X-Plex-Device-Screen-Resolution':