UNPKG

@api.video/private-video-session

Version:
283 lines (248 loc) 7.72 kB
import md5 from "md5"; const isAssetUrlOptions = (options: Options): options is AssetUrlOptions => { return options && "assetUrl" in options; }; type AssetUrlOptions = { assetUrl: string; }; type TokenOptions = { token: string; videoId: string; }; type Domain = { live?: string; vod?: string; embed?: string; collector?: string; }; type Options = (AssetUrlOptions | TokenOptions) & { domains?: Domain }; type VideoIdPrivateToken = { host?: string; videoId: string; privateToken: string; environment?: Environment; videoType?: VideoType; subdomain?: string; }; type Environment = "production" | "staging"; type VideoType = "vod" | "live"; type UrlType = { re: RegExp; type: VideoType; env: Environment; }; export class PrivateVideoSession { private videoId: string; private privateToken: string; private environment: Environment; private videoType: VideoType; private sessionToken?: string; private domains: Domain; constructor(options: Options) { this.domains = options.domains || {}; const videoIdPrivateToken: VideoIdPrivateToken = isAssetUrlOptions(options) ? this.initFromAssetUrl(options.assetUrl) : this.initFromToken(options.token, options.videoId); this.videoId = videoIdPrivateToken.videoId; this.privateToken = videoIdPrivateToken.privateToken; this.environment = videoIdPrivateToken.environment || "production"; this.videoType = videoIdPrivateToken.videoType || "vod"; if (Object.keys(this.domains).length > 0 && !this.domains[this.videoType]) { throw new Error( `The ${this.videoType} domain is required when using ${this.videoType} assets with custom domains.` ); } } public async getHlsUrl(): Promise<string> { return this.videoType === "vod" ? `https://${this.getMediaHost()}/vod/${this.videoId}/token/${ this.privateToken }/hls/manifest.m3u8?avh=${await this.getSessionToken()}` : `https://${this.getMediaHost()}/private/${this.privateToken}/${ this.videoId }.m3u8?avh=${await this.getSessionToken()}`; } public async getPlayerUrl(): Promise<string> { return `https://${this.getEmbedHost()}/${this.videoType}/${ this.videoId }?token=${this.privateToken}&avh=${await this.getSessionToken()}`; } public async getMp4Url(): Promise<string> { if (this.videoType === "live") { throw new Error("The mp4 url is not available for live streams."); } return `https://${this.getMediaHost()}/vod/${this.videoId}/token/${ this.privateToken }/mp4/source.mp4?avh=${await this.getSessionToken()}`; } public async getThumbnailUrl(): Promise<string> { return this.videoType === "vod" ? `https://${this.getMediaHost()}/vod/${this.videoId}/token/${ this.privateToken }/thumbnail.jpg?avh=${await this.getSessionToken()}` : `https://${this.getMediaHost()}/private/${this.privateToken}/${ this.videoId }/thumbnail.jpg?avh=${await this.getSessionToken()}`; } public async getSessionToken(): Promise<string> { if (this.sessionToken) { return this.sessionToken; } const sessionStorageKey = md5(`xTokenSession_${this.privateToken}`); const storageSessionToken = sessionStorage.getItem(sessionStorageKey); if (storageSessionToken) { this.sessionToken = storageSessionToken; return storageSessionToken; } const newSessionToken = await this.fetchSessionToken(); if (newSessionToken) { sessionStorage.setItem(sessionStorageKey, newSessionToken); this.sessionToken = newSessionToken; } return newSessionToken; } private getEmbedHost(): string { if (this.domains.embed) { return this.domains.embed; } return this.environment === "production" ? "embed.api.video" : "embed.staging.api.video"; } private getMediaHost(): string { if (this.domains[this.videoType]) { return this.domains[this.videoType]!; } return this.environment === "production" ? `${this.videoType}.api.video` : `${this.videoType}.staging.api.video`; } private initFromAssetUrl(privateAssetUrl: string): VideoIdPrivateToken { const urls: UrlType[] = [ { re: new RegExp( `https:\/\/${ this.domains.vod || "vod.api.video" }\/vod\/(?<videoId>[^\/]*)\/token\/(?<privateToken>[^\/]*)\/.*`, "gm" ), type: "vod", env: "production", }, { re: new RegExp( `https:\/\/${ this.domains.vod || "vod.staging.api.video" }\/vod\/(?<videoId>[^\/]*)\/token\/(?<privateToken>[^\/]*)\/.*`, "gm" ), type: "vod", env: "staging", }, { re: new RegExp( `https:\/\/${ this.domains.embed || "embed.api.video" }\/vod\/(?<videoId>[^\/]*)\\?token=(?<privateToken>[^\/]*)`, "gm" ), type: "vod", env: "production", }, { re: new RegExp( `https:\/\/${ this.domains.embed || "embed.staging.api.video" }\/vod\/(?<videoId>[^\/]*)\\?token=(?<privateToken>[^\/]*)`, "gm" ), type: "vod", env: "staging", }, { re: new RegExp( `https:\/\/${ this.domains.live || "live.api.video" }\/private\/(?<privateToken>[^\/]*)\/(?<videoId>[^\.]*)\..*`, "gm" ), type: "live", env: "production", }, { re: new RegExp( `https:\/\/${ this.domains.live || "live.staging.api.video" }\/private\/(?<privateToken>[^\/]*)\/(?<videoId>[^\.]*)\..*`, "gm" ), type: "live", env: "staging", }, { re: new RegExp( `https:\/\/${ this.domains.embed || "embed.api.video" }\/live\/(?<videoId>[^\/]*)\\?token=(?<privateToken>[^\/]*)`, "gm" ), type: "live", env: "production", }, { re: new RegExp( `https:\/\/${ this.domains.embed || "embed.staging.api.video" }\/live\/(?<videoId>[^\/]*)\\?token=(?<privateToken>[^\/]*)`, "gm" ), type: "live", env: "staging", }, ]; for (const url of urls) { const parsed = url.re.exec(privateAssetUrl); if (parsed?.groups?.videoId && parsed.groups.privateToken) { return { videoId: parsed.groups.videoId, privateToken: parsed.groups.privateToken, environment: url.env, videoType: url.type, }; } } throw new Error( "The provided URL doesn't look like an api.video private asset URL." ); } private initFromToken( privateToken: string, videoId: string ): VideoIdPrivateToken { if (!privateToken || !videoId) { throw new Error("The provided token and videoId are invalid."); } return { videoId, privateToken, }; } private async fetchSessionToken(): Promise<string> { const response = await fetch( this.videoType === "vod" ? `https://${this.getMediaHost()}/vod/${this.videoId}/token/${ this.privateToken }/session` : `https://${this.getMediaHost()}/private/${this.privateToken}/${ this.videoId }/session` ); if (response.status !== 200) { throw new Error( "Can't retrieve a session token. The private token has probably already been used." ); } const json = await response.json(); return json.session_token; } }