switch to query param based Auth (#103)

* switch to query param based Auth

* remove some changes commited by mistake.

* fixed tslint issues

* change the dash to authorization header

* remove credentials headers
This commit is contained in:
giakas 2022-01-10 13:02:03 -08:00 коммит произвёл GitHub
Родитель 88acf93e2d
Коммит ac8278a006
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 96 добавлений и 133 удалений

Просмотреть файл

@ -1,4 +1,6 @@
import { WidgetGeneralError } from '../../../widgets/src';
import { MediaApi } from '../media/media-api.class';
import { VideoContentToken } from '../media/media.definitions';
import { TokenHandler } from './token-handler.class';
export class AvaAPi {
@ -29,14 +31,14 @@ export class AvaAPi {
headers['Authorization'] = `Bearer ${TokenHandler.avaAPIToken}`;
const response = await fetch(url, {
credentials: 'include',
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${TokenHandler.avaAPIToken}`
}
});
const data = await response.json();
const data: VideoContentToken = await response.json();
MediaApi.contentToken = data.Token;
if (!data.ExpirationDate) {
throw new WidgetGeneralError('Invalid cookie expiration');
}

Просмотреть файл

@ -5,6 +5,7 @@ export class MediaApi {
private static _rtspStream: string | undefined;
private static _format = MediaApi.supportsMediaSource() ? VideoFormat.DASH : VideoFormat.HLS;
private static _videoEntity: VideoEntity;
private static _contentToken: string;
public static supportsMediaSource(): boolean {
return !!window.MediaSource;
@ -16,6 +17,14 @@ export class MediaApi {
this._rtspStream = this._videoEntity?.properties?.contentUrls?.rtspTunnelUrl;
}
public static set contentToken(value: string) {
this._contentToken = value;
}
public static get contentToken() {
return this._contentToken;
}
public static isApple(): boolean {
return !!navigator.vendor && navigator.vendor.includes('Apple');
}
@ -37,18 +46,21 @@ export class MediaApi {
}
public static get rtspStream() {
return this._rtspStream;
return this.addTokenQueryParam(this._rtspStream, 'authorization');
}
public static get liveStream(): string {
// if RTSP is present use RTSP URL.
let liveUrl = '';
if (this._rtspStream && this.supportsMediaSource()) {
return this._rtspStream;
}
const format = MediaApi._format === VideoFormat.HLS ? 'm3u8-cmaf' : 'mpd-time-cmaf';
const extension = MediaApi._format === VideoFormat.HLS ? '.m3u8' : '.mpd';
liveUrl = this.rtspStream;
} else {
const format = MediaApi._format === VideoFormat.HLS ? 'm3u8-cmaf' : 'mpd-time-cmaf';
const extension = MediaApi._format === VideoFormat.HLS ? '.m3u8' : '.mpd';
return `${this.baseStream}/manifest(format=${format})${extension}`;
liveUrl = `${this.baseStream}/manifest(format=${format})${extension}`;
}
return MediaApi._format === VideoFormat.HLS ? this.addTokenQueryParam(liveUrl) : liveUrl;
}
public static getVODStream(range: IExpandedTimeRange = null): string {
@ -66,7 +78,8 @@ export class MediaApi {
}
}
return `${this.baseStream}/manifest(format=${format}${range_query})${extension}`;
const url = `${this.baseStream}/manifest(format=${format}${range_query})${extension}`;
return MediaApi._format === VideoFormat.HLS ? this.addTokenQueryParam(url) : url;
}
public static getVODStreamForCLip(startTime: Date, endTime: Date): string {
@ -80,15 +93,11 @@ export class MediaApi {
range_query = `,starttime=${startTimeISOFormat},endtime=${endTimeISOFormat}`;
}
return `${this.baseStream}/manifest(format=${format}${range_query})${extension}`;
const url = `${this.baseStream}/manifest(format=${format}${range_query})${extension}`;
return MediaApi._format === VideoFormat.HLS ? this.addTokenQueryParam(url) : url;
}
public static getAvailableMedia(
precision: Precision,
range: IExpandedTimeRange = null,
allowCrossSiteCredentials = true,
token?: string
): Promise<Response> {
public static getAvailableMedia(precision: Precision, range: IExpandedTimeRange = null): Promise<Response> {
// time ranges are required for month, day and full
if ((precision === Precision.MONTH || precision === Precision.DAY || precision === Precision.FULL) && !range) {
throw Error('wrong parameters');
@ -100,17 +109,7 @@ export class MediaApi {
const url = `${this.baseStream}/availableMedia${range_query}`;
// eslint-disable-next-line no-undef
const requestInit: RequestInit = {};
if (allowCrossSiteCredentials) {
requestInit.credentials = 'include';
}
if (token) {
requestInit.headers = {
Authorization: `Bearer ${token}`
};
}
const requestInit: RequestInit = { headers: { Authorization: `Bearer ${this.contentToken}` } };
return fetch(url, requestInit);
}
@ -127,4 +126,10 @@ export class MediaApi {
return '';
}
}
private static addTokenQueryParam(urlString: string, paramName = 'token') {
const url = new URL(urlString);
url.searchParams.set(paramName, encodeURIComponent(this.contentToken));
return url.toString();
}
}

Просмотреть файл

@ -84,3 +84,8 @@ export interface SystemData {
lastModifiedByType?: string;
lastModifiedAt?: Date;
}
export interface VideoContentToken {
readonly ExpirationDate?: Date;
readonly Token?: string;
}

Просмотреть файл

@ -83,13 +83,7 @@ export class PlayerComponent extends FASTElement {
PlayerWrapper.setDebugMode(debug);
}
public async init(
allowCrossSiteCredentials = true,
accessToken?: string,
allowedControllers?: ControlPanelElements[],
clipTimeRange?: IClipTimeRange,
isMuted?: boolean
) {
public async init(allowedControllers?: ControlPanelElements[], clipTimeRange?: IClipTimeRange, isMuted?: boolean) {
if (!this.connected) {
return;
}
@ -140,10 +134,10 @@ export class PlayerComponent extends FASTElement {
this.isMuted = isMuted ?? true;
this.initializePlayer(this.allowedControllers, allowCrossSiteCredentials, accessToken);
this.initializePlayer(this.allowedControllers);
}
public async initializePlayer(allowedControllers: ControlPanelElements[], allowCrossSiteCredentials = true, accessToken?: string) {
public async initializePlayer(allowedControllers: ControlPanelElements[]) {
if (this.player) {
// If there was an existing error -clear state
this.clearError();
@ -166,11 +160,6 @@ export class PlayerComponent extends FASTElement {
this.player.addLoading();
if (accessToken) {
this.player.accessToken = accessToken;
}
this.player.allowCrossCred = allowCrossSiteCredentials;
if (!MediaApi.videoFlags.canStream || (!MediaApi.baseStream && (!MediaApi.videoFlags.isInUse || !MediaApi.liveStream))) {
this.hasError = true;
this.player.removeLoading();
@ -199,12 +188,6 @@ export class PlayerComponent extends FASTElement {
this.classList.remove('loading');
}
public setPlaybackAuthorization(accessToken: string) {
if (accessToken) {
this.player.accessToken = accessToken;
}
}
public async initializeAvailableMedia(checkForLive: boolean) {
await this.fetchAvailableYears();
@ -399,7 +382,7 @@ export class PlayerComponent extends FASTElement {
public switchToDash() {
MediaApi.rtspStream = '';
this.initializePlayer(this.allowedControllers, this.player.allowCrossCred, this.player.accessToken);
this.initializePlayer(this.allowedControllers);
}
public retryStreaming() {
@ -464,23 +447,18 @@ export class PlayerComponent extends FASTElement {
private async fetchAvailableSegments(startDate: IExpandedDate, end: IExpandedDate): Promise<IAvailableMediaResponse> {
try {
const availableHours = await MediaApi.getAvailableMedia(
Precision.FULL,
{
start: {
year: startDate.year,
month: startDate.month,
day: startDate.day
},
end: {
year: end.year,
month: end.month,
day: end.day
}
const availableHours = await MediaApi.getAvailableMedia(Precision.FULL, {
start: {
year: startDate.year,
month: startDate.month,
day: startDate.day
},
this.player.allowCrossCred,
this.player.accessToken
);
end: {
year: end.year,
month: end.month,
day: end.day
}
});
return await availableHours.json();
} catch (error) {
@ -695,7 +673,7 @@ export class PlayerComponent extends FASTElement {
}
private async fetchAvailableYears() {
const availableYears = await MediaApi.getAvailableMedia(Precision.YEAR, null, this.player.allowCrossCred, this.player.accessToken);
const availableYears = await MediaApi.getAvailableMedia(Precision.YEAR, null);
try {
const yearRanges: IAvailableMediaResponse = await availableYears.json();
@ -726,23 +704,18 @@ export class PlayerComponent extends FASTElement {
private async fetchAvailableMonths(year: number) {
// Take available months according to year
try {
const availableMonths = await MediaApi.getAvailableMedia(
Precision.MONTH,
{
start: {
year: year,
month: 1,
day: 1
},
end: {
year: year,
month: 12,
day: 1
}
const availableMonths = await MediaApi.getAvailableMedia(Precision.MONTH, {
start: {
year: year,
month: 1,
day: 1
},
this.player.allowCrossCred,
this.player.accessToken
);
end: {
year: year,
month: 12,
day: 1
}
});
const monthRanges: IAvailableMediaResponse = await availableMonths.json();
// Get last available month
@ -770,23 +743,18 @@ export class PlayerComponent extends FASTElement {
const lastDayOfMonth = new Date(year, month, 1, 0, 0, 0);
lastDayOfMonth.setDate(lastDayOfMonth.getDate() - 1);
// fetch available days
const availableDays = await MediaApi.getAvailableMedia(
Precision.DAY,
{
start: {
year: year,
month: month,
day: 1
},
end: {
year: year,
month: month,
day: lastDayOfMonth.getDate()
}
const availableDays = await MediaApi.getAvailableMedia(Precision.DAY, {
start: {
year: year,
month: month,
day: 1
},
this.player.allowCrossCred,
this.player.accessToken
);
end: {
year: year,
month: month,
day: lastDayOfMonth.getDate()
}
});
const dayRanges: IAvailableMediaResponse = await availableDays.json();

Просмотреть файл

@ -33,11 +33,9 @@ export class PlayerWrapper {
private isClip = false;
private isLoaded = false;
private duringSegmentJump = false;
private _accessToken = '';
private _mimeType: MimeType;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private controls: any;
private _allowCrossCred = true;
private timestampOffset: number;
private firstSegmentStartSeconds: number;
private date: Date;
@ -110,9 +108,9 @@ export class PlayerWrapper {
if (this._liveStream === MediaApi.rtspStream) {
// getVideo API may one day return the RTSP URL, until then, hard-code it.
url.searchParams.set('rtsp', encodeURIComponent('rtsp://localhost'));
if (this.accessToken) {
url.searchParams.set('authorization', this.accessToken);
}
// if (MediaApi.contentToken) {
// url.searchParams.set('authorization', MediaApi.contentToken);
// }
}
return url.toString();
}
@ -123,14 +121,6 @@ export class PlayerWrapper {
this._vodStream = value;
}
public set allowCrossCred(value: boolean) {
this._allowCrossCred = value;
}
public get allowCrossCred() {
return this._allowCrossCred;
}
public set mimeType(value: MimeType) {
this._mimeType = value;
}
@ -154,14 +144,6 @@ export class PlayerWrapper {
this.video.play();
}
public set accessToken(accessToken: string) {
this._accessToken = accessToken;
}
public get accessToken() {
return this._accessToken;
}
public set currentDate(startDate: Date) {
this._currentDate = startDate;
}
@ -362,10 +344,7 @@ export class PlayerWrapper {
this._availableSegments?.timeRanges[this._availableSegments?.timeRanges.length - 1]?.start,
this._currentDate
);
const firstSegmentStartSeconds = extractRealTimeFromISO(
this._availableSegments?.timeRanges[0]?.start,
this._currentDate
);
const firstSegmentStartSeconds = extractRealTimeFromISO(this._availableSegments?.timeRanges[0]?.start, this._currentDate);
this.disableNextSegmentButton(this.currentSegment.startSeconds === lastSegmentStartSeconds);
this.disablePrevSegmentButton(this.currentSegment.startSeconds === firstSegmentStartSeconds);
}
@ -685,13 +664,16 @@ export class PlayerWrapper {
}
private authenticationHandler(type: shaka_player.net.NetworkingEngine.RequestType, request: shaka_player.extern.Request) {
request['allowCrossSiteCredentials'] = this._allowCrossCred;
if (!this._accessToken) {
if (!MediaApi.contentToken) {
return;
}
// Add authorization header
request.headers['Authorization'] = `Bearer ${this._accessToken}`;
const url: string = request['uris'][0];
if (url.indexOf('http') != -1) {
const urlRequest = new URL(url);
urlRequest.searchParams.set('token', MediaApi.contentToken);
request['uris'][0] = urlRequest.toString();
}
}
private onShakaMetadata(event: shaka_player.PlayerEvents.EmsgEvent) {
@ -733,6 +715,8 @@ export class PlayerWrapper {
private async onTrackChange() {
// Get player manifest
if (!MediaApi.supportsMediaSource()) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
await new Promise((resolve) => setTimeout(resolve, 1000));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const date = (this.video as any).getStartDate();
Logger.log(`video start date is ${date} ${date.getUTCDate()}`);

Просмотреть файл

@ -88,19 +88,18 @@ export class Player extends BaseWidget {
public setSource(source: ISource) {
this.source = source;
MediaApi.videoEntity = this.source.videoEntity;
MediaApi.contentToken = this.source.authenticationToken;
this.setLocalization(this.config?.locale, ['common', 'player']);
if (this.loaded) {
const playerComponent: PlayerComponent = this.shadowRoot.querySelector('media-player');
playerComponent.cameraName = AvaAPi.videoName;
playerComponent.init(this.source.allowCrossSiteCredentials, this.source.authenticationToken, this.allowedControllers);
playerComponent.init(this.allowedControllers);
}
}
public setPlaybackAuthorization(token: string) {
const playerComponent: PlayerComponent = this.shadowRoot.querySelector('media-player');
playerComponent.setPlaybackAuthorization(token);
MediaApi.contentToken = token;
}
public set apiBase(apiBase: string) {
@ -120,7 +119,7 @@ export class Player extends BaseWidget {
// If set source state
if (this.source) {
playerComponent.cameraName = AvaAPi.videoName;
playerComponent.init(this.source.allowCrossSiteCredentials, this.source.authenticationToken, this.allowedControllers);
playerComponent.init(this.allowedControllers);
return;
}
// Configuration state - work with AVA API
@ -136,7 +135,7 @@ export class Player extends BaseWidget {
// Authorize video
await AvaAPi.authorize();
playerComponent.cameraName = AvaAPi.videoName;
playerComponent.init(true, '', this.allowedControllers, this.clipTimeRange, this.isMuted);
playerComponent.init(this.allowedControllers, this.clipTimeRange, this.isMuted);
}
})
.catch((error) => {
@ -172,7 +171,7 @@ export class Player extends BaseWidget {
private handelFallback(error: HttpError) {
const player: PlayerComponent = this.shadowRoot.querySelector('media-player');
player.cameraName = AvaAPi.videoName;
player.init(true, '', this.allowedControllers);
player.init(this.allowedControllers);
player.handleError(error);
}