From 85f3beae71f76f3b432319c9575a214dce0bba5f Mon Sep 17 00:00:00 2001 From: Luca Armaroli Date: Sun, 11 Oct 2020 21:56:08 +0200 Subject: [PATCH] added access token refresh logic (closes #255) --- src/TokenCache.ts | 38 ++++++++++++++++++++++++-------------- src/destreamer.ts | 8 +++++++- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/TokenCache.ts b/src/TokenCache.ts index db9ff37..85f7ac3 100644 --- a/src/TokenCache.ts +++ b/src/TokenCache.ts @@ -9,6 +9,10 @@ import jwtDecode from 'jwt-decode'; import puppeteer from 'puppeteer'; +type Jwt = { + [key: string]: any +} + export class TokenCache { private tokenCacheFile = '.token_cache'; @@ -21,24 +25,18 @@ export class TokenCache { const session: Session = JSON.parse(fs.readFileSync(this.tokenCacheFile, 'utf8')); - type Jwt = { - [key: string]: any - } - const decodedJwt: Jwt = jwtDecode(session.AccessToken); + const [isExpiring, timeLeft] = this.isExpiring(session); - const now: number = Math.floor(Date.now() / 1000); - const exp: number = decodedJwt['exp']; - const timeLeft: number = exp - now; - - if (timeLeft < 120) { + if (isExpiring) { logger.warn('Access token has expired! \n'); return null; } + else { + logger.info(`Access token still good for ${Math.floor(timeLeft / 60)} minutes.\n`.green); - logger.info(`Access token still good for ${Math.floor(timeLeft / 60)} minutes.\n`.green); - - return session; + return session; + } } public Write(session: Session): void { @@ -50,11 +48,23 @@ export class TokenCache { logger.info('Fresh access token dropped into .token_cachen \n'.green); }); } + + public isExpiring(session: Session): [boolean, number] { + const decodedJwt: Jwt = jwtDecode(session.AccessToken); + + const timeLeft: number = decodedJwt['exp']; - Math.floor(Date.now() / 1000); + + if (timeLeft < (5 * 60)) { + return [true, 0]; + } + else { + return [false, timeLeft]; + } + } } export async function refreshSession(url: string): Promise { - const videoId: string = url.split('/').pop() ?? process.exit(ERROR_CODE.INVALID_VIDEO_GUID); const browser: puppeteer.Browser = await puppeteer.launch({ executablePath: getPuppeteerChromiumPath(), @@ -70,7 +80,7 @@ export async function refreshSession(url: string): Promise { const page: puppeteer.Page = (await browser.pages())[0]; await page.goto(url, { waitUntil: 'load' }); - await browser.waitForTarget((target: puppeteer.Target) => target.url().includes(videoId), { timeout: 30000 }); + await browser.waitForTarget((target: puppeteer.Target) => target.url().endsWith('microsoftstream.com/'), { timeout: 150000 }); let session: Session | null = null; let tries = 1; diff --git a/src/destreamer.ts b/src/destreamer.ts index 4218803..9f5f174 100644 --- a/src/destreamer.ts +++ b/src/destreamer.ts @@ -7,7 +7,7 @@ import { setProcessEvents } from './Events'; import { logger } from './Logger'; import { getPuppeteerChromiumPath } from './PuppeteerHelper'; import { drawThumbnail } from './Thumbnail'; -import { TokenCache} from './TokenCache'; +import { TokenCache, refreshSession} from './TokenCache'; import { Video, Session } from './Types'; import { checkRequirements, parseInputFile, parseCLIinput, getUrlsFromPlaylist} from './Utils'; import { getVideosInfo, createUniquePaths } from './VideoUtils'; @@ -195,6 +195,12 @@ async function downloadVideo(videoGUIDs: Array, continue; } + if (argv.keepLoginCookies && tokenCache.isExpiring(session)) { + logger.info('Trying to refresh access token...'); + session = await refreshSession('https://web.microsoftstream.com/'); + apiClient.setSession(session); + } + masterParser.push(await apiClient.callUrl(video.playbackUrl).then(res => res?.data)); masterParser.end();