Skip to content

Commit

Permalink
Merge branch 'release-candidate' into release
Browse files Browse the repository at this point in the history
  • Loading branch information
AntonLantukh committed Jun 13, 2024
2 parents 301d829 + b2e7a8f commit e4c4bd0
Show file tree
Hide file tree
Showing 116 changed files with 1,093 additions and 647 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @AntonLantukh @dbudzins
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
git config --global user.email 'ott-release-script@jwplayer.com'
git fetch origin ${{ github.ref_name }}
git merge origin/${{ github.ref_name }}
yarn && yarn i18next
yarn && yarn workspace @jwp/ott-web run i18next
env:
GITHUB_TOKEN: ${{ secrets.ACTION_TOKEN }}

Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/web-test-deploy-preview-and-lighthouse.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ defaults:
jobs:
build_and_preview:
name: Build and preview
if: '! github.event.pull_request.head.repo.fork '
runs-on: ubuntu-latest
outputs:
output1: ${{ steps.firebase_hosting_preview.outputs.details_url }}
Expand All @@ -29,6 +30,7 @@ jobs:

lhci:
name: Lighthouse
if: '! github.event.pull_request.head.repo.fork '
runs-on: ubuntu-latest
needs: build_and_preview
steps:
Expand Down
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,31 @@
## [6.2.0](https://github.com/jwplayer/ott-web-app/compare/v6.1.2...v6.2.0) (2024-06-13)


### Features

* **auth:** disable social login features ([f504e8f](https://github.com/jwplayer/ott-web-app/commit/f504e8f481730ce21e48abf0c31a5e54a31cba1c))
* extend minimum browser support using APP_LEGACY_BUILD env-var ([1794113](https://github.com/jwplayer/ott-web-app/commit/1794113cce60dbb02ff5ab999b132b354d14a661))
* **payment:** show external payment explanation ([7efffc7](https://github.com/jwplayer/ott-web-app/commit/7efffc797e37d244ca2c3f2a81d88187bbe6dfe6))
* **profiles:** remove profiles from the web app ([e507314](https://github.com/jwplayer/ott-web-app/commit/e50731405626bdbe0f29fde61402bac34d669af8))
* **project:** implement i18next-parser-workspaces cli ([2fa5bfd](https://github.com/jwplayer/ott-web-app/commit/2fa5bfd34ffad9561cb598a9cbd7ea57043de807))
* **project:** make button states consistent ([#546](https://github.com/jwplayer/ott-web-app/issues/546)) ([07c8c55](https://github.com/jwplayer/ott-web-app/commit/07c8c55b35e848a1a3c771556fce12f26e84b0e6))
* **project:** support live channel without tv guide ([92564d4](https://github.com/jwplayer/ott-web-app/commit/92564d42b7a43661d4a94b7ca7029ea91b3b1104))
* **project:** use content type for live events ([6e6b2d6](https://github.com/jwplayer/ott-web-app/commit/6e6b2d64de729fe3f59f991039ffd8a3c56ef827))


### Bug Fixes

* **a11y:** ensure label is present for buttons in busy state ([a388e48](https://github.com/jwplayer/ott-web-app/commit/a388e488c8e10fab8cb13cf26687d2c643e6555e))
* **a11y:** hide focus outline when not tabbing ([3d1cbf1](https://github.com/jwplayer/ott-web-app/commit/3d1cbf16e010c595c1af72eaa35af50fee8045ac))
* **a11y:** make related videos section semantically correct ([57e2641](https://github.com/jwplayer/ott-web-app/commit/57e2641f92cf5db8a5ccd7d9d0cce34123c5d675))
* **epg:** live tag overlap issues ([448c196](https://github.com/jwplayer/ott-web-app/commit/448c19660e83b665c8fb45cd4b7e34021219ed22))
* generic consent_required validation message ([7146972](https://github.com/jwplayer/ott-web-app/commit/71469721cacb6224e080910e5491da7935f5eea9))
* generic required validation message & show after submission ([70c34c5](https://github.com/jwplayer/ott-web-app/commit/70c34c54cb6d5770cd670034fceaf26f9b91e5e6))
* **payment:** waiting for payment not working for jwp ppv ([8d92adc](https://github.com/jwplayer/ott-web-app/commit/8d92adc3f666a2cfe512401fc405e8cfcb4af6cd))
* **project:** hide live event date for live channels ([6009ef5](https://github.com/jwplayer/ott-web-app/commit/6009ef55c9966cecd37a5986bb2e50912e3db65c))
* **project:** remove focus outline around app ([a706d0e](https://github.com/jwplayer/ott-web-app/commit/a706d0e2f99fa35f4033c9b7b7b7d6e85c546a83))
* **project:** restrict text length to avoid scrolling issue ([#538](https://github.com/jwplayer/ott-web-app/issues/538)) ([18a1990](https://github.com/jwplayer/ott-web-app/commit/18a19903cddaa3d88eb2047fe01b530ac6a1efb1))

## [6.1.2](https://github.com/jwplayer/ott-web-app/compare/v6.1.1...v6.1.2) (2024-05-21)


Expand Down
28 changes: 0 additions & 28 deletions i18next-parser.config.js

This file was deleted.

5 changes: 4 additions & 1 deletion knip.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,15 @@ const config: KnipConfig = {
'@babel/core', // Required peer dependency for babel plugins
'@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',
'luxon', // Used in tests
'playwright', // Used in test configs
'sharp', // Requirement for @vite-pwa/assets-generator
'tsconfig-paths', // Used for e2e test setup
'virtual:pwa-register', // Service Worker code is injected at build time,
'virtual:pwa-register', // Service Worker code is injected at build time
'virtual:polyfills', // Polyfills are conditionally injected
],
},
'configs/eslint-config-jwp': {
Expand Down
7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@jwp/ott",
"version": "6.1.2",
"version": "6.2.0",
"private": true,
"license": "Apache-2.0",
"repository": "https://github.com/jwplayer/ott-web-app.git",
Expand All @@ -21,9 +21,8 @@
"format:eslint": "eslint \"{**/*,*}.{js,ts,jsx,tsx}\" --fix",
"format:prettier": "prettier --write \"{**/*,*}.{js,ts,jsx,tsx}\"",
"format:stylelint": "stylelint --fix '**/*.{css,scss}'",
"i18next": "i18next 'platforms/*/src/**/*.{ts,tsx}' 'packages/*/src/**/*.{ts,tsx}' && node ./scripts/i18next/generate.js",
"i18next-diff": "npx ts-node ./scripts/i18next/diff-translations",
"i18next-update": "npx ts-node ./scripts/i18next/update-translations.ts && yarn i18next",
"i18next-update": "npx ts-node ./scripts/i18next/update-translations.ts && yarn workspace @jwp/ott-web run i18next",
"lint": "run-p -c lint:*",
"lint:eslint": "eslint \"{**/*,*}.{js,ts,jsx,tsx}\"",
"lint:prettier": "prettier --check \"{**/*,*}.{js,ts,jsx,tsx}\"",
Expand All @@ -43,7 +42,7 @@
"csv-parse": "^5.4.0",
"eslint": "^8.57.0",
"husky": "^6.0.0",
"i18next-parser": "^8.0.0",
"i18next-parser-workspaces": "^0.2.0",
"knip": "^5.0.3",
"lint-staged": "^15.1.0",
"npm-run-all": "^4.1.5",
Expand Down
6 changes: 4 additions & 2 deletions packages/common/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,12 @@ export const CONTENT_TYPE = {
series: 'series',
// Separate episode page
episode: 'episode',
// Page with a list of channels
// Page with a list of live channels
live: 'live',
// Separate channel page
// Live channel (24x7)
liveChannel: 'livechannel',
// Temporary live stream that starts at a specific time
liveEvent: 'liveevent',
// Static page with markdown
page: 'page',
// Page with shelves list
Expand Down
2 changes: 1 addition & 1 deletion packages/common/src/controllers/AccountController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ export default class AccountController {
let pendingOffer: Offer | null = null;

if (!activeSubscription && !!retry && retry > 0) {
const retryDelay = 1500; // Any initial delay has already occured, so we can set this to a fixed value
const retryDelay = 1500; // Any initial delay has already occurred, so we can set this to a fixed value

return await this.reloadSubscriptions({ delay: retryDelay, retry: retry - 1 });
}
Expand Down
8 changes: 3 additions & 5 deletions packages/common/src/controllers/CheckoutController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,13 +242,11 @@ export default class CheckoutController {
const { getAccountInfo } = useAccountStore.getState();

const { customerId } = getAccountInfo();

assertModuleMethod(this.checkoutService.getSubscriptionSwitches, 'getSubscriptionSwitches is not available in checkout service');
assertModuleMethod(this.checkoutService.getOffer, 'getOffer is not available in checkout service');

const { subscription } = useAccountStore.getState();

if (!subscription) return null;
if (!subscription || !this.checkoutService.getSubscriptionSwitches) return null;

assertModuleMethod(this.checkoutService.getOffer, 'getOffer is not available in checkout service');

const response = await this.checkoutService.getSubscriptionSwitches({
customerId: customerId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,38 @@ export default class JWPCheckoutService extends CheckoutService {
};
};

private formatOffer = (offer: AccessFee): Offer => {
/**
* Format a (Cleeng like) offer id for the given access fee (pricing option). For JWP, we need the asset id and
* access fee id in some cases.
*/
private formatOfferId(offer: AccessFee) {
const ppvOffers = ['ppv', 'ppv_custom'];
const offerId = ppvOffers.includes(offer.access_type.name) ? `C${offer.id}` : `S${offer.id}`;

return ppvOffers.includes(offer.access_type.name) ? `C${offer.item_id}_${offer.id}` : `S${offer.item_id}_${offer.id}`;
}

/**
* Parse the given offer id and extract the asset id.
* The offer id might be the Cleeng format (`S<assetId>_<pricingOptionId>`) or the asset id as string.
*/
private parseOfferId(offerId: string | number) {
if (typeof offerId === 'string') {
// offer id format `S<assetId>_<pricingOptionId>`
if (offerId.startsWith('C') || offerId.startsWith('S')) {
return parseInt(offerId.slice(1).split('_')[0]);
}

// offer id format `<assetId>`
return parseInt(offerId);
}

return offerId;
}

private formatOffer = (offer: AccessFee): Offer => {
return {
id: offer.id,
offerId,
offerId: this.formatOfferId(offer),
offerCurrency: offer.currency,
customerPriceInclTax: offer.amount,
customerCurrency: offer.currency,
Expand Down Expand Up @@ -97,9 +122,9 @@ export default class JWPCheckoutService extends CheckoutService {

getOffers: GetOffers = async (payload) => {
const offers = await Promise.all(
payload.offerIds.map(async (assetId) => {
payload.offerIds.map(async (offerId) => {
try {
const { data } = await InPlayer.Asset.getAssetAccessFees(parseInt(`${assetId}`));
const { data } = await InPlayer.Asset.getAssetAccessFees(this.parseOfferId(offerId));

return data?.map((offer) => this.formatOffer(offer));
} catch {
Expand Down Expand Up @@ -217,7 +242,7 @@ export default class JWPCheckoutService extends CheckoutService {

getEntitlements: GetEntitlements = async ({ offerId }) => {
try {
const response = await InPlayer.Asset.checkAccessForAsset(parseInt(offerId));
const response = await InPlayer.Asset.checkAccessForAsset(this.parseOfferId(offerId));
return this.formatEntitlements(response.data.expires_at, true);
} catch {
return this.formatEntitlements();
Expand Down
4 changes: 3 additions & 1 deletion packages/common/src/utils/ScreenMap.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { Playlist, PlaylistItem } from '../../types/playlist';

import { isContentType } from './common';

type ScreenPredicate<T> = (data?: T) => boolean;

type ScreenDefinition<T, C> = {
Expand All @@ -16,7 +18,7 @@ export class ScreenMap<T extends Playlist | PlaylistItem, C> {
}

registerByContentType(component: C, contentType: string) {
this.register(component, (data) => data?.contentType?.toLowerCase() === contentType);
this.register(component, (data) => !!data && isContentType(data, contentType));
}

registerDefault(component: C) {
Expand Down
4 changes: 4 additions & 0 deletions packages/common/src/utils/common.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { Playlist, PlaylistItem } from '../../types/playlist';

export function debounce<T extends (...args: any[]) => void>(callback: T, wait = 200) {
let timeout: NodeJS.Timeout | null;
return (...args: unknown[]) => {
Expand Down Expand Up @@ -102,6 +104,8 @@ export function logDev(message: unknown, ...optionalParams: unknown[]) {
}
}

export const isContentType = (item: PlaylistItem | Playlist, contentType: string) => item.contentType?.toLowerCase() === contentType.toLowerCase();

export const isTruthyCustomParamValue = (value: unknown): boolean => ['true', '1', 'yes', 'on'].includes(String(value)?.toLowerCase());

export const isFalsyCustomParamValue = (value: unknown): boolean => ['false', '0', 'no', 'off'].includes(String(value)?.toLowerCase());
Expand Down
5 changes: 4 additions & 1 deletion packages/common/src/utils/liveEvent.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import type { PlaylistItem } from '../../types/playlist';
import { CONTENT_TYPE } from '../constants';

import { isContentType } from './common';

export enum EventState {
PRE_LIVE = 'PRE_LIVE',
Expand All @@ -15,7 +18,7 @@ export const enum MediaStatus {
}

export const isLiveEvent = (playlistItem?: PlaylistItem) => {
return !!playlistItem && !!playlistItem['VCH.EventState'] && !!playlistItem['VCH.ScheduledStart'];
return !!playlistItem && isContentType(playlistItem, CONTENT_TYPE.liveEvent);
};

export const isScheduledOrLiveMedia = (playlistItem: PlaylistItem) => {
Expand Down
14 changes: 6 additions & 8 deletions packages/common/src/utils/media.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import type { Playlist, PlaylistItem } from '../../types/playlist';
import type { PlaylistItem } from '../../types/playlist';
import { CONTENT_TYPE } from '../constants';

import { isContentType } from './common';

type RequiredProperties<T, P extends keyof T> = T & Required<Pick<T, P>>;

type DeprecatedPlaylistItem = {
seriesPlayListId?: string;
seriesPlaylistId?: string;
};

export const isPlaylist = (item: unknown): item is Playlist => !!item && typeof item === 'object' && 'feedid' in item;
export const isPlaylistItem = (item: unknown): item is PlaylistItem => !!item && typeof item === 'object' && 'mediaid' in item;

// For the deprecated series flow we store seriesId in custom params
export const getSeriesPlaylistIdFromCustomParams = (item: (PlaylistItem & DeprecatedPlaylistItem) | undefined) =>
item ? item.seriesPlayListId || item.seriesPlaylistId || item.seriesId : undefined;
Expand All @@ -22,12 +21,12 @@ export const isLegacySeriesFlow = (item: PlaylistItem) => {

// For the new series flow we use contentType custom param to define media item to be series
// In this case media item and series item have the same id
export const isSeriesContentType = (item: PlaylistItem) => item.contentType?.toLowerCase() === CONTENT_TYPE.series;
export const isSeriesContentType = (item: PlaylistItem) => isContentType(item, CONTENT_TYPE.series);

export const isSeries = (item: PlaylistItem) => isLegacySeriesFlow(item) || isSeriesContentType(item);

export const isEpisode = (item: PlaylistItem) => {
return typeof item?.episodeNumber !== 'undefined' || item?.contentType?.toLowerCase() === CONTENT_TYPE.episode;
return typeof item?.episodeNumber !== 'undefined' || isContentType(item, CONTENT_TYPE.episode);
};

export const getLegacySeriesPlaylistIdFromEpisodeTags = (item: PlaylistItem | undefined) => {
Expand All @@ -47,5 +46,4 @@ export const getLegacySeriesPlaylistIdFromEpisodeTags = (item: PlaylistItem | un
return;
};

export const isLiveChannel = (item: PlaylistItem): item is RequiredProperties<PlaylistItem, 'contentType' | 'liveChannelsId'> =>
item.contentType?.toLowerCase() === CONTENT_TYPE.liveChannel && !!item.liveChannelsId;
export const isLiveChannel = (item: PlaylistItem): item is RequiredProperties<PlaylistItem, 'contentType'> => isContentType(item, CONTENT_TYPE.liveChannel);
18 changes: 11 additions & 7 deletions packages/hooks-react/src/useCheckAccess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ type IntervalCheckAccessPayload = {
interval?: number;
iterations?: number;
offerId?: string;
callback?: (hasAccess: boolean) => void;
callback?: ({ hasAccess, offerId }: { hasAccess: boolean; offerId: string }) => void;
};

const useCheckAccess = () => {
Expand All @@ -22,21 +22,25 @@ const useCheckAccess = () => {
const offers = checkoutController.getSubscriptionOfferIds();

const intervalCheckAccess = useCallback(
({ interval = 3000, iterations = 5, offerId, callback }: IntervalCheckAccessPayload) => {
if (!offerId && offers?.[0]) {
offerId = offers[0];
({ interval = 3000, iterations = 5, offerId = offers?.[0], callback }: IntervalCheckAccessPayload) => {
if (!offerId) {
callback?.({ hasAccess: false, offerId: '' });
return;
}

intervalRef.current = window.setInterval(async () => {
const hasAccess = await accountController.checkEntitlements(offerId);

if (hasAccess) {
await accountController.reloadSubscriptions({ retry: 10, delay: 2000 });
callback?.(true);
window.clearInterval(intervalRef.current);
// No duplicate retry mechanism. This can also be a TVOD offer which isn't validated using the
// reloadSubscriptions method.
await accountController.reloadSubscriptions();
callback?.({ hasAccess: true, offerId: offerId || '' });
} else if (--iterations === 0) {
window.clearInterval(intervalRef.current);
setErrorMessage(t('payment.longer_than_usual'));
callback?.(false);
callback?.({ hasAccess: false, offerId: offerId || '' });
}
}, interval);
},
Expand Down
7 changes: 4 additions & 3 deletions packages/ui-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
"yup": "^0.32.9"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^14.2.1",
"@types/dompurify": "^2.3.4",
"@types/jwplayer": "^8.2.13",
"@types/marked": "^4.0.7",
Expand All @@ -46,7 +46,8 @@
"vi-fetch": "^0.8.0",
"vite-plugin-svgr": "^4.2.0",
"vitest": "^1.3.1",
"wicg-inert": "^3.1.2"
"wicg-inert": "^3.1.2",
"vitest-axe": "^1.0.0-pre.3"
},
"peerDependencies": {
"@jwp/ott-common": "*",
Expand Down
Loading

0 comments on commit e4c4bd0

Please sign in to comment.