eb-player 2.4.2 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.EBPlayer = {}));
|
|
5
5
|
})(this, (function (exports) { 'use strict';
|
|
6
6
|
|
|
7
|
-
var __EB_PLAYER_VERSION__ = "2.
|
|
7
|
+
var __EB_PLAYER_VERSION__ = "2.5.0";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Finite State Machine for player playback state transitions.
|
|
@@ -5236,6 +5236,11 @@
|
|
|
5236
5236
|
if (text.trim().startsWith('{')) {
|
|
5237
5237
|
return TokenResponse.fromJson({ src, json: text });
|
|
5238
5238
|
}
|
|
5239
|
+
const fullUrlResponse = parseFullUrlResponse(text);
|
|
5240
|
+
if (fullUrlResponse) {
|
|
5241
|
+
const responseParameters = { token: null, tokenPath: null, ...fullUrlResponse };
|
|
5242
|
+
return new TokenResponse({ src, responseParameters });
|
|
5243
|
+
}
|
|
5239
5244
|
const params = new URLSearchParams(text.trim().replace(/^\?/, ''));
|
|
5240
5245
|
const token = params.get('token') ?? null;
|
|
5241
5246
|
const expiresStr = params.get('expires');
|
|
@@ -5274,8 +5279,11 @@
|
|
|
5274
5279
|
// Akamai response parameter parsing (inlined from CDNtoken/akamaiResponseParameters.js)
|
|
5275
5280
|
// ---------------------------------------------------------------------------
|
|
5276
5281
|
function parseAkamaiResponse({ src, text }) {
|
|
5277
|
-
|
|
5278
|
-
|
|
5282
|
+
// The token server may answer with a complete replacement URL (Akamai "URL
|
|
5283
|
+
// rewrite": a token path segment inserted after the origin, plus a query
|
|
5284
|
+
// `hdnea`). That URL is the result, not a fragment to merge into `src`.
|
|
5285
|
+
const fullUrl = parseFullUrlResponse(text)?.fullUrl ?? null;
|
|
5286
|
+
const srcUrl = new URL(fullUrl ?? `${src}${text ? `?${text}` : ''}`);
|
|
5279
5287
|
let hdntl = null;
|
|
5280
5288
|
let exp = null;
|
|
5281
5289
|
let hmac = null;
|
|
@@ -5309,7 +5317,83 @@
|
|
|
5309
5317
|
if (aclMatch)
|
|
5310
5318
|
acl = aclMatch[1];
|
|
5311
5319
|
}
|
|
5312
|
-
return { expires: exp, hdntl: tokenValue, hmac, acl, hdnea };
|
|
5320
|
+
return { expires: exp, hdntl: tokenValue, hmac, acl, hdnea, fullUrl };
|
|
5321
|
+
}
|
|
5322
|
+
// ---------------------------------------------------------------------------
|
|
5323
|
+
// Full-URL token responses (Akamai ESI Token Authority / URL rewrite)
|
|
5324
|
+
// ---------------------------------------------------------------------------
|
|
5325
|
+
const ABSOLUTE_URL_PATTERN = /^https?:\/\//i;
|
|
5326
|
+
/**
|
|
5327
|
+
* Detects a token response that is itself a complete URL, e.g.
|
|
5328
|
+
* `https://cdn/<token-segment>/path/asset.m3u8?hdnea=exp=...~acl=...~hmac=...`.
|
|
5329
|
+
* `expires` comes from an `expires` query param when present, otherwise from
|
|
5330
|
+
* the `exp=` field of the `hdnea`/`hdntl` token.
|
|
5331
|
+
*/
|
|
5332
|
+
function parseFullUrlResponse(text) {
|
|
5333
|
+
const fullUrl = text.trim();
|
|
5334
|
+
if (!ABSOLUTE_URL_PATTERN.test(fullUrl)) {
|
|
5335
|
+
return null;
|
|
5336
|
+
}
|
|
5337
|
+
const expiresParam = new URL(fullUrl).searchParams.get('expires');
|
|
5338
|
+
if (expiresParam) {
|
|
5339
|
+
return { fullUrl, expires: parseInt(expiresParam, 10) };
|
|
5340
|
+
}
|
|
5341
|
+
return { fullUrl, expires: parseAkamaiResponse({ src: fullUrl, text: '' }).expires };
|
|
5342
|
+
}
|
|
5343
|
+
function fullUrlOf(tokenResponse) {
|
|
5344
|
+
const params = tokenResponse?.responseParameters;
|
|
5345
|
+
return params && 'fullUrl' in params ? params.fullUrl ?? null : null;
|
|
5346
|
+
}
|
|
5347
|
+
function rewriteGrantOf(tokenResponse) {
|
|
5348
|
+
const params = tokenResponse?.responseParameters;
|
|
5349
|
+
const fullUrl = fullUrlOf(tokenResponse);
|
|
5350
|
+
if (!tokenResponse || !params || !fullUrl) {
|
|
5351
|
+
return null;
|
|
5352
|
+
}
|
|
5353
|
+
const original = new URL(tokenResponse.src);
|
|
5354
|
+
const rewritten = new URL(fullUrl);
|
|
5355
|
+
if (!rewritten.pathname.endsWith(original.pathname)) {
|
|
5356
|
+
return null;
|
|
5357
|
+
}
|
|
5358
|
+
const insertedPath = rewritten.pathname.slice(0, rewritten.pathname.length - original.pathname.length);
|
|
5359
|
+
return {
|
|
5360
|
+
fullUrl,
|
|
5361
|
+
originalBase: original.origin,
|
|
5362
|
+
rewrittenBase: rewritten.origin + insertedPath,
|
|
5363
|
+
search: rewritten.search,
|
|
5364
|
+
expires: params.expires
|
|
5365
|
+
};
|
|
5366
|
+
}
|
|
5367
|
+
/**
|
|
5368
|
+
* Maps a URL back to its un-rewritten form (token segment and `hdnea`/`hdntl`
|
|
5369
|
+
* query removed). Feeding an already-rewritten URL to the token server makes
|
|
5370
|
+
* it stack a second token segment, so refreshes must go through this first.
|
|
5371
|
+
* Returns null when the URL is not under the grant.
|
|
5372
|
+
*/
|
|
5373
|
+
function unrewriteUrl(url, grant) {
|
|
5374
|
+
if (!url.startsWith(`${grant.rewrittenBase}/`)) {
|
|
5375
|
+
return null;
|
|
5376
|
+
}
|
|
5377
|
+
const original = new URL(grant.originalBase + url.slice(grant.rewrittenBase.length));
|
|
5378
|
+
original.searchParams.delete('hdnea');
|
|
5379
|
+
original.searchParams.delete('hdntl');
|
|
5380
|
+
return original.toString();
|
|
5381
|
+
}
|
|
5382
|
+
/**
|
|
5383
|
+
* Applies a grant to a URL (original or already rewritten): the token segment
|
|
5384
|
+
* is inserted after the origin and the server's query string appended
|
|
5385
|
+
* verbatim. Returns null when the URL lives on another origin.
|
|
5386
|
+
*/
|
|
5387
|
+
function rewriteUrl(url, grant) {
|
|
5388
|
+
const original = new URL(unrewriteUrl(url, grant) ?? url);
|
|
5389
|
+
if (original.origin !== grant.originalBase) {
|
|
5390
|
+
return null;
|
|
5391
|
+
}
|
|
5392
|
+
original.searchParams.delete('hdnea');
|
|
5393
|
+
original.searchParams.delete('hdntl');
|
|
5394
|
+
const ownQuery = original.search;
|
|
5395
|
+
const query = ownQuery ? `${ownQuery}&${grant.search.slice(1)}` : grant.search;
|
|
5396
|
+
return `${grant.rewrittenBase}${original.pathname}${query}`;
|
|
5313
5397
|
}
|
|
5314
5398
|
// ---------------------------------------------------------------------------
|
|
5315
5399
|
// Internal wait helper
|
|
@@ -5636,6 +5720,13 @@
|
|
|
5636
5720
|
if (!this.isTokenExpired(akamaiParams.expires)) {
|
|
5637
5721
|
return url;
|
|
5638
5722
|
}
|
|
5723
|
+
// A cached full-URL grant covers every URL under the same origin
|
|
5724
|
+
// (child playlists, segments) without another token request.
|
|
5725
|
+
const grant = rewriteGrantOf(this.lastTokenResponse);
|
|
5726
|
+
const reused = grant && !this.isTokenExpired(grant.expires) ? rewriteUrl(url, grant) : null;
|
|
5727
|
+
if (reused) {
|
|
5728
|
+
return reused;
|
|
5729
|
+
}
|
|
5639
5730
|
// Remove hdntl / hdnea path segments before requesting new token
|
|
5640
5731
|
const pathSegments = srcUrl.pathname.split('/');
|
|
5641
5732
|
srcUrl.pathname = pathSegments
|
|
@@ -5655,6 +5746,10 @@
|
|
|
5655
5746
|
if (cachedResponse) {
|
|
5656
5747
|
const cachedParams = cachedResponse.responseParameters;
|
|
5657
5748
|
if (cachedParams && !this.isTokenExpired(cachedParams.expires)) {
|
|
5749
|
+
const grant = rewriteGrantOf(cachedResponse);
|
|
5750
|
+
if (grant) {
|
|
5751
|
+
return rewriteUrl(url, grant) ?? url;
|
|
5752
|
+
}
|
|
5658
5753
|
const { token, expires: cachedExpires, tokenPath } = cachedParams;
|
|
5659
5754
|
return CDNTokenManager.appendTokenParams(url, token, cachedExpires, tokenPath);
|
|
5660
5755
|
}
|
|
@@ -5675,7 +5770,11 @@
|
|
|
5675
5770
|
const sharesPrefixScopedGrant = this.tokenType === TOKEN_TYPES.EASY_B
|
|
5676
5771
|
|| this.tokenType === TOKEN_TYPES.VENOM
|
|
5677
5772
|
|| this.tokenType === TOKEN_TYPES.BUNNY;
|
|
5678
|
-
|
|
5773
|
+
// A previous full-URL grant may have rewritten this URL; ask the token
|
|
5774
|
+
// server about the original, or it stacks a second token segment.
|
|
5775
|
+
const previousGrant = rewriteGrantOf(this.lastTokenResponse);
|
|
5776
|
+
const originalUrl = (previousGrant && unrewriteUrl(srcUrl.toString(), previousGrant)) ?? srcUrl.toString();
|
|
5777
|
+
const fetchSrc = (sharesPrefixScopedGrant && this.sourceUrl) ? this.sourceUrl : originalUrl;
|
|
5679
5778
|
try {
|
|
5680
5779
|
const tokenResponse = await this.fetchToken({ src: fetchSrc });
|
|
5681
5780
|
if (tokenResponse === null) {
|
|
@@ -5691,6 +5790,20 @@
|
|
|
5691
5790
|
}
|
|
5692
5791
|
return url;
|
|
5693
5792
|
}
|
|
5793
|
+
// Full-URL response: the server's URL is the result. When the response
|
|
5794
|
+
// was minted for another URL (deduplicated in-flight fetch, or the
|
|
5795
|
+
// easy_b asset-level source), apply its grant to this one instead.
|
|
5796
|
+
const fullUrl = fullUrlOf(tokenResponse);
|
|
5797
|
+
if (fullUrl) {
|
|
5798
|
+
if (tokenResponse.src === originalUrl) {
|
|
5799
|
+
return fullUrl;
|
|
5800
|
+
}
|
|
5801
|
+
const grant = rewriteGrantOf(tokenResponse);
|
|
5802
|
+
const rewritten = grant ? rewriteUrl(originalUrl, grant) : null;
|
|
5803
|
+
if (rewritten) {
|
|
5804
|
+
return rewritten;
|
|
5805
|
+
}
|
|
5806
|
+
}
|
|
5694
5807
|
switch (this.tokenType) {
|
|
5695
5808
|
case TOKEN_TYPES.BUNNY:
|
|
5696
5809
|
case TOKEN_TYPES.VENOM:
|