@eluvio/elv-client-js 4.2.17 → 4.2.18

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eluvio/elv-client-js",
3
- "version": "4.2.17",
3
+ "version": "4.2.18",
4
4
  "description": "Javascript client for the Eluvio Content Fabric",
5
5
  "main": "src/index.js",
6
6
  "author": "Kevin Talmadge",
@@ -570,18 +570,13 @@ class AuthorizationClient {
570
570
  const stateChannelApi = "elv_channelContentRequestContext";
571
571
  const additionalParams = [JSON.stringify(audienceData)];
572
572
 
573
- const payload = await this.MakeKMSCall({
573
+ token = await this.MakeKMSCall({
574
574
  objectId,
575
575
  methodName: stateChannelApi,
576
576
  paramTypes: ["address", "address", "uint", "uint"],
577
577
  params: [this.client.signer.address, Utils.HashToAddress(objectId), value, Date.now()],
578
578
  additionalParams
579
579
  });
580
-
581
- const signature = await this.Sign(Ethers.utils.keccak256(Ethers.utils.toUtf8Bytes(payload)));
582
- const multiSig = Utils.FormatSignature(signature);
583
-
584
- token = `${payload}.${Utils.B64(multiSig)}`;
585
580
  }
586
581
 
587
582
  if(!this.noCache) {
@@ -20,6 +20,8 @@ const {
20
20
  } = require("../Validation");
21
21
 
22
22
  const MergeWith = require("lodash/mergeWith");
23
+ const LodashSet = require("lodash/set");
24
+ const LodashGet = require("lodash/get");
23
25
 
24
26
  // Note: Keep these ordered by most-restrictive to least-restrictive
25
27
  exports.permissionLevels = {
@@ -583,7 +585,7 @@ exports.ContentObjects = async function({libraryId, filterOptions={}}) {
583
585
  *
584
586
  * @returns {Promise<Object>} - Description of content object
585
587
  */
586
- exports.ContentObject = async function({objectId, versionHash, writeToken, noCache}) {
588
+ exports.ContentObject = async function({objectId, versionHash, writeToken, noCache, authorizationToken}) {
587
589
  this.Log(`Retrieving content object: ${writeToken || versionHash || objectId}`);
588
590
 
589
591
  if(writeToken) {
@@ -598,7 +600,9 @@ exports.ContentObject = async function({objectId, versionHash, writeToken, noCac
598
600
  if(noCache || !this.objectInfo[id] || Date.now() - this.objectInfo[id].retrievedAt > 30000) {
599
601
  let path = UrlJoin("q", id);
600
602
  const info = await this.HttpClient.RequestJsonBody({
601
- headers: await this.authClient.AuthorizationHeader({objectId, versionHash}),
603
+ headers: {
604
+ Authorization: `Bearer ${authorizationToken || await this.authClient.AuthorizationToken({objectId, versionHash})}`,
605
+ },
602
606
  method: "GET",
603
607
  path: path,
604
608
  queryParams: {
@@ -626,7 +630,7 @@ exports.ContentObject = async function({objectId, versionHash, writeToken, noCac
626
630
  *
627
631
  * @returns {Promise<string>} - The account address of the owner
628
632
  */
629
- exports.ContentObjectOwner = async function({objectId, versionHash}) {
633
+ exports.ContentObjectOwner = async function({objectId, versionHash, authorizationToken}) {
630
634
  versionHash ? ValidateVersion(versionHash) : ValidateObject(objectId);
631
635
 
632
636
  if(versionHash) {
@@ -635,7 +639,7 @@ exports.ContentObjectOwner = async function({objectId, versionHash}) {
635
639
 
636
640
  this.Log(`Retrieving content object owner: ${objectId}`);
637
641
 
638
- return this.utils.HashToAddress((await this.ContentObject({objectId, versionHash})).content_profile.owner);
642
+ return this.utils.HashToAddress((await this.ContentObject({objectId, versionHash, authorizationToken})).content_profile.owner);
639
643
  };
640
644
 
641
645
  /**
@@ -649,7 +653,7 @@ exports.ContentObjectOwner = async function({objectId, versionHash}) {
649
653
  *
650
654
  * @returns {Promise<string>} - Tenant ID of the object
651
655
  */
652
- exports.ContentObjectTenantId = async function({objectId, versionHash}) {
656
+ exports.ContentObjectTenantId = async function({objectId, versionHash, authorizationToken}) {
653
657
  versionHash ? ValidateVersion(versionHash) : ValidateObject(objectId);
654
658
 
655
659
  if(versionHash) {
@@ -659,7 +663,7 @@ exports.ContentObjectTenantId = async function({objectId, versionHash}) {
659
663
  // Cache results because they will never change
660
664
  if(!this.objectTenantIds[objectId]) {
661
665
  try {
662
- this.objectTenantIds[objectId] = (await this.ContentObject({objectId, versionHash})).content_profile.tenant_id;
666
+ this.objectTenantIds[objectId] = (await this.ContentObject({objectId, versionHash, authorizationToken})).content_profile.tenant_id;
663
667
  } catch(error) {
664
668
  error.message = `Unable to determine tenant ID for ${versionHash || objectId}`;
665
669
  throw error;
@@ -680,7 +684,7 @@ exports.ContentObjectTenantId = async function({objectId, versionHash}) {
680
684
  *
681
685
  * @returns {Promise<string>} - Library ID of the object
682
686
  */
683
- exports.ContentObjectLibraryId = async function({objectId, versionHash}) {
687
+ exports.ContentObjectLibraryId = async function({objectId, versionHash, authorizationToken}) {
684
688
  versionHash ? ValidateVersion(versionHash) : ValidateObject(objectId);
685
689
 
686
690
  if(versionHash) {
@@ -690,7 +694,7 @@ exports.ContentObjectLibraryId = async function({objectId, versionHash}) {
690
694
  // Cache results because they will never change
691
695
  if(!this.objectLibraryIds[objectId]) {
692
696
  try {
693
- this.objectLibraryIds[objectId] = (await this.ContentObject({objectId, versionHash})).qlib_id;
697
+ this.objectLibraryIds[objectId] = (await this.ContentObject({objectId, versionHash, authorizationToken})).qlib_id;
694
698
  } catch(error) {
695
699
  error.message = `Unable to determine library ID for ${versionHash || objectId}`;
696
700
  throw error;
@@ -709,56 +713,43 @@ exports.ProduceMetadataLinks = async function({
709
713
  authorizationToken,
710
714
  noAuth
711
715
  }) {
712
- // Primitive
713
- if(!metadata || typeof metadata !== "object") { return metadata; }
716
+ path = UrlJoin(path || "").replace(/^\//, "").replace(/\/$/, "");
714
717
 
715
- // Array
716
- if(Array.isArray(metadata)) {
717
- return await this.utils.LimitedMap(
718
- 5,
719
- metadata,
720
- async (entry, i) => await this.ProduceMetadataLinks({
721
- libraryId,
722
- objectId,
723
- versionHash,
724
- path: UrlJoin(path, i.toString()),
725
- metadata: entry,
726
- authorizationToken,
727
- noAuth
728
- })
729
- );
730
- }
718
+ const Traverse = async (currentPath="") => {
719
+ const currentMetadata = !currentPath ? metadata : LodashGet(metadata, currentPath.split("/"));
731
720
 
732
- // Object
733
- if(metadata["/"] &&
734
- (metadata["/"].match(/\.\/(rep|files)\/.+/) ||
735
- metadata["/"].match(/^\/?qfab\/([\w]+)\/?(rep|files)\/.+/)))
736
- {
737
- // Is file or rep link - produce a url
738
- return {
739
- ...metadata,
740
- url: await this.LinkUrl({libraryId, objectId, versionHash, linkPath: path, authorizationToken, noAuth})
741
- };
742
- }
721
+ // Primitive
722
+ if(!currentMetadata || typeof currentMetadata !== "object") {
723
+ return;
724
+ }
743
725
 
744
- let result = {};
745
- await this.utils.LimitedMap(
746
- 5,
747
- Object.keys(metadata),
748
- async key => {
749
- result[key] = await this.ProduceMetadataLinks({
750
- libraryId,
751
- objectId,
752
- versionHash,
753
- path: UrlJoin(path, key),
754
- metadata: metadata[key],
755
- authorizationToken,
756
- noAuth
757
- });
726
+ // Array
727
+ if(Array.isArray(currentMetadata)) {
728
+ for(let i = 0; i < currentMetadata.length; i++) {
729
+ await Traverse(UrlJoin(currentPath, i.toString()));
730
+ }
758
731
  }
759
- );
760
732
 
761
- return result;
733
+ // Object
734
+ if(currentMetadata["/"] &&
735
+ (currentMetadata["/"].match(/\.\/(rep|files)\/.+/) ||
736
+ currentMetadata["/"].match(/^\/?qfab\/([\w]+)\/?(rep|files)\/.+/))) {
737
+ // Is file or rep link - produce a url
738
+ LodashSet(
739
+ metadata,
740
+ UrlJoin(currentPath, "url").split("/"),
741
+ await this.LinkUrl({libraryId, objectId, versionHash, linkPath: UrlJoin(path, currentPath), authorizationToken, noAuth})
742
+ )
743
+ } else {
744
+ for(let key of Object.keys(currentMetadata)) {
745
+ await Traverse(UrlJoin(currentPath, key))
746
+ }
747
+ }
748
+ }
749
+
750
+ Traverse();
751
+
752
+ return metadata;
762
753
  };
763
754
 
764
755
  exports.MetadataAuth = async function({
@@ -1056,7 +1047,7 @@ exports.ContentObjectVersions = async function({libraryId, objectId}) {
1056
1047
  return this.HttpClient.RequestJsonBody({
1057
1048
  headers: await this.authClient.AuthorizationHeader({libraryId, objectId}),
1058
1049
  method: "GET",
1059
- path: path
1050
+ path
1060
1051
  });
1061
1052
  };
1062
1053
 
@@ -1070,13 +1061,13 @@ exports.ContentObjectVersions = async function({libraryId, objectId}) {
1070
1061
  *
1071
1062
  * @returns {Promise<string>} - The latest version hash of the object
1072
1063
  */
1073
- exports.LatestVersionHash = async function({objectId, versionHash}) {
1064
+ exports.LatestVersionHash = async function({objectId, versionHash, authorizationToken}) {
1074
1065
  if(versionHash) {
1075
1066
  objectId = this.utils.DecodeVersionHash(versionHash).objectId;
1076
1067
  }
1077
1068
 
1078
1069
  try {
1079
- return (await this.ContentObject({objectId, noCache: true})).hash;
1070
+ return (await this.ContentObject({objectId, noCache: true, authorizationToken})).hash;
1080
1071
  } catch(error) {
1081
1072
  error.message = `Unable to determine latest version hash for ${versionHash || objectId}`;
1082
1073
  throw error;
@@ -1175,11 +1166,11 @@ exports.PlayoutPathResolution = async function({
1175
1166
  authorizationToken
1176
1167
  }) {
1177
1168
  if(!libraryId) {
1178
- libraryId = await this.ContentObjectLibraryId({objectId});
1169
+ libraryId = await this.ContentObjectLibraryId({objectId, authorizationToken});
1179
1170
  }
1180
1171
 
1181
1172
  if(!versionHash) {
1182
- versionHash = await this.LatestVersionHash({objectId});
1173
+ versionHash = await this.LatestVersionHash({objectId, authorizationToken});
1183
1174
  }
1184
1175
 
1185
1176
  let path = UrlJoin("qlibs", libraryId, "q", writeToken || versionHash, "rep", handler, offering, "options.json");
@@ -1215,7 +1206,7 @@ exports.PlayoutPathResolution = async function({
1215
1206
  authorizationToken
1216
1207
  });
1217
1208
  linkTargetId = this.utils.DecodeVersionHash(linkTargetHash).objectId;
1218
- linkTargetLibraryId = await this.ContentObjectLibraryId({objectId: linkTargetId});
1209
+ linkTargetLibraryId = await this.ContentObjectLibraryId({objectId: linkTargetId, authorizationToken});
1219
1210
 
1220
1211
  if(!multiOfferingLink && !offering) {
1221
1212
  // If the offering is not specified, the intent is to get available offerings. For a single offering link, must
@@ -1272,7 +1263,7 @@ exports.AvailableOfferings = async function({
1272
1263
 
1273
1264
  if(directLink) {
1274
1265
  return await this.ContentObjectMetadata({
1275
- libraryId: await this.ContentObjectLibraryId({objectId}),
1266
+ libraryId: await this.ContentObjectLibraryId({objectId, authorizationToken}),
1276
1267
  objectId,
1277
1268
  versionHash,
1278
1269
  metadataSubtree: linkPath,
@@ -1383,10 +1374,10 @@ exports.PlayoutOptions = async function({
1383
1374
  if(!objectId) {
1384
1375
  objectId = this.utils.DecodeVersionHash(versionHash).objectId;
1385
1376
  } else if(!versionHash) {
1386
- versionHash = await this.LatestVersionHash({objectId});
1377
+ versionHash = await this.LatestVersionHash({objectId, authorizationToken});
1387
1378
  }
1388
1379
 
1389
- const libraryId = await this.ContentObjectLibraryId({objectId});
1380
+ const libraryId = await this.ContentObjectLibraryId({objectId, authorizationToken});
1390
1381
 
1391
1382
  try {
1392
1383
  // If public/asset_metadata/sources/<offering> exists, use that instead of directly calling on object
@@ -540,6 +540,7 @@ class LiveConf {
540
540
  conf.live_recording.recording_config.recording_params.source_timescale = sourceTimescale;
541
541
  break;
542
542
  case "rtmp":
543
+ case "flv":
543
544
  sourceTimescale = 16000;
544
545
  conf.live_recording.recording_config.recording_params.source_timescale = sourceTimescale;
545
546
  break;
@@ -2265,14 +2265,6 @@ exports.StreamApplyProfile = async function({
2265
2265
  }));
2266
2266
  }
2267
2267
 
2268
- // Load the base config profile and merge with overrides
2269
- const overrides = await this.ContentObjectMetadata({
2270
- libraryId,
2271
- objectId,
2272
- writeToken: streamWriteToken,
2273
- metadataSubtree: "live_recording_overrides"
2274
- }) || {};
2275
-
2276
2268
  const currentConfig = await this.ContentObjectMetadata({
2277
2269
  libraryId,
2278
2270
  objectId,
@@ -2280,8 +2272,9 @@ exports.StreamApplyProfile = async function({
2280
2272
  metadataSubtree: "live_recording_config"
2281
2273
  }) || {};
2282
2274
 
2283
- // Preserve stream-specific fields (e.g. url) from the current config, then apply profile, then overrides
2284
- const config = R.mergeDeepRight(R.mergeDeepRight(currentConfig, profile), overrides);
2275
+ // Only preserve stream-identity fields from the current config; all technical settings come from the new profile
2276
+ const preservedConfig = R.pick(["url", "name"], currentConfig);
2277
+ const config = R.mergeDeepRight(preservedConfig, profile);
2285
2278
 
2286
2279
  const currentProfileName = await this.ContentObjectMetadata({
2287
2280
  libraryId,
@@ -2299,6 +2292,34 @@ exports.StreamApplyProfile = async function({
2299
2292
  liveRecordingConfig: config
2300
2293
  });
2301
2294
 
2295
+ // Clear live_recording and live_recording_overrides so stale settings from the previous profile don't persist
2296
+ await this.ReplaceMetadata({
2297
+ libraryId,
2298
+ objectId,
2299
+ writeToken: streamWriteToken,
2300
+ metadataSubtree: "live_recording",
2301
+ metadata: {}
2302
+ });
2303
+
2304
+ await this.ReplaceMetadata({
2305
+ libraryId,
2306
+ objectId,
2307
+ writeToken: streamWriteToken,
2308
+ metadataSubtree: "live_recording_overrides",
2309
+ metadata: {}
2310
+ });
2311
+
2312
+ // If input_stream_info is available, regenerate live_recording from the new profile now
2313
+ if(config.input_stream_info) {
2314
+ await this.StreamConfig({
2315
+ name: objectId,
2316
+ liveRecordingConfig: config,
2317
+ inputStreamInfo: config.input_stream_info,
2318
+ writeToken: streamWriteToken,
2319
+ finalize: false
2320
+ });
2321
+ }
2322
+
2302
2323
  if(!profileSlug) {
2303
2324
  profileSlug = slugify(profile.name);
2304
2325
  }
@@ -2578,13 +2599,6 @@ exports.StreamConfig = async function({
2578
2599
  objectId: objectId,
2579
2600
  }
2580
2601
 
2581
- const liveRecordingMeta = await this.ContentObjectMetadata({
2582
- libraryId: libraryId,
2583
- objectId,
2584
- writeToken,
2585
- metadataSubtree: "/live_recording"
2586
- });
2587
-
2588
2602
  let liveRecordingConfigProfile;
2589
2603
  if(liveRecordingConfig && Object.keys(liveRecordingConfig || {}).length > 0) {
2590
2604
  // Extract values that may have been saved during Create but aren't being repeated in the Config step
@@ -2612,6 +2626,15 @@ exports.StreamConfig = async function({
2612
2626
 
2613
2627
  status.userConfig = liveRecordingConfigProfile;
2614
2628
 
2629
+ // If the stored probe is from a different protocol than the current URL, discard it and re-probe
2630
+ if(probe && !inputStreamInfo) {
2631
+ const urlProtocol = liveRecordingConfigProfile.url?.split(":")?.[0];
2632
+ const probeProtocol = (probe.format?.filename || "").split(":")?.[0];
2633
+ if(urlProtocol && probeProtocol && urlProtocol !== probeProtocol) {
2634
+ probe = null;
2635
+ }
2636
+ }
2637
+
2615
2638
  const streamData = {
2616
2639
  client: this
2617
2640
  };
@@ -2648,7 +2671,6 @@ exports.StreamConfig = async function({
2648
2671
  const liveConf = new LiveConf({
2649
2672
  url: liveRecordingConfigProfile.url,
2650
2673
  probeData: probe,
2651
- liveRecordingMeta,
2652
2674
  nodeId,
2653
2675
  nodeUrl: endpoint,
2654
2676
  includeAVSegDurations: false,
@@ -3392,9 +3414,8 @@ exports.OutputsList = async function({libraryId, objectId, includeState=true}) {
3392
3414
  value.input.status = streamStatus?.state;
3393
3415
  }
3394
3416
 
3395
- value = await this.OutputsResolveSrtPullUrls({value});
3396
-
3397
3417
  if(includeState) {
3418
+ await this.OutputsResolveSrtPullUrls({value});
3398
3419
  try {
3399
3420
  const nodeId = value.srt_pull?.node_ids?.[0];
3400
3421
  const result = await this.OutputsState({outputId: key, objectId, libraryId, nodeId, includeState: true});
@@ -3464,7 +3485,7 @@ exports.OutputsListItem = async function({libraryId, objectId, outputId, include
3464
3485
  value.input.status = streamStatus?.state;
3465
3486
  }
3466
3487
 
3467
- value = await this.OutputsResolveSrtPullUrls({value});
3488
+ await this.OutputsResolveSrtPullUrls({value});
3468
3489
 
3469
3490
  if(includeState) {
3470
3491
  try {
@@ -3567,7 +3588,7 @@ const RouteToOutputNode = async ({client, libraryId, objectId, outputId, nodeId}
3567
3588
  const fabricUrl = nodes?.[0]?.services?.fabric_api?.urls?.[0];
3568
3589
  if(fabricUrl) {
3569
3590
  client.SetNodes({fabricURIs: [fabricUrl]});
3570
- if(config) { config = await client.OutputsResolveSrtPullUrls({value: config}); }
3591
+ if(config) { await client.OutputsResolveSrtPullUrls({value: config}); }
3571
3592
  }
3572
3593
  }
3573
3594
 
package/src/client/NTP.js CHANGED
@@ -253,6 +253,51 @@ exports.DeleteNTPInstance = async function({tenantId, ntpId}) {
253
253
  });
254
254
  };
255
255
 
256
+ /**
257
+ * Generate a report for the specified NTP instance.
258
+ *
259
+ * @methodGroup NTP Instances
260
+ * @namedParams
261
+ * @param {string} tenantId - The ID of the tenant in which this NTP instance was created
262
+ * @param {string} ntpId - The ID of the NTP instance
263
+ * @param {string} password - The code associated with the NTP instance
264
+ * @param {string=} email - Email address associated with the code
265
+ */
266
+ exports.ReportNTPInstance = async function({tenantId, ntpId, password, email}) {
267
+ ValidatePresence("tenantId", tenantId);
268
+ ValidatePresence("ntpId", ntpId);
269
+
270
+ let paramsJSON = [];
271
+
272
+ if (password) {
273
+ paramsJSON.push(`pwd:${password}`);
274
+ }
275
+
276
+ if (email) {
277
+ paramsJSON.push(`eml:${email}`);
278
+ }
279
+
280
+ const res = await this.authClient.MakeKMSCall({
281
+ tenantId,
282
+ methodName: "elv_updateOTPInstance",
283
+ params: [
284
+ tenantId,
285
+ ntpId,
286
+ "report",
287
+ JSON.stringify(paramsJSON),
288
+ Date.now()
289
+ ],
290
+ paramTypes: [
291
+ "string",
292
+ "string",
293
+ "string",
294
+ "string",
295
+ "int"
296
+ ]
297
+ });
298
+ return res || {};
299
+ };
300
+
256
301
  /**
257
302
  * Retrieve info for NTP instances in the specified tenant
258
303
  *
@@ -389,6 +434,32 @@ exports.IssueSignedNTPCode = async function({tenantId, ntpId, email, maxRedempti
389
434
  return result;
390
435
  };
391
436
 
437
+ /**
438
+ * Check the status of the specified ticket/code without redeeming it.
439
+ *
440
+ * @methodGroup Tickets
441
+ * @namedParams
442
+ * @param {string} tenantId - The ID of the tenant from which the ticket was issued
443
+ * @param {string} ntpId - The ID of the NTP instance from which the ticket was issued
444
+ * @param {string} code - Access code
445
+ * @param {string=} email - Email address associated with the code
446
+ *
447
+ * @return {Promise<Object>} - Status information for the ticket
448
+ */
449
+ exports.NTPStatus = async function({tenantId, ntpId, code, email}) {
450
+ ValidatePresence("tenantId", tenantId);
451
+ ValidatePresence("ntpId", ntpId);
452
+ ValidatePresence("code", code);
453
+
454
+ const response = await this.HttpClient.Request({
455
+ method: "POST",
456
+ path: UrlJoin("ks", "otp", "ntp", tenantId, ntpId, "status"),
457
+ body: {"_PASSWORD": code, "_EMAIL": email}
458
+ });
459
+
460
+ return await response.json();
461
+ };
462
+
392
463
  /**
393
464
  * Redeem the specified ticket/code to authorize the client. Must provide either issuer or tenantId and ntpId
394
465
  *