Skip to content

Commit

Permalink
chore: merge with parent
Browse files Browse the repository at this point in the history
  • Loading branch information
kiremitrov123 committed Sep 16, 2024
2 parents d224273 + 99493d9 commit 138709f
Show file tree
Hide file tree
Showing 45 changed files with 327 additions and 244 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
## [6.6.0](https://github.com/jwplayer/ott-web-app/compare/v6.5.0...v6.6.0) (2024-09-06)


### Features

* **project:** add injectable wrapper to common components ([#598](https://github.com/jwplayer/ott-web-app/issues/598)) ([a6ad0d8](https://github.com/jwplayer/ott-web-app/commit/a6ad0d88b0e7cdc36548867870c8eb3c014f1ad2))
* **project:** app metadata insertion ([3753a9c](https://github.com/jwplayer/ott-web-app/commit/3753a9c352289620af6ec597fb5f474d7b8ac6d4))
* **project:** remove free and productIds from content-types.json ([#605](https://github.com/jwplayer/ott-web-app/issues/605)) ([2268447](https://github.com/jwplayer/ott-web-app/commit/226844726061184252af24fabc8340e8539230b6))


### Bug Fixes

* **e2e:** fix tests after cleeng api update ([#606](https://github.com/jwplayer/ott-web-app/issues/606)) ([9062dba](https://github.com/jwplayer/ott-web-app/commit/9062dba9184561b5af399e25632f4fe132960223))
* **search:** override search query cache ([#594](https://github.com/jwplayer/ott-web-app/issues/594)) ([1c25ad2](https://github.com/jwplayer/ott-web-app/commit/1c25ad2cd2ecfc1d388e5f8094006f7d961c93a0))
* wrong protocol and url path ([#588](https://github.com/jwplayer/ott-web-app/issues/588)) ([de75eb7](https://github.com/jwplayer/ott-web-app/commit/de75eb7eaca51d4fef9be4a40f13d043437bf3f0))

## [6.5.0](https://github.com/jwplayer/ott-web-app/compare/v6.4.0...v6.5.0) (2024-07-25)


Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@jwp/ott",
"version": "6.5.0",
"version": "6.6.0",
"private": true,
"license": "Apache-2.0",
"repository": "https://github.com/jwplayer/ott-web-app.git",
Expand Down
87 changes: 50 additions & 37 deletions packages/common/src/controllers/AccessController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,83 +6,96 @@ import AccessService from '../services/AccessService';
import AccountService from '../services/integrations/AccountService';
import { INTEGRATION_TYPE } from '../modules/types';
import { getNamedModule } from '../modules/container';
import StorageService from '../services/StorageService';
import type { AccessTokens } from '../../types/access';

const ACCESS_TOKENS = 'access_tokens';

@injectable()
export default class AccessController {
private readonly accessService: AccessService;
private readonly accountService: AccountService;
private readonly storageService: StorageService;

private siteId: string = '';
private apiAccessBridgeUrl: string | undefined = '';

constructor(@inject(INTEGRATION_TYPE) integrationType: IntegrationType, @inject(AccessService) accessService: AccessService) {
constructor(
@inject(INTEGRATION_TYPE) integrationType: IntegrationType,
@inject(StorageService) storageService: StorageService,
@inject(AccessService) accessService: AccessService,
) {
this.accessService = accessService;
this.storageService = storageService;
this.accountService = getNamedModule(AccountService, integrationType);
}

initialize = async () => {
const { config, settings, accessModel } = useConfigStore.getState();
const { config, accessModel } = useConfigStore.getState();
this.siteId = config.siteId;
this.apiAccessBridgeUrl = settings?.apiAccessBridgeUrl;

// If the APP_API_ACCESS_BRIDGE_URL environment variable is defined, useAccessBridge will return true
// For the AVOD access model, signing and DRM are not supported, so passport generation is skipped
if (!this.apiAccessBridgeUrl || accessModel === 'AVOD') {
// For the AVOD access model, signing and DRM are not supported, so access tokens generation is skipped
if (accessModel === 'AVOD') {
return;
}

// Not awaiting to avoid blocking the loading process,
// as the passport can be stored asynchronously without affecting the app's performance
this.generateOrRefreshPassport();
// as the access tokens can be stored asynchronously without affecting the app's performance
void this.generateOrRefreshAccessTokens();
};

generateOrRefreshPassport = async () => {
if (!this.apiAccessBridgeUrl) {
return;
}
generateOrRefreshAccessTokens = async () => {
const existingAccessTokens = await this.getAccessTokens();
const shouldRefresh = existingAccessTokens && Date.now() > existingAccessTokens.expires;

const existingPassport = await this.accessService.getPassport();
const shouldRefresh = existingPassport && Date.now() > existingPassport.expires;
if (!existingAccessTokens) {
await this.generateAccessTokens();
}

if (shouldRefresh) {
return await this.refreshPassport();
} else if (existingPassport) {
return existingPassport;
return await this.refreshAccessTokens();
}

const auth = await this.accountService.getAuthData();
const newPassport = await this.accessService.generatePassport(this.apiAccessBridgeUrl, this.siteId, auth?.jwt);
if (newPassport) {
await this.accessService.setPassport(newPassport);
}
return;
};

generatePassport = async () => {
if (!this.apiAccessBridgeUrl) {
generateAccessTokens = async () => {
if (!this.siteId) {
return;
}

const auth = await this.accountService.getAuthData();

const passport = await this.accessService.generatePassport(this.apiAccessBridgeUrl, this.siteId, auth?.jwt);
if (passport) {
await this.accessService.setPassport(passport);
const accessTokens = await this.accessService.generateAccessTokens(this.siteId, auth?.jwt);
if (accessTokens) {
await this.setAccessTokens(accessTokens);
}
};

refreshPassport = async () => {
if (!this.apiAccessBridgeUrl) {
refreshAccessTokens = async () => {
const existingAccessTokens = await this.getAccessTokens();
// there is no access tokens stored, nothing to refresh
if (!existingAccessTokens) {
return;
}

const existingPassport = await this.accessService.getPassport();
if (!existingPassport) {
return;
const accessTokens = await this.accessService.refreshAccessTokens(this.siteId, existingAccessTokens.refresh_token);
if (accessTokens) {
await this.setAccessTokens(accessTokens);
}
};

const passport = await this.accessService.refreshPassport(this.apiAccessBridgeUrl, this.siteId, existingPassport.refresh_token);
if (passport) {
await this.accessService.setPassport(passport);
}
setAccessTokens = async (accessTokens: AccessTokens) => {
// Since the actual valid time for a passport token is 1 hour, set the expires to one hour from now.
// The expires field here is used as a helper to manage the passport's validity and refresh process.
const expires = new Date(Date.now() + 3600 * 1000).getTime();
return await this.storageService.setItem(ACCESS_TOKENS, JSON.stringify({ ...accessTokens, expires }), true);
};

getAccessTokens = async (): Promise<(AccessTokens & { expires: number }) | null> => {
return await this.storageService.getItem(ACCESS_TOKENS, true, true);
};

removeAccessTokens = async () => {
return await this.storageService.removeItem(ACCESS_TOKENS);
};
}
6 changes: 6 additions & 0 deletions packages/common/src/controllers/AccountController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ import { logError } from '../logger';

import WatchHistoryController from './WatchHistoryController';
import FavoritesController from './FavoritesController';
import AccessController from './AccessController';

@injectable()
export default class AccountController {
private readonly checkoutService: CheckoutService;
private readonly accountService: AccountService;
private readonly subscriptionService: SubscriptionService;
private readonly accessController: AccessController;
private readonly favoritesController: FavoritesController;
private readonly watchHistoryController: WatchHistoryController;
private readonly features: AccountServiceFeatures;
Expand All @@ -41,6 +43,7 @@ export default class AccountController {

constructor(
@inject(INTEGRATION_TYPE) integrationType: IntegrationType,
accessController: AccessController,
favoritesController: FavoritesController,
watchHistoryController: WatchHistoryController,
) {
Expand All @@ -49,6 +52,7 @@ export default class AccountController {
this.subscriptionService = getNamedModule(SubscriptionService, integrationType);

// @TODO: Controllers shouldn't be depending on other controllers, but we've agreed to keep this as is for now
this.accessController = accessController;
this.favoritesController = favoritesController;
this.watchHistoryController = watchHistoryController;

Expand Down Expand Up @@ -163,6 +167,7 @@ export default class AccountController {
const response = await this.accountService.login({ email, password, referrer });

if (response) {
void this.accessController?.generateAccessTokens();
await this.afterLogin(response.user, response.customerConsents);
return;
}
Expand All @@ -180,6 +185,7 @@ export default class AccountController {

logout = async () => {
await this.accountService?.logout();
await this.accessController?.removeAccessTokens();
await this.clearLoginState();

// let the application know to refresh all entitlements
Expand Down
13 changes: 12 additions & 1 deletion packages/common/src/controllers/AppController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,13 @@ export default class AppController {
// update settings in the config store
useConfigStore.setState({ settings });

// when an integration is set, we initialize AccountController and AccessController
// when an integration is set, we initialize the AccountController
if (integrationType) {
await getModule(AccountController).initialize(url, refreshEntitlements);
}

// when the apiAccessBridgeUrl is set up in the .ini file, we initialize the AccessController
if (settings?.apiAccessBridgeUrl) {
await getModule(AccessController).initialize();
}

Expand Down Expand Up @@ -122,4 +126,11 @@ export default class AppController {

return configState.integrationType;
};

getApiAccessBridgeUrl = (): string | undefined => {
const configState = useConfigStore.getState();

if (!configState.loaded) throw new Error('A call to `AppController#getApiAccessBridgeUrl()` was made before loading the config');
return configState.settings?.apiAccessBridgeUrl || undefined;
};
}
3 changes: 2 additions & 1 deletion packages/common/src/controllers/CheckoutController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { useCheckoutStore } from '../stores/CheckoutStore';
import { useAccountStore } from '../stores/AccountStore';
import { FormValidationError } from '../errors/FormValidationError';
import { determineSwitchDirection } from '../utils/subscription';
import { findDefaultCardMethodId } from '../utils/payments';

@injectable()
export default class CheckoutController {
Expand Down Expand Up @@ -70,7 +71,7 @@ export default class CheckoutController {
const { customer } = getAccountInfo();

const paymentMethods = await this.getPaymentMethods();
const paymentMethodId = paymentMethods[0]?.id;
const paymentMethodId = parseInt(findDefaultCardMethodId(paymentMethods));

const createOrderArgs: CreateOrderArgs = {
offer,
Expand Down
12 changes: 12 additions & 0 deletions packages/common/src/modules/functions/getApiAccessBridgeUrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { interfaces } from 'inversify';

import AppController from '../../controllers/AppController';

/**
* Retrieves the access bridge URL from the AppController.
* If the access bridge URL is defined in the application's .ini configuration file,
* the function returns the URL. If the value is not defined, it returns `undefined`.
*/
export const getApiAccessBridgeUrl = (context: interfaces.Context) => {
return context.container.get(AppController).getApiAccessBridgeUrl();
};
12 changes: 8 additions & 4 deletions packages/common/src/modules/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import 'reflect-metadata'; // include once in the app for inversify (see: https://github.com/inversify/InversifyJS/blob/master/README.md#-installation)
import { INTEGRATION, EPG_TYPE } from '../constants';
import { container } from './container';
import { DETERMINE_INTEGRATION_TYPE, INTEGRATION_TYPE } from './types';
import { API_ACCESS_BRIDGE_URL, DETERMINE_INTEGRATION_TYPE, INTEGRATION_TYPE } from './types';

import ApiService from '../services/ApiService';
import WatchHistoryService from '../services/WatchHistoryService';
Expand All @@ -16,7 +16,7 @@ import SettingsService from '../services/SettingsService';
import WatchHistoryController from '../controllers/WatchHistoryController';
import CheckoutController from '../controllers/CheckoutController';
import AccountController from '../controllers/AccountController';
import AccessControler from '../controllers/AccessController';
import AccessController from '../controllers/AccessController';
import FavoritesController from '../controllers/FavoritesController';
import AppController from '../controllers/AppController';
import EpgController from '../controllers/EpgController';
Expand All @@ -26,6 +26,10 @@ import EpgService from '../services/EpgService';
import ViewNexaEpgService from '../services/epg/ViewNexaEpgService';
import JWEpgService from '../services/epg/JWEpgService';

// Access integration
import AccessService from '../services/AccessService';
import { getApiAccessBridgeUrl } from './functions/getApiAccessBridgeUrl';

// Integration interfaces
import AccountService from '../services/integrations/AccountService';
import CheckoutService from '../services/integrations/CheckoutService';
Expand All @@ -44,7 +48,6 @@ import JWPCheckoutService from '../services/integrations/jwp/JWPCheckoutService'
import JWPSubscriptionService from '../services/integrations/jwp/JWPSubscriptionService';
import { getIntegrationType } from './functions/getIntegrationType';
import { isCleengIntegrationType, isJwpIntegrationType } from './functions/calculateIntegrationType';
import AccessService from '../services/AccessService';

// Common services
container.bind(ConfigService).toSelf();
Expand All @@ -63,15 +66,16 @@ container.bind(EpgController).toSelf();

// Integration controllers
container.bind(AccountController).toSelf();
container.bind(AccessControler).toSelf();
container.bind(CheckoutController).toSelf();
container.bind(AccessController).toSelf();

// EPG services
container.bind(EpgService).to(JWEpgService).whenTargetNamed(EPG_TYPE.jwp);
container.bind(EpgService).to(ViewNexaEpgService).whenTargetNamed(EPG_TYPE.viewNexa);

// Functions
container.bind(INTEGRATION_TYPE).toDynamicValue(getIntegrationType);
container.bind(API_ACCESS_BRIDGE_URL).toDynamicValue(getApiAccessBridgeUrl);

// Cleeng integration
container.bind(DETERMINE_INTEGRATION_TYPE).toConstantValue(isCleengIntegrationType);
Expand Down
2 changes: 2 additions & 0 deletions packages/common/src/modules/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ export const INTEGRATION_TYPE = Symbol('INTEGRATION_TYPE');
export const DETERMINE_INTEGRATION_TYPE = Symbol('DETERMINE_INTEGRATION_TYPE');

export const GET_CUSTOMER_IP = Symbol('GET_CUSTOMER_IP');

export const API_ACCESS_BRIDGE_URL = Symbol('API_ACCESS_BRIDGE_URL');
Loading

0 comments on commit 138709f

Please sign in to comment.