parent
5adfdc1b05
commit
72f9624f1d
@ -1,8 +0,0 @@
|
||||
import React from 'react';
|
||||
import '../styles/globals.css';
|
||||
|
||||
function MyApp({ Component, pageProps }) {
|
||||
return <Component {...pageProps} />;
|
||||
}
|
||||
|
||||
export default MyApp;
|
@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import '../styles/globals.css';
|
||||
import App from 'next/app';
|
||||
|
||||
class CoreApp extends App {
|
||||
public render(): JSX.Element {
|
||||
const { Component, pageProps } = this.props;
|
||||
return <Component {...pageProps} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default CoreApp;
|
@ -0,0 +1,39 @@
|
||||
import React, { useState } from 'react';
|
||||
import { NextPage } from 'next';
|
||||
import PlexOAuth from '../utils/plex';
|
||||
|
||||
const plexOAuth = new PlexOAuth();
|
||||
|
||||
const PlexText: NextPage = () => {
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [authToken, setAuthToken] = useState<string>('');
|
||||
|
||||
const getPlexLogin = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const authToken = await plexOAuth.login();
|
||||
setAuthToken(authToken);
|
||||
setLoading(false);
|
||||
} catch (e) {
|
||||
console.log(e.message);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<span className="inline-flex rounded-md shadow-sm">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => getPlexLogin()}
|
||||
disabled={loading}
|
||||
className="inline-flex items-center px-6 py-3 border border-transparent text-base leading-6 font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:shadow-outline-indigo active:bg-indigo-700 transition ease-in-out duration-150"
|
||||
>
|
||||
{loading ? 'Loading...' : 'Plex Login'}
|
||||
</button>
|
||||
</span>
|
||||
<div className="mt-4">Auth Token: {authToken}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PlexText;
|
@ -0,0 +1,213 @@
|
||||
import axios from 'axios';
|
||||
import Bowser from 'bowser';
|
||||
|
||||
interface PlexHeaders {
|
||||
Accept: string;
|
||||
'X-Plex-Product': string;
|
||||
'X-Plex-Version': string;
|
||||
'X-Plex-Client-Identifier': string;
|
||||
'X-Plex-Model': string;
|
||||
'X-Plex-Platform': string;
|
||||
'X-Plex-Platform-Version': string;
|
||||
'X-Plex-Device': string;
|
||||
'X-Plex-Device-Name': string;
|
||||
'X-Plex-Device-Screen-Resolution': string;
|
||||
'X-Plex-Language': string;
|
||||
}
|
||||
|
||||
export interface PlexPin {
|
||||
id: number;
|
||||
code: string;
|
||||
}
|
||||
|
||||
class PlexOAuth {
|
||||
private plexHeaders?: PlexHeaders;
|
||||
|
||||
private pin?: PlexPin;
|
||||
private popup?: Window;
|
||||
|
||||
private authToken?: string;
|
||||
|
||||
public initializeHeaders(): void {
|
||||
if (!window) {
|
||||
throw new Error(
|
||||
'Window is not defined. Are you calling this in the browser?'
|
||||
);
|
||||
}
|
||||
const browser = Bowser.getParser(window.navigator.userAgent);
|
||||
this.plexHeaders = {
|
||||
Accept: 'application/json',
|
||||
'X-Plex-Product': 'Overseerr',
|
||||
'X-Plex-Version': '2.0',
|
||||
'X-Plex-Client-Identifier': '7f9de3ba-e12b-11ea-87d0-0242ac130003',
|
||||
'X-Plex-Model': 'Plex OAuth',
|
||||
'X-Plex-Platform': browser.getOSName(),
|
||||
'X-Plex-Platform-Version': browser.getOSVersion(),
|
||||
'X-Plex-Device': browser.getBrowserName(),
|
||||
'X-Plex-Device-Name': browser.getBrowserVersion(),
|
||||
'X-Plex-Device-Screen-Resolution':
|
||||
window.screen.width + 'x' + window.screen.height,
|
||||
'X-Plex-Language': 'en',
|
||||
};
|
||||
}
|
||||
|
||||
public async getPin(): Promise<PlexPin> {
|
||||
if (!this.plexHeaders) {
|
||||
throw new Error(
|
||||
'You must initialize the plex headers clientside to login'
|
||||
);
|
||||
}
|
||||
try {
|
||||
const response = await axios.post(
|
||||
'https://plex.tv/api/v2/pins?strong=true',
|
||||
undefined,
|
||||
{ headers: this.plexHeaders }
|
||||
);
|
||||
|
||||
this.pin = { id: response.data.id, code: response.data.code };
|
||||
|
||||
return this.pin;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public async login(): Promise<string> {
|
||||
try {
|
||||
this.initializeHeaders();
|
||||
await this.getPin();
|
||||
|
||||
if (!this.plexHeaders || !this.pin) {
|
||||
throw new Error('Unable to call login if class is not initialized.');
|
||||
}
|
||||
|
||||
const params = {
|
||||
clientID: this.plexHeaders['X-Plex-Client-Identifier'],
|
||||
'context[device][product]': this.plexHeaders['X-Plex-Product'],
|
||||
'context[device][version]': this.plexHeaders['X-Plex-Version'],
|
||||
'context[device][platform]': this.plexHeaders['X-Plex-Platform'],
|
||||
'context[device][platformVersion]': this.plexHeaders[
|
||||
'X-Plex-Platform-Version'
|
||||
],
|
||||
'context[device][device]': this.plexHeaders['X-Plex-Device'],
|
||||
'context[device][deviceName]': this.plexHeaders['X-Plex-Device-Name'],
|
||||
'context[device][model]': this.plexHeaders['X-Plex-Model'],
|
||||
'context[device][screenResolution]': this.plexHeaders[
|
||||
'X-Plex-Device-Screen-Resolution'
|
||||
],
|
||||
'context[device][layout]': 'desktop',
|
||||
code: this.pin.code,
|
||||
};
|
||||
this.openPopup({
|
||||
url: `https://app.plex.tv/auth/#!?${this.encodeData(params)}`,
|
||||
title: 'Plex Auth',
|
||||
w: 600,
|
||||
h: 700,
|
||||
});
|
||||
|
||||
return this.pinPoll();
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private async pinPoll(): Promise<string> {
|
||||
const executePoll = async (
|
||||
resolve: (authToken: string) => void,
|
||||
reject: (e: Error) => void
|
||||
) => {
|
||||
try {
|
||||
if (!this.pin) {
|
||||
throw new Error('Unable to poll when pin is not initialized.');
|
||||
}
|
||||
|
||||
const response = await axios.get(
|
||||
`https://plex.tv/api/v2/pins/${this.pin.id}`,
|
||||
{ headers: this.plexHeaders }
|
||||
);
|
||||
|
||||
if (response.data?.authToken) {
|
||||
this.authToken = response.data.authToken;
|
||||
this.closePopup();
|
||||
resolve(response.data.authToken);
|
||||
} else if (!response.data?.authToken && !this.popup?.closed) {
|
||||
setTimeout(executePoll, 1000, resolve, reject);
|
||||
} else {
|
||||
reject(new Error('Popup closed without completing login'));
|
||||
}
|
||||
} catch (e) {
|
||||
this.closePopup();
|
||||
reject(e);
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise(executePoll);
|
||||
}
|
||||
|
||||
private closePopup(): void {
|
||||
this.popup?.close();
|
||||
this.popup = undefined;
|
||||
}
|
||||
|
||||
private openPopup({
|
||||
url,
|
||||
title,
|
||||
w,
|
||||
h,
|
||||
}: {
|
||||
url: string;
|
||||
title: string;
|
||||
w: number;
|
||||
h: number;
|
||||
}): Window | void {
|
||||
if (!window) {
|
||||
throw new Error(
|
||||
'Window is undefined. Are you running this in the browser?'
|
||||
);
|
||||
}
|
||||
// Fixes dual-screen position Most browsers Firefox
|
||||
const dualScreenLeft =
|
||||
window.screenLeft != undefined ? window.screenLeft : window.screenX;
|
||||
const dualScreenTop =
|
||||
window.screenTop != undefined ? window.screenTop : window.screenY;
|
||||
const width = window.innerWidth
|
||||
? window.innerWidth
|
||||
: document.documentElement.clientWidth
|
||||
? document.documentElement.clientWidth
|
||||
: screen.width;
|
||||
const height = window.innerHeight
|
||||
? window.innerHeight
|
||||
: document.documentElement.clientHeight
|
||||
? document.documentElement.clientHeight
|
||||
: screen.height;
|
||||
const left = width / 2 - w / 2 + dualScreenLeft;
|
||||
const top = height / 2 - h / 2 + dualScreenTop;
|
||||
const newWindow = window.open(
|
||||
url,
|
||||
title,
|
||||
'scrollbars=yes, width=' +
|
||||
w +
|
||||
', height=' +
|
||||
h +
|
||||
', top=' +
|
||||
top +
|
||||
', left=' +
|
||||
left
|
||||
);
|
||||
if (newWindow) {
|
||||
newWindow.focus();
|
||||
this.popup = newWindow;
|
||||
return this.popup;
|
||||
}
|
||||
}
|
||||
|
||||
private encodeData(data: Record<string, string>): string {
|
||||
return Object.keys(data)
|
||||
.map(function (key) {
|
||||
return [key, data[key]].map(encodeURIComponent).join('=');
|
||||
})
|
||||
.join('&');
|
||||
}
|
||||
}
|
||||
|
||||
export default PlexOAuth;
|
@ -0,0 +1,3 @@
|
||||
export type Undefinable<T> = T | undefined;
|
||||
export type Nullable<T> = T | null;
|
||||
export type Maybe<T> = T | null | undefined;
|
@ -1,8 +1,13 @@
|
||||
/* eslint-disable */
|
||||
module.exports = {
|
||||
purge: ['./pages/**/*.{ts,tsx}', './components/**/*.{ts,tsx}'],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
variants: {},
|
||||
plugins: [],
|
||||
plugins: [
|
||||
require('@tailwindcss/ui')({
|
||||
layout: 'sidebar',
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
Loading…
Reference in new issue