Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
git config user.name "${{ env.GH_USER }}"
git config user.email "${{ env.GH_EMAIL }}"
git fetch origin ${{ github.ref_name }}
git merge origin/${{ github.ref_name }}
git merge origin/${{ github.ref_name }} --allow-unrelated-histories
yarn && yarn workspace @jwp/ott-web run i18next
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-static-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}

Expand Down
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
## [6.11.0](https://github.com/jwplayer/ott-web-app/compare/v6.10.0...v6.11.0) (2025-09-05)


### Features

* add e2e tests for content lists ([#689](https://github.com/jwplayer/ott-web-app/issues/689)) ([fab2ff1](https://github.com/jwplayer/ott-web-app/commit/fab2ff1ef421aa01b532c430e790fd74d1c79f85))
* add e2e tests for translations ([#687](https://github.com/jwplayer/ott-web-app/issues/687)) ([0bc69a7](https://github.com/jwplayer/ott-web-app/commit/0bc69a7514eff2a61573b034dd7bb6fcf197617d))
* movie screen by composition ([6f0a73e](https://github.com/jwplayer/ott-web-app/commit/6f0a73e9783169f0f9efbfaff723740b52703466))
* Reference implementation of the labels filter in the web app ([#682](https://github.com/jwplayer/ott-web-app/issues/682)) ([5184a76](https://github.com/jwplayer/ott-web-app/commit/5184a764511b5cba897acb0c180d8c5fdcd924c4))


### Bug Fixes

* Fix broken SCA flow payment finalization ([#677](https://github.com/jwplayer/ott-web-app/issues/677)) ([405f5e5](https://github.com/jwplayer/ott-web-app/commit/405f5e5a0310e2188f9bcfdbc7c54594876a48fd))
* **i18n:** update i18n keys ([#683](https://github.com/jwplayer/ott-web-app/issues/683)) ([c8f9a24](https://github.com/jwplayer/ott-web-app/commit/c8f9a24f2d7c192b5f736096837b3d5d7421d3f7))
* Trial period displayed incorrectly ([#676](https://github.com/jwplayer/ott-web-app/issues/676)) ([e139359](https://github.com/jwplayer/ott-web-app/commit/e139359e2bdc43248442a090568b082ba2902db9))

## [6.10.0](https://github.com/jwplayer/ott-web-app/compare/v6.9.0...v6.10.0) (2025-04-09)


Expand Down
6 changes: 3 additions & 3 deletions configs/eslint-config-jwp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
"test": "exit 0"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^8.17.0",
"@typescript-eslint/parser": "^8.17.0",
"@typescript-eslint/eslint-plugin": "^8.26.1",
"@typescript-eslint/parser": "^8.26.1",
"confusing-browser-globals": "^1.0.11",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-hooks": "^4.6.2"
}
}
2 changes: 1 addition & 1 deletion configs/postcss-config-jwp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"test": "exit 0"
},
"devDependencies": {
"postcss": "^8.4.49",
"postcss": "^8.5.3",
"postcss-import": "^14.1.0",
"postcss-scss": "^4.0.9",
"stylelint": "^15.11.0"
Expand Down
2 changes: 1 addition & 1 deletion configs/stylelint-config-jwp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"devDependencies": {
"stylelint": "^15.11.0",
"stylelint-config-recommended-scss": "^13.1.0",
"stylelint-declaration-strict-value": "^1.10.6",
"stylelint-declaration-strict-value": "^1.10.11",
"stylelint-order": "^6.0.4",
"stylelint-scss": "^5.3.2"
}
Expand Down
22 changes: 16 additions & 6 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ each shelf separately.

**content[].contentId**

The eight-character Playlists IDs from the JW Player dashboard. These IDs populate the video "shelves" on your site. *
*contentId** is not required if you use `continue_watching` or `favorites` **type**.
The eight-character Playlists IDs from the JW Player dashboard. These IDs populate the video "shelves" on your site. \*
\*contentId** is not required if you use `continue_watching` or `favorites` **type\*\*.

---

Expand Down Expand Up @@ -145,6 +145,16 @@ You can change the background color of the shelf with the help of this property

---

**content[].filterTags** (optional)

You can optionally define a list of comma separated labels which are used to filter shelves.

The OTT Web App offers a demonstration of its filtering capabilities. This example can be enabled by configuring the `enableLabelsFiltering` custom parameter.

Filtering by device type, country, and day of week is supported. See the `Home.tsx` file for a code example.

---

**styling**

Use the `styling` object to define extra styles for your application.
Expand Down Expand Up @@ -305,11 +315,11 @@ Note, this setting is ignored if Cleeng is not enabled (i.e. there is not Cleeng
**integrations.cleeng.monthlyOffer** (optional)

If Cleeng is enabled, and you want to show the Payments and Subscription functionality, you need to include at least one
offer ID (either this or the yearly offer property).
The application uses this ID to map to an offer that you've configured in your Cleeng environment under Offers to
represent your monthly subscription.
offer ID (either this or the yearly offer property).
The application uses this ID to map to an offer that you've configured in your Cleeng environment under Offers to
represent your monthly subscription.
Note that only the data used from the Cleeng offer is the price, the free days, and the free period,
and the app does not verify if the offer length is actually monthly.
and the app does not verify if the offer length is actually monthly.
If no monthly or yearly offer is configured, the Payments section will not be shown.

---
Expand Down
4 changes: 1 addition & 3 deletions knip.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const config: KnipConfig = {
'packages/ui-react': {
entry: ['src/**/*'],
ignoreDependencies: [
'@types/dompurify', // Somehow this is not recognised
'sass-embedded', // Used in Vite
'postcss-config-jwp', // Used in postcss.config
],
Expand All @@ -29,11 +30,8 @@ const config: KnipConfig = {
ignoreDependencies: [
'@codeceptjs/allure-legacy',
'@codeceptjs/configure', // Used in e2e tests
'@babel/plugin-proposal-decorators', // Used to build with decorators for ioc resolution
'@babel/core', // Required peer dependency for babel plugins
'@jwp/ott-testing', // Used in e2e testing
'@types/luxon', // Used in tests
'babel-plugin-transform-typescript-metadata', // Used to build with decorators for ioc resolution
'core-js', // Conditionally imported at build time
'eslint-plugin-codeceptjs', // Used by apps/web/test-e2e/.eslintrc.cjs
'i18next-parser',
Expand Down
31 changes: 14 additions & 17 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@jwp/ott",
"version": "6.10.0",
"version": "6.11.0",
"private": true,
"license": "Apache-2.0",
"repository": "https://github.com/jwplayer/ott-web-app.git",
Expand Down Expand Up @@ -32,37 +32,34 @@
"pre-commit": "yarn depcheck && lint-staged",
"prepare": "husky install",
"test": "TZ=UTC LC_ALL=en_US.UTF-8 vitest run",
"test-update": "TZ=UTC LC_ALL=en_US.UTF-8 vitest run -u",
"test-watch": "TZ=UTC LC_ALL=en_US.UTF-8 vitest",
"web": "yarn --cwd platforms/web",
"access-bridge": "yarn --cwd platforms/access-bridge"
},
"devDependencies": {
"@commitlint/cli": "^19.6.0",
"@commitlint/config-conventional": "^19.6.0",
"@types/node": "^22.10.1",
"@babel/core": "^7.26.10",
"@babel/plugin-proposal-decorators": "^7.25.9",
"@commitlint/cli": "^19.8.0",
"@commitlint/config-conventional": "^19.8.0",
"@types/node": "^22.13.10",
"csv-parse": "^5.6.0",
"eslint": "^8.57.1",
"eslint-config-jwp": "*",
"husky": "^6.0.0",
"i18next-parser-workspaces": "^0.2.0",
"knip": "^5.39.1",
"lint-staged": "^15.2.10",
"i18next-parser-workspaces": "^0.2.1",
"knip": "^5.61.2",
"lint-staged": "^15.4.3",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.8",
"read": "^2.1.0",
"ts-node": "^10.9.2",
"typescript": "5.7.2",
"vitest": "^2.1.8"
"typescript": "5.8.3",
"vitest": "^3.0.8"
},
"resolutions": {
"axios": "^0.29.0",
"axios": "^0.30.0",
"cheerio": "1.0.0-rc.12",
"codeceptjs/**/fast-xml-parser": "^4.5.0",
"eslint/**/cross-spawn": "^7.0.6",
"lint-staged/**/cross-spawn": "^7.0.6",
"npm-run-all/**/cross-spawn": "^6.0.6",
"micromatch": ">=4.0.8",
"vitest/**/vite": "^5.4.11",
"ws": ">=5.2.4"
"**/minimatch/brace-expansion": "^1.1.12"
}
}
14 changes: 7 additions & 7 deletions packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,25 @@
"dependencies": {
"@inplayer-org/inplayer.js": "^3.13.28",
"date-fns": "^4.1.0",
"fast-xml-parser": "^4.5.0",
"i18next": "^24.0.5",
"fast-xml-parser": "^4.5.3",
"i18next": "^24.2.2",
"ini": "^3.0.1",
"inversify": "^6.1.5",
"inversify": "^7.5.4",
"jwt-decode": "^4.0.0",
"lodash.merge": "^4.6.2",
"react-i18next": "^15.1.3",
"react-i18next": "^15.4.1",
"reflect-metadata": "^0.2.2",
"yup": "^1.4.0",
"yup": "^1.6.1",
"zustand": "^4.5.5"
},
"devDependencies": {
"@jwp/ott-testing": "*",
"@types/ini": "^1.3.34",
"@types/lodash.merge": "^4.6.9",
"eslint-config-jwp": "*",
"jsdom": "^22.1.0",
"jsdom": "^26.0.0",
"timezone-mock": "^1.3.6",
"vi-fetch": "^0.8.0",
"vitest": "^2.1.8"
"vitest": "^3.0.8"
}
}
24 changes: 12 additions & 12 deletions packages/common/src/modules/container.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
import { Container, injectable, type interfaces, inject } from 'inversify';
import { Container, injectable, type ServiceIdentifier, inject } from 'inversify';

export const container = new Container({ defaultScope: 'Singleton', skipBaseClassChecks: true });
export const container = new Container({ defaultScope: 'Singleton' });

export { injectable, inject };

export function getModule<T>(constructorFunction: interfaces.ServiceIdentifier<T>, required: false): T | undefined;
export function getModule<T>(constructorFunction: interfaces.ServiceIdentifier<T>, required: true): T;
export function getModule<T>(constructorFunction: interfaces.ServiceIdentifier<T>): T;
export function getModule<T>(constructorFunction: interfaces.ServiceIdentifier<T>, required = true): T | undefined {
export function getModule<T>(constructorFunction: ServiceIdentifier<T>, required: false): T | undefined;
export function getModule<T>(constructorFunction: ServiceIdentifier<T>, required: true): T;
export function getModule<T>(constructorFunction: ServiceIdentifier<T>): T;
export function getModule<T>(constructorFunction: ServiceIdentifier<T>, required = true): T | undefined {
const module = container.getAll(constructorFunction)[0];

if (required && !module) throw new Error(`Service / Controller '${String(constructorFunction)}' not found`);

return module;
}

export function getAllModules<T>(constructorFunction: interfaces.ServiceIdentifier<T>): T[] {
export function getAllModules<T>(constructorFunction: ServiceIdentifier<T>): T[] {
return container.getAll(constructorFunction);
}

export function getNamedModule<T>(constructorFunction: interfaces.ServiceIdentifier<T>, name: string | null, required: false): T | undefined;
export function getNamedModule<T>(constructorFunction: interfaces.ServiceIdentifier<T>, name: string | null, required: true): T;
export function getNamedModule<T>(constructorFunction: interfaces.ServiceIdentifier<T>, name: string | null): T;
export function getNamedModule<T>(constructorFunction: interfaces.ServiceIdentifier<T>, name: string | null, required = true): T | undefined {
export function getNamedModule<T>(constructorFunction: ServiceIdentifier<T>, name: string | null, required: false): T | undefined;
export function getNamedModule<T>(constructorFunction: ServiceIdentifier<T>, name: string | null, required: true): T;
export function getNamedModule<T>(constructorFunction: ServiceIdentifier<T>, name: string | null): T;
export function getNamedModule<T>(constructorFunction: ServiceIdentifier<T>, name: string | null, required = true): T | undefined {
if (!name) {
// if no name is given we throw an error to satisfy the non-nullable return type
if (required) {
Expand All @@ -34,7 +34,7 @@ export function getNamedModule<T>(constructorFunction: interfaces.ServiceIdentif
let module;

try {
module = container.getAllNamed(constructorFunction, name)[0];
module = container.getAll(constructorFunction, { name })[0];

return module;
} catch (err: unknown) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { interfaces } from 'inversify';
import type { ResolutionContext } from 'inversify';

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

Expand All @@ -7,6 +7,6 @@ import AppController from '../../controllers/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();
export const getApiAccessBridgeUrl = (context: ResolutionContext) => {
return context.get(AppController).getApiAccessBridgeUrl();
};
6 changes: 3 additions & 3 deletions packages/common/src/modules/functions/getIntegrationType.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { interfaces } from 'inversify';
import type { ResolutionContext } from 'inversify';

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

/**
* This function is used to get the integration type from the AppController and is mainly used for getting named
* modules from the container registry.
*/
export const getIntegrationType = (context: interfaces.Context) => {
return context.container.get(AppController).getIntegrationType();
export const getIntegrationType = (context: ResolutionContext) => {
return context.get(AppController).getIntegrationType();
};
16 changes: 8 additions & 8 deletions packages/common/src/modules/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ 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);
container.bind(EpgService).to(JWEpgService).whenNamed(EPG_TYPE.jwp);
container.bind(EpgService).to(ViewNexaEpgService).whenNamed(EPG_TYPE.viewNexa);

// Functions
container.bind(INTEGRATION_TYPE).toDynamicValue(getIntegrationType);
Expand All @@ -82,14 +82,14 @@ container.bind(API_ACCESS_BRIDGE_URL).toDynamicValue(getApiAccessBridgeUrl);
// Cleeng integration
container.bind(DETERMINE_INTEGRATION_TYPE).toConstantValue(isCleengIntegrationType);
container.bind(CleengService).toSelf();
container.bind(AccountService).to(CleengAccountService).whenTargetNamed(INTEGRATION.CLEENG);
container.bind(CheckoutService).to(CleengCheckoutService).whenTargetNamed(INTEGRATION.CLEENG);
container.bind(SubscriptionService).to(CleengSubscriptionService).whenTargetNamed(INTEGRATION.CLEENG);
container.bind(AccountService).to(CleengAccountService).whenNamed(INTEGRATION.CLEENG);
container.bind(CheckoutService).to(CleengCheckoutService).whenNamed(INTEGRATION.CLEENG);
container.bind(SubscriptionService).to(CleengSubscriptionService).whenNamed(INTEGRATION.CLEENG);

// JWP integration
container.bind(DETERMINE_INTEGRATION_TYPE).toConstantValue(isJwpIntegrationType);
container.bind(JWPAPIService).toSelf();
container.bind(JWPEntitlementService).toSelf();
container.bind(AccountService).to(JWPAccountService).whenTargetNamed(INTEGRATION.JWP);
container.bind(CheckoutService).to(JWPCheckoutService).whenTargetNamed(INTEGRATION.JWP);
container.bind(SubscriptionService).to(JWPSubscriptionService).whenTargetNamed(INTEGRATION.JWP);
container.bind(AccountService).to(JWPAccountService).whenNamed(INTEGRATION.JWP);
container.bind(CheckoutService).to(JWPCheckoutService).whenNamed(INTEGRATION.JWP);
container.bind(SubscriptionService).to(JWPSubscriptionService).whenNamed(INTEGRATION.JWP);
4 changes: 2 additions & 2 deletions packages/common/src/services/ApiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ export default class ApiService {
* Transform incoming content lists
*/
protected transformContentList = (contentList: ContentList, language: string): Playlist => {
const { list, ...rest } = contentList;
const { list, id, ...rest } = contentList;

const playlist: Playlist = { ...rest, playlist: [] };
const playlist: Playlist = { ...rest, feedid: id, playlist: [] };

playlist.playlist = list.map((item) => {
const { custom_params, media_id, description, tags, ...rest } = item;
Expand Down
1 change: 1 addition & 0 deletions packages/common/src/utils/configSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const contentSchema: ObjectSchema<Content> = object({
backgroundColor: string().nullable().optional(),
type: string().oneOf(['playlist', 'continue_watching', 'favorites', 'content_list']).required(),
custom: object().optional(),
filterTags: string().optional(),
}).defined();

const menuSchema: ObjectSchema<Menu> = object().shape({
Expand Down
Loading