@eluvio/elv-client-js 4.0.81 → 4.0.83

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.0.81",
3
+ "version": "4.0.83",
4
4
  "description": "Javascript client for the Eluvio Content Fabric",
5
5
  "main": "src/index.js",
6
6
  "author": "Kevin Talmadge",
@@ -475,11 +475,14 @@ class FrameClient {
475
475
  "SetPermission",
476
476
  "SpaceNodes",
477
477
  "StartABRMezzanineJobs",
478
+ "StreamAddWatermark",
478
479
  "StreamConfig",
480
+ "StreamCopyToVod",
479
481
  "StreamCreate",
480
482
  "StreamInitialize",
481
483
  "StreamInsertion",
482
484
  "StreamListUrls",
485
+ "StreamRemoveWatermark",
483
486
  "StreamStatus",
484
487
  "StreamStartOrStopOrReset",
485
488
  "StreamStopSession",
@@ -20,6 +20,15 @@ const AbrProfileLiveVod = {
20
20
  }
21
21
  ]
22
22
  },
23
+ "{\"media_type\":\"audio\",\"channels\":6}": {
24
+ "rung_specs": [
25
+ {
26
+ "bit_rate": 384000,
27
+ "media_type": "audio",
28
+ "pregenerate": true
29
+ }
30
+ ]
31
+ },
23
32
  "{\"media_type\":\"video\",\"aspect_ratio_height\":273,\"aspect_ratio_width\":640}": {
24
33
  "rung_specs": [
25
34
  {
@@ -1075,7 +1075,7 @@ exports.ContentObjectVersions = async function({libraryId, objectId}) {
1075
1075
  };
1076
1076
 
1077
1077
  /**
1078
- * Retrieve the version hash of the latest version of the specified object
1078
+ * Retrieve the version hash of the latest version of the specified object from chain
1079
1079
  *
1080
1080
  * @methodGroup Content Objects
1081
1081
  * @namedParams
@@ -1122,6 +1122,42 @@ exports.LatestVersionHash = async function({objectId, versionHash}) {
1122
1122
  return latestHash;
1123
1123
  };
1124
1124
 
1125
+ /**
1126
+ * Retrieve the version hash of the latest version of the specified object via fabric API.
1127
+ * Requires authorization.
1128
+ *
1129
+ * @methodGroup Content Objects
1130
+ * @namedParams
1131
+ * @param {string=} objectId - ID of the object
1132
+ * @param {string=} versionHash - Version hash of the object
1133
+ *
1134
+ * @returns {Promise<string>} - The latest version hash of the object
1135
+ */
1136
+ exports.LatestVersionHashV2 = async function({objectId, versionHash}) {
1137
+ if(versionHash) { objectId = this.utils.DecodeVersionHash(versionHash).objectId; }
1138
+
1139
+ ValidateObject(objectId);
1140
+
1141
+ let latestHash;
1142
+ try {
1143
+ let path = UrlJoin("q", objectId);
1144
+
1145
+ let q = await this.utils.ResponseToJson(
1146
+ this.HttpClient.Request({
1147
+ headers: await this.authClient.AuthorizationHeader({objectId}),
1148
+ method: "GET",
1149
+ path: path
1150
+ })
1151
+ );
1152
+ latestHash = q.hash;
1153
+
1154
+ } catch(error) {
1155
+ console.log("ERROR", error);
1156
+ throw Error(`Unable to determine latest version hash for ${versionHash || objectId}`);
1157
+ }
1158
+ return latestHash;
1159
+ };
1160
+
1125
1161
  /* URL Methods */
1126
1162
 
1127
1163
  /**
@@ -1044,11 +1044,26 @@ exports.PublishContentVersion = async function({objectId, versionHash, awaitComm
1044
1044
 
1045
1045
  if(confirmEvent) {
1046
1046
  // Found confirmation
1047
- this.Log(`Commit confirmed: ${objectHash}`);
1047
+ this.Log(`Commit confirmed on chain: ${objectHash}`);
1048
1048
  break;
1049
1049
  }
1050
1050
  }
1051
1051
  }
1052
+
1053
+ // APIv2 ensure the fabric API returns the correct hash
1054
+ if(awaitCommitConfirmation) {
1055
+ const pollingInterval = 500; // ms
1056
+ let tries = 20;
1057
+ while (tries > 0) {
1058
+ const h = await this.LatestVersionHashV2({objectId});
1059
+ if (h == versionHash) {
1060
+ this.Log(`Commit confirmed on fabric node: ${versionHash}`);
1061
+ break;
1062
+ }
1063
+ await new Promise(resolve => setTimeout(resolve, pollingInterval));
1064
+ }
1065
+ }
1066
+
1052
1067
  };
1053
1068
 
1054
1069
  /**
@@ -9,6 +9,7 @@ const path = require("path");
9
9
  const fs = require("fs");
10
10
  const HttpClient = require("../HttpClient");
11
11
  const Fraction = require("fraction.js");
12
+ const {ValidateObject, ValidatePresence} = require("../Validation");
12
13
 
13
14
  const MakeTxLessToken = async({client, libraryId, objectId, versionHash}) => {
14
15
  const tok = await client.authClient.AuthorizationToken({libraryId, objectId,
@@ -22,6 +23,41 @@ const Sleep = (ms) => {
22
23
  return new Promise(resolve => setTimeout(resolve, ms));
23
24
  };
24
25
 
26
+ const CueInfo = async ({eventId, status}) => {
27
+ let cues;
28
+ try {
29
+ const lroStatusResponse = await this.utils.ResponseToJson(
30
+ await HttpClient.Fetch(status.lro_status_url)
31
+ );
32
+ console.log("lroStatusResponse", lroStatusResponse)
33
+ cues = lroStatusResponse.custom.cues;
34
+ } catch (error) {
35
+ console.log("LRO status failed", error);
36
+ return {error: "failed to retrieve status", eventId};
37
+ }
38
+
39
+ let eventStart, eventEnd;
40
+ for (const value of Object.values(cues)) {
41
+ for (const event of Object.values(value.descriptors)) {
42
+ if (event.id == eventId) {
43
+ switch (event.type_id) {
44
+ case 32:
45
+ case 16:
46
+ eventStart = value.insertion_time;
47
+ break;
48
+ case 33:
49
+ case 17:
50
+ eventEnd = value.insertion_time;
51
+ break;
52
+
53
+ }
54
+ }
55
+ }
56
+ }
57
+
58
+ return {eventStart, eventEnd, eventId};
59
+ }
60
+
25
61
  /**
26
62
  * Set the offering for the live stream
27
63
  *
@@ -453,6 +489,8 @@ exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
453
489
  await HttpClient.Fetch(status.lro_status_url)
454
490
  );
455
491
  state = lroStatus.state;
492
+ status.warnings = lroStatus.custom && lroStatus.custom.warnings;
493
+ status.quality = lroStatus.custom && lroStatus.custom.quality;
456
494
  } catch(error) {
457
495
  console.log("LRO Status (failed): ", error.response.statusCode);
458
496
  status.state = "stopped";
@@ -827,7 +865,7 @@ exports.StreamStopSession = async function({name}) {
827
865
  writeToken: metaEdgeWriteToken
828
866
  });
829
867
  } catch(error) {
830
- this.Log(`Unable to retrieve metadata for edge write token ${edgeWriteToken}`);
868
+ this.Log("Unable to retrieve metadata for edge write token");
831
869
  }
832
870
 
833
871
  const {writeToken} = await this.EditContentObject({
@@ -891,21 +929,28 @@ exports.StreamStopSession = async function({name}) {
891
929
  * @return {Promise<Object>} - The name, object ID, and state of the stream
892
930
  */
893
931
  exports.StreamInitialize = async function({name, drm=false, format}) {
894
- const contentTypes = await this.ContentTypes();
895
-
896
932
  let typeAbrMaster;
897
933
  let typeLiveStream;
898
934
 
899
- for(let i = 0; i < Object.keys(contentTypes).length; i++) {
900
- const key = Object.keys(contentTypes)[i];
935
+ // Fetch Title and Live Stream content types from tenant meta
936
+ const tenantContractId = await client.userProfileClient.TenantContractId();
937
+ let liveStreamContentType, titleContentType;
938
+ const {live_stream, title} = await client.ContentObjectMetadata({
939
+ libraryId: tenantContractId.replace("iten", "ilib"),
940
+ objectId: tenantContractId.replace("iten", "iq__"),
941
+ metadataSubtree: "public/content_types",
942
+ select: [
943
+ "live_stream",
944
+ "title"
945
+ ]
946
+ });
901
947
 
902
- if(contentTypes[key].name.includes("ABR Master") || contentTypes[key].name.includes("Title")) {
903
- typeAbrMaster = contentTypes[key].hash;
904
- }
948
+ if(live_stream) {
949
+ typeLiveStream = live_stream;
950
+ }
905
951
 
906
- if(contentTypes[key].name.includes("Live Stream")) {
907
- typeLiveStream = contentTypes[key].hash;
908
- }
952
+ if(title) {
953
+ typeAbrMaster = title;
909
954
  }
910
955
 
911
956
  if(typeAbrMaster === undefined || typeLiveStream === undefined) {
@@ -961,9 +1006,10 @@ exports.StreamSetOfferingAndDRM = async function({name, typeAbrMaster, typeLiveS
961
1006
  const vFrameRate = "30000/1001";
962
1007
  const vTimeBase = "1/30000"; // "1/16000";
963
1008
 
964
- const abrProfile = require("../abr_profiles/abr_profile_live_drm.js");
1009
+ const abrProfileDefault = require("../abr_profiles/abr_profile_live_drm.js");
965
1010
 
966
- let playoutFormats = abrProfile.playout_formats;
1011
+ let playoutFormats;
1012
+ let abrProfile = JSON.parse(JSON.stringify(abrProfileDefault));
967
1013
  if(format) {
968
1014
  drm = true; // Override DRM parameter
969
1015
  playoutFormats = {};
@@ -991,6 +1037,8 @@ exports.StreamSetOfferingAndDRM = async function({name, typeAbrMaster, typeLiveS
991
1037
  }
992
1038
  }
993
1039
  };
1040
+ } else {
1041
+ playoutFormats = Object.assign({}, abrProfile.playout_formats);
994
1042
  }
995
1043
 
996
1044
  abrProfile.playout_formats = playoutFormats;
@@ -1416,7 +1464,7 @@ exports.StreamConfig = async function({name, customSettings={}}) {
1416
1464
  *
1417
1465
  * @methodGroup Live Stream
1418
1466
  * @namedParams
1419
- * @param {string=} - ID of the live stream site object
1467
+ * @param {string=} siteId - ID of the live stream site object
1420
1468
  *
1421
1469
  * @return {Promise<Object>} - The list of stream URLs
1422
1470
  */
@@ -1532,3 +1580,382 @@ exports.StreamListUrls = async function({siteId}={}) {
1532
1580
  console.error(error);
1533
1581
  }
1534
1582
  };
1583
+
1584
+ /**
1585
+ * Copy a portion of a live stream recording into a standard VoD object using the zero-copy content fabric API
1586
+ *
1587
+ * Limitations:
1588
+ * - currently requires the target object to be pre-created and have content encryption keys (CAPS)
1589
+ * - for audio and video to be sync'd, the live stream needs to have the beginning of the desired recording period
1590
+ * - for an event stream, make sure the TTL is long enough to allow running the live-to-vod command before the beginning of the recording expires
1591
+ * - for 24/7 streams, make sure to reset the stream before the desired recording (as to create a new recording period) and have the TTL long enough
1592
+ * to allow running the live-to-vod command before the beginning of the recording expires.
1593
+ * - startTime and endTime are not currently implemented by this method
1594
+ *
1595
+ *
1596
+ * @methodGroup Live Stream
1597
+ * @namedParams
1598
+ * @param {string} name - Object ID or name of the live stream
1599
+ * @param {string} targetObjectId - Object ID of the target VOD object
1600
+ * @param {string=} eventId -
1601
+ * @param {boolean=} finalize - If enabled, target object will be finalized after copy to vod operations
1602
+ * @param {number=} recordingPeriod - Determines which recording period to copy, which are 0-based. -1 copies the current (or last) period
1603
+ *
1604
+ * @return {Promise<Object>} - The status response for the stream
1605
+ */
1606
+
1607
+ /*
1608
+ Example fabric API flow:
1609
+
1610
+ https://host-76-74-34-194.contentfabric.io/qlibs/ilib24CtWSJeVt9DiAzym8jB6THE9e7H/q/$QWT/call/media/live_to_vod/init -d @r1 -H "Authorization: Bearer $TOK"
1611
+
1612
+ {
1613
+ "live_qhash": "hq__5Zk1jSN8vNLUAXjQwMJV8F8J8ESXNvmVKkhaXySmGc1BXnJPG2FvvaXee4CXqvFHuGuU3fqLJc",
1614
+ "start_time": "",
1615
+ "end_time": "",
1616
+ "recording_period": -1,
1617
+ "streams": ["video", "audio"],
1618
+ "variant_key": "default"
1619
+ }
1620
+
1621
+ https://host-76-74-34-194.contentfabric.io/qlibs/ilib24CtWSJeVt9DiAzym8jB6THE9e7H/q/$QWT/call/media/abr_mezzanine/init -H "Authorization: Bearer $TOK" -d @r2
1622
+
1623
+ {
1624
+
1625
+ "abr_profile": { ... },
1626
+ "offering_key": "default",
1627
+ "prod_master_hash": "tqw__HSQHBt7vYxWfCMPH5yXwKTfhdPcQ4Lcs9WUMUbTtnMbTZPTLo4BfJWPMGpoy1Dpv1wWQVtUtAtAr429TnVs",
1628
+ "variant_key": "default",
1629
+ "keep_other_streams": false
1630
+ }
1631
+
1632
+ https://host-76-74-34-194.contentfabric.io/qlibs/ilib24CtWSJeVt9DiAzym8jB6THE9e7H/q/$QWT/call/media/live_to_vod/copy -d '{"variant_key":"","offering_key":""}' -H "Authorization: Bearer $TOK"
1633
+
1634
+
1635
+ https://host-76-74-34-194.contentfabric.io/qlibs/ilib24CtWSJeVt9DiAzym8jB6THE9e7H/q/$QWT/call/media/abr_mezzanine/offerings/default/finalize -d '{}' -H "Authorization: Bearer $TOK"
1636
+
1637
+ */
1638
+
1639
+ exports.StreamCopyToVod = async function({
1640
+ name,
1641
+ targetObjectId,
1642
+ eventId,
1643
+ streams=null,
1644
+ finalize=true,
1645
+ recordingPeriod=-1,
1646
+ startTime="",
1647
+ endTime=""
1648
+ }) {
1649
+ const conf = await this.LoadConf({name});
1650
+ const abrProfile = require("../abr_profiles/abr_profile_live_to_vod.js");
1651
+
1652
+ const status = await this.StreamStatus({name});
1653
+ const libraryId = status.library_id;
1654
+
1655
+ this.Log(`Copying stream ${name} to target ${targetObjectId}`);
1656
+
1657
+ ValidateObject(targetObjectId);
1658
+
1659
+ const targetLibraryId = await this.ContentObjectLibraryId({objectId: targetObjectId});
1660
+
1661
+ // Validation - ensure target object has content encryption keys
1662
+ const kmsAddress = await this.authClient.KMSAddress({objectId: targetObjectId});
1663
+ const kmsCapId = `eluv.caps.ikms${this.utils.AddressToHash(kmsAddress)}`;
1664
+ const kmsCap = await this.ContentObjectMetadata({
1665
+ libraryId: targetLibraryId,
1666
+ objectId: targetObjectId,
1667
+ metadataSubtree: kmsCapId
1668
+ });
1669
+
1670
+ if(!kmsCap) {
1671
+ throw Error(`No content encryption key set for object ${targetObjectId}`);
1672
+ }
1673
+
1674
+ try {
1675
+ status.live_object_id = conf.objectId;
1676
+
1677
+ const liveHash = await this.LatestVersionHash({objectId: conf.objectId, libraryId});
1678
+ status.live_hash = liveHash;
1679
+
1680
+ if(eventId) {
1681
+ // Retrieve start and end times for the event
1682
+ let event = await this.CueInfo({eventId, status});
1683
+ if(event.eventStart && event.eventEnd) {
1684
+ startTime = event.eventStart;
1685
+ endTime = event.eventEnd;
1686
+ }
1687
+ }
1688
+
1689
+ const {writeToken} = await this.EditContentObject({
1690
+ objectId: targetObjectId,
1691
+ libraryId: targetLibraryId
1692
+ });
1693
+
1694
+ status.target_object_id = targetObjectId;
1695
+ status.target_library_id = targetLibraryId;
1696
+ status.target_write_token = writeToken;
1697
+
1698
+ this.Log("Process live source (takes around 20 sec per hour of content)");
1699
+
1700
+ await this.CallBitcodeMethod({
1701
+ libraryId: targetLibraryId,
1702
+ objectId: targetObjectId,
1703
+ writeToken,
1704
+ method: "/media/live_to_vod/init",
1705
+ body: {
1706
+ "live_qhash": liveHash,
1707
+ "start_time": startTime, // eg. "2023-10-03T02:09:02.00Z",
1708
+ "end_time": endTime, // eg. "2023-10-03T02:15:00.00Z",
1709
+ "streams": streams,
1710
+ "recording_period": recordingPeriod,
1711
+ "variant_key": "default"
1712
+ },
1713
+ constant: false,
1714
+ format: "text"
1715
+ });
1716
+
1717
+ const abrMezInitBody = {
1718
+ abr_profile: abrProfile,
1719
+ "offering_key": "default",
1720
+ "prod_master_hash": writeToken,
1721
+ "variant_key": "default",
1722
+ "keep_other_streams": false
1723
+ };
1724
+
1725
+ await this.CallBitcodeMethod({
1726
+ libraryId: targetLibraryId,
1727
+ objectId: targetObjectId,
1728
+ writeToken,
1729
+ method: "/media/abr_mezzanine/init",
1730
+ body: abrMezInitBody,
1731
+ constant: false,
1732
+ format: "text"
1733
+ });
1734
+
1735
+ try {
1736
+ await this.CallBitcodeMethod({
1737
+ libraryId: targetLibraryId,
1738
+ objectId: targetObjectId,
1739
+ writeToken,
1740
+ method: "/media/live_to_vod/copy",
1741
+ body: {},
1742
+ constant: false,
1743
+ format: "text"
1744
+ });
1745
+ } catch(error) {
1746
+ console.error("Unable to call /media/live_to_vod/copy", error);
1747
+ throw error;
1748
+ }
1749
+
1750
+ await this.CallBitcodeMethod({
1751
+ libraryId: targetLibraryId,
1752
+ objectId: targetObjectId,
1753
+ writeToken,
1754
+ method: "/media/abr_mezzanine/offerings/default/finalize",
1755
+ body: abrMezInitBody,
1756
+ constant: false,
1757
+ format: "text"
1758
+ });
1759
+
1760
+ if(finalize) {
1761
+ const finalizeResponse = await this.FinalizeContentObject({
1762
+ libraryId: targetLibraryId,
1763
+ objectId: targetObjectId,
1764
+ writeToken,
1765
+ commitMessage: "Live Stream to VoD"
1766
+ });
1767
+
1768
+ status.target_hash = finalizeResponse.hash;
1769
+ }
1770
+
1771
+ // Clean up unnecessary status items
1772
+ delete status.playout_urls;
1773
+ delete status.lro_status_url;
1774
+ delete status.recording_period;
1775
+ delete status.recording_period_sequence;
1776
+ delete status.edge_meta_size;
1777
+ delete status.insertions;
1778
+
1779
+ return status;
1780
+ } catch(error) {
1781
+ this.Log(error, true);
1782
+ throw error;
1783
+ }
1784
+ };
1785
+
1786
+ /**
1787
+ * Remove a watermark for a live stream
1788
+ *
1789
+ * @methodGroup Live Stream
1790
+ * @namedParams
1791
+ * @param {string} objectId - Object ID of the live stream
1792
+ * @param {Array<string>} types - Specify which type of watermark to remove. Possible values:
1793
+ * - "image"
1794
+ * - "text"
1795
+ * @param {boolean=} finalize - If enabled, target object will be finalized after removing watermark
1796
+ *
1797
+ * @return {Promise<Object>} - The finalize response
1798
+ */
1799
+ exports.StreamRemoveWatermark = async function({
1800
+ objectId,
1801
+ types,
1802
+ finalize=true
1803
+ }) {
1804
+ ValidateObject(objectId);
1805
+
1806
+ const libraryId = await this.ContentObjectLibraryId({objectId});
1807
+ const {writeToken} = await this.EditContentObject({
1808
+ objectId,
1809
+ libraryId
1810
+ });
1811
+
1812
+ this.Log(`Removing watermark types: ${types.join(", ")} ${libraryId} ${objectId}`);
1813
+
1814
+ const edgeWriteToken = await this.ContentObjectMetadata({
1815
+ objectId,
1816
+ libraryId,
1817
+ metadataSubtree: "/live_recording/fabric_config/edge_write_token"
1818
+ });
1819
+
1820
+ const recordingParamsPath = "live_recording/recording_config/recording_params";
1821
+
1822
+ const recordingMetadata = await this.ContentObjectMetadata({
1823
+ libraryId,
1824
+ objectId,
1825
+ writeToken,
1826
+ metadataSubtree: recordingParamsPath,
1827
+ resolveLinks: false
1828
+ });
1829
+
1830
+ if(!recordingMetadata) {
1831
+ throw Error("Stream object must be configured");
1832
+ }
1833
+
1834
+ types.forEach(type => {
1835
+ if(type === "text") {
1836
+ delete recordingMetadata.simple_watermark;
1837
+ } else if(type === "image") {
1838
+ delete recordingMetadata.image_watermark;
1839
+ }
1840
+ });
1841
+
1842
+ await this.ReplaceMetadata({
1843
+ libraryId,
1844
+ objectId,
1845
+ writeToken,
1846
+ metadataSubtree: recordingParamsPath,
1847
+ metadata: recordingMetadata
1848
+ });
1849
+
1850
+ if(edgeWriteToken) {
1851
+ await this.ReplaceMetadata({
1852
+ libraryId,
1853
+ objectId,
1854
+ writeToken: edgeWriteToken,
1855
+ metadataSubtree: recordingParamsPath,
1856
+ metadata: recordingMetadata
1857
+ });
1858
+ }
1859
+
1860
+ if(finalize) {
1861
+ const finalizeResponse = await this.FinalizeContentObject({
1862
+ libraryId,
1863
+ objectId,
1864
+ writeToken,
1865
+ commitMessage: "Watermark removed"
1866
+ });
1867
+
1868
+ return finalizeResponse;
1869
+ }
1870
+ };
1871
+
1872
+ /**
1873
+ * Create a watermark for a live stream
1874
+ *
1875
+ * @methodGroup Live Stream
1876
+ * @namedParams
1877
+ * @param {string} objectId - Object ID of the live stream
1878
+ * @param {Object} simpleWatermark - Text watermark
1879
+ * @param {Object} imageWatermark - Image watermark
1880
+ * @param {boolean=} finalize - If enabled, target object will be finalized after adding watermark
1881
+ *
1882
+ * @return {Promise<Object>} - The finalize response
1883
+ */
1884
+ exports.StreamAddWatermark = async function({
1885
+ objectId,
1886
+ simpleWatermark,
1887
+ imageWatermark,
1888
+ finalize=true
1889
+ }) {
1890
+ ValidateObject(objectId);
1891
+
1892
+ const libraryId = await this.ContentObjectLibraryId({objectId});
1893
+ const {writeToken} = await this.EditContentObject({
1894
+ objectId,
1895
+ libraryId
1896
+ });
1897
+
1898
+ const edgeWriteToken = await this.ContentObjectMetadata({
1899
+ objectId,
1900
+ libraryId,
1901
+ metadataSubtree: "/live_recording/fabric_config/edge_write_token"
1902
+ });
1903
+
1904
+ this.Log(`Adding watermarking type: ${imageWatermark ? "image" : "text"} ${libraryId} ${objectId}`);
1905
+
1906
+ const recordingParamsPath = "live_recording/recording_config/recording_params";
1907
+
1908
+ const recordingMetadata = await this.ContentObjectMetadata({
1909
+ libraryId,
1910
+ objectId,
1911
+ writeToken,
1912
+ metadataSubtree: recordingParamsPath,
1913
+ resolveLinks: false
1914
+ });
1915
+
1916
+ if(!recordingMetadata) {
1917
+ throw Error("Stream object must be configured");
1918
+ }
1919
+
1920
+ if(simpleWatermark) {
1921
+ recordingMetadata.simple_watermark = simpleWatermark;
1922
+ } else if(imageWatermark) {
1923
+ recordingMetadata.image_watermark = imageWatermark;
1924
+ }
1925
+
1926
+ await this.ReplaceMetadata({
1927
+ libraryId,
1928
+ objectId,
1929
+ writeToken,
1930
+ metadataSubtree: recordingParamsPath,
1931
+ metadata: recordingMetadata
1932
+ });
1933
+
1934
+ if(edgeWriteToken) {
1935
+ await this.ReplaceMetadata({
1936
+ libraryId,
1937
+ objectId,
1938
+ writeToken: edgeWriteToken,
1939
+ metadataSubtree: recordingParamsPath,
1940
+ metadata: recordingMetadata
1941
+ });
1942
+ }
1943
+
1944
+ const response = {
1945
+ "imageWatermark": recordingMetadata.image_watermark,
1946
+ "textWatermark": recordingMetadata.simple_watermark
1947
+ };
1948
+
1949
+ if(finalize) {
1950
+ const finalizeResponse = await this.FinalizeContentObject({
1951
+ libraryId,
1952
+ objectId,
1953
+ writeToken,
1954
+ commitMessage: "Watermark set"
1955
+ });
1956
+
1957
+ response.hash = finalizeResponse.hash;
1958
+ }
1959
+
1960
+ return response;
1961
+ };
@@ -0,0 +1,45 @@
1
+ const { ElvClient } = require("../src/ElvClient");
2
+
3
+ const yargs = require("yargs");
4
+ const argv = yargs
5
+ .option("msg", {
6
+ description: "message to be decoded",
7
+ type: "string"
8
+ })
9
+ .option("config-url", {
10
+ type: "string",
11
+ description: "URL pointing to the Fabric configuration. i.e. https://main.net955210.contentfabric.io/config"
12
+ })
13
+ .demandOption(
14
+ ["msg"],
15
+ "\nUsage: PRIVATE_KEY=<private-key> node DecodeSignedMessage.js --msg <message>\n"
16
+ )
17
+ .strict().argv;
18
+ const ClientConfiguration = (!argv["config-url"]) ? (require("../TestConfiguration.json")) : {"config-url": argv["config-url"]};
19
+
20
+ const DecodeSignedMessage = async ({msg}) => {
21
+ try {
22
+ const client = await ElvClient.FromConfigurationUrl({
23
+ configUrl: ClientConfiguration["config-url"],
24
+ });
25
+ const wallet = client.GenerateWallet();
26
+ const signer = wallet.AddAccount({
27
+ privateKey: process.env.PRIVATE_KEY
28
+ });
29
+
30
+ client.SetSigner({signer});
31
+
32
+ const decodedMessage = await client.DecodeSignedMessageJSON({
33
+ signedMessage: msg,
34
+ });
35
+
36
+ console.log(JSON.stringify(decodedMessage));
37
+ } catch(error) {
38
+ console.error(error);
39
+ console.error(JSON.stringify(error, null, 2));
40
+ }
41
+
42
+ process.exit(0);
43
+ };
44
+
45
+ DecodeSignedMessage({msg: argv.msg});