Skip to content

Commit

Permalink
feat: inplayer authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
kiremitrov123 committed Nov 30, 2022
1 parent 89bfab8 commit f1922e4
Show file tree
Hide file tree
Showing 14 changed files with 339 additions and 77 deletions.
4 changes: 2 additions & 2 deletions .env.dev
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
APP_CONFIG_DEFAULT_SOURCE=https://web-ott.s3.eu-west-1.amazonaws.com/apps/configs/demo.json
APP_CONFIG_DEFAULT_SOURCE=gnnuzabk
APP_UNSAFE_ALLOW_DYNAMIC_CONFIG=1
APP_API_BASE_URL=https://cdn.jwplayer.com
APP_API_BASE_URL=https://cdn.jwplayer.com
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ firebase-debug.log
.idea
.DS_Store
.vscode/

# env dev
.env.dev
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"deploy:github": "node ./scripts/deploy-github.js"
},
"dependencies": {
"@inplayer-org/inplayer.js": "^3.13.1",
"classnames": "^2.3.1",
"date-fns": "^2.28.0",
"dompurify": "^2.3.8",
Expand Down
8 changes: 8 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import React, { Component } from 'react';
import { getI18n, I18nextProvider } from 'react-i18next';
import InPlayer from '@inplayer-org/inplayer.js';

import { initializeInPlayerAccount } from './stores/inplayer/AccountController';

import type { Config } from '#types/Config';
import Router from '#src/containers/Router/Router';
Expand Down Expand Up @@ -37,6 +40,11 @@ class App extends Component {
await initializeAccount();
}

if (config?.integrations?.inplayer?.clientId) {
InPlayer.setConfig(import.meta.env.APP_INPLAYER_SDK);
await initializeInPlayerAccount();
}

// We only request favorites and continue_watching data if there is a corresponding item in the content section
// and a playlist in the features section.
// We first initialize the account otherwise if we have favorites saved as externalData and in a local storage the sections may blink
Expand Down
8 changes: 6 additions & 2 deletions src/components/UserMenu/UserMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import BalanceWallet from '#src/icons/BalanceWallet';
import Exit from '#src/icons/Exit';
import MenuButton from '#components/MenuButton/MenuButton';
import { logout } from '#src/stores/AccountController';
import useClientIntegration, { ClientIntegrations } from '#src/hooks/useClientIntegration';
import { inPlayerLogout } from '#src/stores/inplayer/AccountController';

type Props = {
inPopover?: boolean;
Expand All @@ -21,15 +23,17 @@ type Props = {
const UserMenu = ({ showPaymentsItem, inPopover = false, onClick }: Props) => {
const { t } = useTranslation('user');
const navigate = useNavigate();
const { client } = useClientIntegration();
const logoutUser = client === ClientIntegrations.INPLAYER ? inPlayerLogout : logout;

const onLogout = useCallback(async () => {
if (onClick) {
onClick();
}

await logout();
await logoutUser();
navigate('/', { replace: true });
}, [onClick, navigate]);
}, [onClick, navigate, logoutUser]);

const menuItems = (
<ul className={styles.menuItems}>
Expand Down
7 changes: 6 additions & 1 deletion src/containers/AccountModal/forms/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,24 @@ import { useLocation, useNavigate } from 'react-router';

import { useConfigStore } from '#src/stores/ConfigStore';
import useForm, { UseFormOnSubmitHandler } from '#src/hooks/useForm';
import useClientIntegration, { ClientIntegrations } from '#src/hooks/useClientIntegration';
import LoginForm from '#components/LoginForm/LoginForm';
import { removeQueryParam } from '#src/utils/location';
import type { LoginFormData } from '#types/account';
import { login } from '#src/stores/AccountController';
import { inplayerLogin } from '#src/stores/inplayer/AccountController';

const Login = () => {
const { siteName } = useConfigStore((s) => s.config);
const navigate = useNavigate();
const location = useLocation();
const { t } = useTranslation('account');
const { client } = useClientIntegration();
const loginUser = client === ClientIntegrations.INPLAYER ? inplayerLogin : login;

const loginSubmitHandler: UseFormOnSubmitHandler<LoginFormData> = async (formData, { setErrors, setSubmitting, setValue }) => {
try {
await login(formData.email, formData.password);
await loginUser(formData.email, formData.password);

// close modal
navigate(removeQueryParam(location, 'u'));
Expand Down
7 changes: 4 additions & 3 deletions src/hooks/useClientIntegration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ const useClientIntegration = () => {
} = useConfigStore.getState();

const isInPlayer = !!integrations?.inplayer?.clientId;
const clientName = isInPlayer ? ClientIntegrations.INPLAYER : ClientIntegrations.CLEENG;
const client = isInPlayer ? ClientIntegrations.INPLAYER : ClientIntegrations.CLEENG;
const clientId = isInPlayer ? integrations?.inplayer?.clientId : integrations?.cleeng?.id;

// TODO: More data will follow. Example: access fees will be fetched and returned once user is auth.
return {
clientName,
clientId,
integration: integrations,
client,
clientId: clientId,
};
};

Expand Down
8 changes: 6 additions & 2 deletions src/pages/User/User.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import Favorites from '#components/Favorites/Favorites';
import type { PlaylistItem } from '#types/playlist';
import { logout } from '#src/stores/AccountController';
import { clear as clearFavorites } from '#src/stores/FavoritesController';
import useClientIntegration, { ClientIntegrations } from '#src/hooks/useClientIntegration';
import { inPlayerLogout } from '#src/stores/inplayer/AccountController';

const User = (): JSX.Element => {
const { accessModel, favoritesList, shelfTitles } = useConfigStore(
Expand All @@ -42,15 +44,17 @@ const User = (): JSX.Element => {
const [showAllTransactions, setShowAllTransactions] = useState(false);
const isLargeScreen = breakpoint > Breakpoint.md;
const { user: customer, subscription, transactions, activePayment, loading } = useAccountStore();
const { client } = useClientIntegration();
const logoutUser = client === ClientIntegrations.INPLAYER ? inPlayerLogout : logout;

const updateBlurImage = useBlurImageUpdater();

const onCardClick = (playlistItem: PlaylistItem) => navigate(mediaURL(playlistItem));
const onCardHover = (playlistItem: PlaylistItem) => updateBlurImage(playlistItem.image);
const onLogout = useCallback(async () => {
// Empty customer on a user page leads to navigate (code bellow), so we don't repeat it here
await logout();
}, []);
await logoutUser();
}, [logoutUser]);

useEffect(() => updateBlurImage(''), [updateBlurImage]);

Expand Down
14 changes: 7 additions & 7 deletions src/stores/ConfigStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ type CleengData = {
};

type InPlayerData = {
inplayerId: string | null | undefined;
inplayerSandbox: boolean;
inplayerAssetId: number | null | undefined;
clientId: string | null | undefined;
sandbox: boolean;
assetId: number | null | undefined;
};

type ConfigState = {
Expand Down Expand Up @@ -60,10 +60,10 @@ export const useConfigStore = createStore<ConfigState>('ConfigStore', (_, get) =
getInPlayerData: (): InPlayerData => {
const inplayer = get().config?.integrations?.inplayer;

const inplayerId = inplayer?.clientId;
const inplayerSandbox = !!inplayer?.useSandbox;
const inplayerAssetId = inplayer?.assetId;
const clientId = inplayer?.clientId;
const sandbox = !!inplayer?.useSandbox;
const assetId = inplayer?.assetId;

return { inplayerId, inplayerSandbox, inplayerAssetId };
return { clientId, sandbox, assetId };
},
}));
81 changes: 81 additions & 0 deletions src/stores/inplayer/AccountController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import InPlayer from '@inplayer-org/inplayer.js';

import { useConfigStore } from '../ConfigStore';
import { useAccountStore } from '../AccountStore';

import * as persist from '#src/utils/persist';
import type { AuthData } from '#types/account';
import { processInplayerAccount, processInPlayerAuth } from '#src/utils/common';

const PERSIST_KEY_ACCOUNT = 'auth';
let subscription: undefined | (() => void);

export const initializeInPlayerAccount = async () => {
try {
const { clientId } = useConfigStore.getState().getInPlayerData();

if (!clientId) {
useAccountStore.getState().setLoading(false);
return;
}

const storedSession: AuthData | null = persist.getItem(PERSIST_KEY_ACCOUNT) as AuthData | null;

// clear previous subscribe (for dev environment only)
if (subscription) {
subscription();
}
subscription = useAccountStore.subscribe(
(state) => state.auth,
(authData) => {
if (authData) {
persist.setItem(PERSIST_KEY_ACCOUNT, authData);
}
},
);

if (storedSession) {
const { data } = await InPlayer.Account.getAccountInfo();

useAccountStore.setState({
auth: storedSession,
user: processInplayerAccount(data),
});
}

useAccountStore.setState({ loading: false });
} catch {
await inPlayerLogout();
}
};

export const inplayerLogin = async (email: string, password: string) => {
const { clientId } = useConfigStore.getState().getInPlayerData();

useAccountStore.setState({ loading: true });

const { data } = await InPlayer.Account.signInV2({
email,
password,
clientId: clientId || '',
referrer: window.location.href,
});

if (data) {
useAccountStore.setState({
auth: processInPlayerAuth(data),
user: processInplayerAccount(data.account),
loading: false,
});
}
};

export const inPlayerLogout = async () => {
await InPlayer.Account.signOut();

persist.removeItem(PERSIST_KEY_ACCOUNT);
useAccountStore.setState({
auth: null,
user: null,
});
};
25 changes: 25 additions & 0 deletions src/utils/common.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { overrideIPCookieKey } from '#test/constants';
import type { AuthData, InPlayerAuthData, InPlayerAccount } from '#types/account';

export function debounce<T extends (...args: any[]) => void>(callback: T, wait = 200) {
let timeout: NodeJS.Timeout | null;
Expand Down Expand Up @@ -82,3 +83,27 @@ export function getOverrideIP() {
export function testId(value: string | undefined) {
return IS_DEVELOPMENT_BUILD || IS_TEST_MODE ? value : undefined;
}

// responsible to convert the InPlayer object to be compatible to the store
export function processInplayerAccount(account: InPlayerAccount) {
const { id, email, full_name: fullName, created_at: createdAt } = account;
const regDate = new Date(createdAt * 1000).toLocaleString();

return {
id: id.toString(),
email,
fullName,
regDate,
country: '',
lastUserIp: '',
};
}

export function processInPlayerAuth(auth: InPlayerAuthData): AuthData {
const { access_token: jwt } = auth;
return {
jwt,
customerToken: '',
refreshToken: '',
};
}
12 changes: 1 addition & 11 deletions src/utils/configLoad.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,7 @@ const defaultConfig: Config = {
},
content: [],
menu: [],
integrations: {
cleeng: {
id: null,
useSandbox: true,
},
inplayer: {
clientId: null,
assetId: null,
useSandbox: true,
},
},
integrations: {},
styling: {
footerText: '',
shelfTitles: true,
Expand Down
13 changes: 13 additions & 0 deletions types/account.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ export type AuthData = {
refreshToken: string;
};

export type InPlayerAuthData = {
access_token: string;
expires?: number;
};

export type JwtDetails = {
customerId: string;
exp: number;
Expand Down Expand Up @@ -163,10 +168,18 @@ export type Customer = {
lastUserIp: string;
firstName?: string;
lastName?: string;
fullName?: string;
externalId?: string;
externalData?: ExternalData;
};

export type InPlayerAccount = {
created_at: number;
email: string;
full_name: string;
id: number;
};

export type Consent = {
broadcasterId: number;
name: string;
Expand Down
Loading

0 comments on commit f1922e4

Please sign in to comment.