@eluvio/elv-client-js 3.1.93 → 3.1.96

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.
Files changed (38) hide show
  1. package/dist/ElvClient-min.js +15 -11
  2. package/dist/ElvClient-node-min.js +17 -13
  3. package/dist/ElvFrameClient-min.js +13 -9
  4. package/dist/ElvPermissionsClient-min.js +13 -9
  5. package/dist/src/AuthorizationClient.js +2248 -1990
  6. package/dist/src/ContentObjectVerification.js +164 -173
  7. package/dist/src/Crypto.js +376 -324
  8. package/dist/src/ElvClient.js +1214 -938
  9. package/dist/src/ElvWallet.js +119 -95
  10. package/dist/src/EthClient.js +1040 -896
  11. package/dist/src/FrameClient.js +331 -300
  12. package/dist/src/HttpClient.js +153 -147
  13. package/dist/src/Id.js +1 -3
  14. package/dist/src/PermissionsClient.js +1294 -1168
  15. package/dist/src/RemoteSigner.js +260 -200
  16. package/dist/src/UserProfileClient.js +1164 -1023
  17. package/dist/src/Utils.js +209 -177
  18. package/dist/src/client/ABRPublishing.js +895 -856
  19. package/dist/src/client/AccessGroups.js +1102 -959
  20. package/dist/src/client/ContentAccess.js +3727 -3425
  21. package/dist/src/client/ContentManagement.js +2252 -2068
  22. package/dist/src/client/Contracts.js +647 -563
  23. package/dist/src/client/Files.js +1886 -1757
  24. package/dist/src/client/NFT.js +126 -112
  25. package/dist/src/client/NTP.js +478 -422
  26. package/package.json +2 -1
  27. package/src/AuthorizationClient.js +1 -1
  28. package/src/ElvClient.js +62 -1
  29. package/src/RemoteSigner.js +22 -18
  30. package/src/client/ABRPublishing.js +2 -1
  31. package/src/client/ContentAccess.js +4 -0
  32. package/testScripts/Test.js +0 -19
  33. package/testScripts/VariantABRProfile.js +45 -0
  34. package/utilities/ObjectSetPermission.js +4 -4
  35. package/utilities/SimpleIngest.js +286 -0
  36. package/utilities/example_files/simple_ingest_library_metadata.json +180 -0
  37. package/utilities/example_files/simple_ingest_library_metadata_animation_NFTs.json +133 -0
  38. package/package-lock.json +0 -12650
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eluvio/elv-client-js",
3
- "version": "3.1.93",
3
+ "version": "3.1.96",
4
4
  "description": "Javascript client for the Eluvio Content Fabric",
5
5
  "main": "src/ElvClient.js",
6
6
  "author": "Kevin Talmadge",
@@ -57,6 +57,7 @@
57
57
  "dependencies": {
58
58
  "@babel/runtime": "^7.8.4",
59
59
  "@eluvio/crypto": ">=1.0.8",
60
+ "@eluvio/elv-abr-profile": "^1.0.0",
60
61
  "@sindresorhus/slugify": "^1.1.0",
61
62
  "babel-loader": "^8.0.6",
62
63
  "bignumber.js": "^8.0.2",
@@ -204,7 +204,7 @@ class AuthorizationClient {
204
204
  addr: Utils.FormatAddress(((this.client.signer && this.client.signer.address) || ""))
205
205
  };
206
206
 
207
- if(!(this.noAuth || noAuth) && !this.client.signer.remoteSigner) {
207
+ if(!(this.noAuth || noAuth)) {
208
208
  const { transactionHash } = await this.MakeAccessRequest({
209
209
  libraryId,
210
210
  objectId,
package/src/ElvClient.js CHANGED
@@ -694,6 +694,67 @@ class ElvClient {
694
694
  return this.utils.FormatAddress(this.signer.address);
695
695
  }
696
696
 
697
+ /*
698
+ TOKEN 211b PREFIX + BODY | aplsjcJf1HYcDDUuCdXcSZtU86nYK162YmYJeuqwMczEBJVkD5D5EvsBvVwYDRsf4hzDvBWMoe9piBpqx...
699
+ PREFIX 6b aplsjc | apl=plain s=ES256K jc=json-compressed
700
+ BODY 205b base58(SIGNATURE + PAYLOAD)
701
+ SIGNATURE + PAYLOAD 151b 151b * 138 / 100 + 1 = 209b (>= 205b)
702
+ SIGNATURE 66b ES256K_Di9Lu83mz4wMoehCEeQhKpJJ7ApmDZLumAa2Cge48F6EHYnbn8msATGGpjucScwimei1TWGd7aeyQY45AdXd5tT1Z
703
+ PAYLOAD 85b json-compressed
704
+ json 79b {"adr":"VVf4DQU357tDnZGYQeDrntRJ5rs=","spc":"ispc3ANoVSzNA3P6t7abLR69ho5YPPZU"}
705
+ */
706
+
707
+ /**
708
+ * Create a signed authorization token that can be used to authorize against the fabric
709
+ *
710
+ * @methodGroup Authorization
711
+ * @namedParams
712
+ * @param {number} duration=86400000 - Time until the token expires, in milliseconds (1 hour = 60 * 60 * 1000 = 3600000). Default is 24 hours.
713
+ * @param {Object=} spec - Additional attributes for this token
714
+ * @param {string=} address - Address of the signing account - if not specified, the current signer address will be used.
715
+ * @param {function=} Sign - If specified, this function will be used to produce the signature instead of the client's current signer
716
+ * @param {boolean=} addEthereumPrefix=true - If specified, the 'Ethereum Signed Message' prefixed hash format will be performed. Disable this if the provided Sign method already does this (e.g. Metamask)
717
+ */
718
+ async CreateFabricToken({
719
+ duration=24 * 60 * 60 * 1000,
720
+ spec={},
721
+ address,
722
+ Sign,
723
+ addEthereumPrefix=true,
724
+ }={}) {
725
+ address = address || this.CurrentAccountAddress();
726
+
727
+ let token = {
728
+ ...spec,
729
+ sub:`iusr${Utils.AddressToHash(address)}`,
730
+ adr: Buffer.from(address.replace(/^0x/, ""), "hex").toString("base64"),
731
+ spc: await this.ContentSpaceId(),
732
+ iat: Date.now(),
733
+ exp: Date.now() + duration,
734
+ };
735
+
736
+ if(!Sign) {
737
+ Sign = async message => this.authClient.Sign(message);
738
+ }
739
+
740
+ let message = `Eluvio Content Fabric Access Token 1.0\n${JSON.stringify(token)}`;
741
+
742
+ if(addEthereumPrefix) {
743
+ message = Ethers.utils.keccak256(Buffer.from(`\x19Ethereum Signed Message:\n${message.length}${message}`, "utf-8"));
744
+ }
745
+
746
+ const signature = await Sign(message);
747
+
748
+ const compressedToken = Pako.deflateRaw(Buffer.from(JSON.stringify(token), "utf-8"));
749
+ return `acspjc${this.utils.B58(
750
+ Buffer.concat([
751
+ Buffer.from(signature.replace(/^0x/, ""), "hex"),
752
+ Buffer.from(compressedToken)
753
+ ])
754
+ )}`;
755
+ }
756
+
757
+
697
758
  /**
698
759
  * Issue a self-signed authorization token
699
760
  *
@@ -705,7 +766,7 @@ class ElvClient {
705
766
  * @param {string=} policyId - The object ID of the policy for this token
706
767
  * @param {string=} subject - The subject of the token
707
768
  * @param {string} grantType=read - Permissions to grant for this token. Options: "access", "read", "create", "update", "read-crypt"
708
- * @param {number} duration - Time until the token expires, in milliseconds (1 hour = 60 * 60 * 1000)
769
+ * @param {number} duration - Time until the token expires, in milliseconds (1 hour = 60 * 60 * 1000 = 3600000)
709
770
  * @param {boolean} allowDecryption=false - If specified, the re-encryption key will be included in the token,
710
771
  * enabling the user of this token to download encrypted content from the specified object
711
772
  * @param {Object=} context - Additional JSON context
@@ -85,24 +85,28 @@ class RemoteSigner extends Ethers.Signer {
85
85
  */
86
86
  async signDigest(digest) {
87
87
  if(!this.signatureCache[digest]) {
88
- this.signatureCache[digest] = new Promise(async resolve => {
89
- let signature = await Utils.ResponseToJson(
90
- this.HttpClient.Request({
91
- method: "POST",
92
- path: UrlJoin("as", "wlt", "sign", "eth", this.id),
93
- headers: {
94
- Authorization: `Bearer ${this.authToken}`
95
- },
96
- body: {
97
- hash: digest
98
- }
99
- })
100
- );
101
-
102
- signature.v = parseInt(signature.v, 16);
103
- signature.recoveryParam = signature.v - 27;
104
-
105
- resolve(signature);
88
+ this.signatureCache[digest] = new Promise(async (resolve, reject) => {
89
+ try {
90
+ let signature = await Utils.ResponseToJson(
91
+ this.HttpClient.Request({
92
+ method: "POST",
93
+ path: UrlJoin("as", "wlt", "sign", "eth", this.id),
94
+ headers: {
95
+ Authorization: `Bearer ${this.authToken}`
96
+ },
97
+ body: {
98
+ hash: digest
99
+ }
100
+ })
101
+ );
102
+
103
+ signature.v = parseInt(signature.v, 16);
104
+ signature.recoveryParam = signature.v - 27;
105
+
106
+ resolve(signature);
107
+ } catch(error) {
108
+ reject(error);
109
+ }
106
110
  });
107
111
  }
108
112
 
@@ -478,7 +478,7 @@ exports.StartABRMezzanineJobs = async function({
478
478
  metadata: lroInfo
479
479
  });
480
480
 
481
- await this.FinalizeContentObject({
481
+ const finalizeResponse = await this.FinalizeContentObject({
482
482
  libraryId,
483
483
  objectId,
484
484
  writeToken: statusDraft.write_token,
@@ -500,6 +500,7 @@ exports.StartABRMezzanineJobs = async function({
500
500
  });
501
501
 
502
502
  return {
503
+ hash: finalizeResponse.hash,
503
504
  lro_draft: lroInfo,
504
505
  writeToken: processingDraft.write_token,
505
506
  data,
@@ -2332,6 +2332,10 @@ exports.LinkData = async function({libraryId, objectId, versionHash, writeToken,
2332
2332
  /* Encryption */
2333
2333
 
2334
2334
  exports.CreateEncryptionConk = async function({libraryId, objectId, versionHash, writeToken, createKMSConk=true}) {
2335
+ if(this.signer.remoteSigner) {
2336
+ return;
2337
+ }
2338
+
2335
2339
  ValidateParameters({libraryId, objectId, versionHash});
2336
2340
  ValidateWriteToken(writeToken);
2337
2341
 
@@ -13,25 +13,6 @@ const Test = async () => {
13
13
  });
14
14
 
15
15
  client.SetSigner({signer});
16
-
17
-
18
- const ethUrl = "https://host-216-66-40-19.contentfabric.io/eth";
19
- const asUrl = "https://host-66-220-3-86.contentfabric.io";
20
-
21
- client.SetNodes({
22
- ethereumURIs: [
23
- ethUrl
24
- ],
25
- authServiceURIs: [
26
- asUrl
27
- ]
28
- });
29
-
30
- console.log(
31
- await client.ContentObjectTenantId({
32
- objectId: "iq__3PXRZX5NCzPQsfTEquCctB2K3KJh"
33
- })
34
- )
35
16
  } catch(error) {
36
17
  console.error(error);
37
18
  console.error(JSON.stringify(error, null, 2));
@@ -0,0 +1,45 @@
1
+ /* eslint-disable no-console */
2
+
3
+
4
+ const ScriptVariant = require("./parentClasses/ScriptVariant");
5
+ const ABR = require("@eluvio/elv-abr-profile");
6
+
7
+ class VariantABRProfile extends ScriptVariant {
8
+
9
+ async body() {
10
+ const client = await this.client();
11
+
12
+ const libraryId = this.args.libraryId;
13
+ const objectId = this.args.objectId;
14
+
15
+ // get media info from production master
16
+ const masterMetadata = await client.ContentObjectMetadata({
17
+ libraryId,
18
+ objectId,
19
+ metadataSubtree: "/production_master"
20
+ });
21
+
22
+ // adapt ABR Profile to production master's video properties
23
+ const generatedProfile = ABR.ABRProfileForVariant(
24
+ masterMetadata.sources,
25
+ masterMetadata.variants.default
26
+ );
27
+
28
+ if(!generatedProfile.ok) {
29
+ console.error("Error generating ABR Profile:\n" + generatedProfile.errors.join("\n"));
30
+ } else {
31
+ console.log(JSON.stringify(generatedProfile.result, null, 2));
32
+ }
33
+ }
34
+
35
+ header() {
36
+ return `Generate ABR Profile for production master ${this.args.objectId}, default variant`;
37
+ }
38
+
39
+ options() {
40
+ return super.options();
41
+ }
42
+ }
43
+
44
+ const script = new VariantABRProfile;
45
+ script.run();
@@ -9,7 +9,7 @@ const Logger = require("./lib/concerns/Logger");
9
9
 
10
10
  const permissionTypes = ["owner", "editable", "viewable", "listable", "public"];
11
11
 
12
- class ObjectAddGroupPerms extends Utility {
12
+ class ObjectSetPermission extends Utility {
13
13
  blueprint() {
14
14
  return {
15
15
  concerns: [Logger, ArgObjectId, Client],
@@ -55,7 +55,7 @@ class ObjectAddGroupPerms extends Utility {
55
55
  }
56
56
 
57
57
  if(require.main === module) {
58
- Utility.cmdLineInvoke(ObjectAddGroupPerms);
58
+ Utility.cmdLineInvoke(ObjectSetPermission);
59
59
  } else {
60
- module.exports = ObjectAddGroupPerms;
61
- }
60
+ module.exports = ObjectSetPermission;
61
+ }
@@ -0,0 +1,286 @@
1
+ // Create new production master from specified file(s)
2
+ const R = require("ramda");
3
+
4
+ const {ModOpt, NewOpt} = require("./lib/options");
5
+ const Utility = require("./lib/Utility");
6
+
7
+ const ABR = require("@eluvio/elv-abr-profile");
8
+
9
+ const Client = require("./lib/concerns/Client");
10
+ const Finalize = require("./lib/concerns/Finalize");
11
+ const LocalFile = require("./lib/concerns/LocalFile");
12
+ const LRO = require("./lib/concerns/LRO");
13
+ const ArgLibraryId = require("./lib/concerns/ArgLibraryId");
14
+ const {seconds} = require("./lib/helpers");
15
+
16
+ class SimpleIngest extends Utility {
17
+ blueprint() {
18
+ return {
19
+ concerns: [Client, Finalize, LocalFile, ArgLibraryId, LRO],
20
+ options: [
21
+ ModOpt("libraryId", {demand: true, forX: "new media object"}),
22
+ NewOpt("title", {
23
+ demand: true,
24
+ descTemplate: "Title for new media object",
25
+ type: "string"
26
+ }),
27
+ NewOpt("drm", {
28
+ default: false,
29
+ descTemplate: "Use DRM for playback",
30
+ type: "boolean"
31
+ }),
32
+ ModOpt("files", {forX: "for new media object"})
33
+ ]
34
+ };
35
+ }
36
+
37
+ async body() {
38
+ const logger = this.logger;
39
+
40
+ let fileHandles = [];
41
+ const fileInfo = this.concerns.LocalFile.fileInfo(fileHandles);
42
+
43
+ // delay getting elvClient until this point so script exits faster
44
+ // if there is a validation error above
45
+ const client = await this.concerns.Client.get();
46
+
47
+ // get metadata from Library
48
+ const libInfo = await this.concerns.ArgLibraryId.libInfo();
49
+
50
+ const type = R.path(["metadata", "abr", "mez_content_type"], libInfo);
51
+ if(R.isNil(type)) throw Error("Library does not specify content type for simple ingests");
52
+
53
+ const libABRProfile = R.path(["metadata", "abr", "default_profile"], libInfo);
54
+ if(R.isNil(libABRProfile)) throw Error("Library does not specify ABR profile for simple ingests");
55
+
56
+ const libMezManageGroups = R.path(["metadata", "abr", "mez_manage_groups"], libInfo);
57
+
58
+ const libMezPermission = R.path(["metadata", "abr", "mez_permission_level"], libInfo);
59
+
60
+ const {drm, libraryId, title} = this.args;
61
+ const encrypt = true;
62
+
63
+ logger.log("Uploading files...");
64
+
65
+ const createMasterResponse = await client.CreateProductionMaster({
66
+ libraryId,
67
+ type,
68
+ name: title,
69
+ description: `Media object created via simple ingest: ${title}`,
70
+ fileInfo,
71
+ encrypt,
72
+ copy: true,
73
+ callback: this.concerns.LocalFile.callback
74
+ });
75
+
76
+ const {id, hash} = createMasterResponse;
77
+ // Log object id immediately, in case of error later in script
78
+ // Don't log hash yet, it will change if --streams was provided (or any other revision to object is needed)
79
+ logger.data("object_id", id);
80
+
81
+ // Close file handles (if any)
82
+ this.concerns.LocalFile.closeFileHandles(fileHandles);
83
+
84
+ logger.errorsAndWarnings(createMasterResponse);
85
+
86
+ logger.logList(
87
+ "",
88
+ "Production master default variant created:",
89
+ ` Object ID: ${id}`,
90
+ ` Version Hash: ${hash}`,
91
+ ""
92
+ );
93
+
94
+ logger.data("version_hash", hash);
95
+
96
+ if(!R.isNil(createMasterResponse.errors) && !R.isEmpty(createMasterResponse.errors)) throw Error(`Error(s) encountered while inspecting uploaded files: ${createMasterResponse.errors.join("\n")}`);
97
+
98
+ // TODO: replace with a 'waitForNewObject' call (Finalize.waitForPublish throws exception for brand new object not yet visible)
99
+ await seconds(2);
100
+
101
+ await this.concerns.Finalize.waitForPublish({
102
+ latestHash: hash,
103
+ libraryId,
104
+ objectId: id
105
+ });
106
+
107
+
108
+ // get production master metadata
109
+ const masterMetadata = (await client.ContentObjectMetadata({
110
+ libraryId,
111
+ objectId: id,
112
+ versionHash: hash,
113
+ metadataSubtree: "/production_master"
114
+ }));
115
+
116
+ const sources = R.prop("sources", masterMetadata);
117
+ const variant = R.path(["variants", "default"], masterMetadata);
118
+
119
+ // add info on source files and variant to data if --json selected
120
+ if(this.args.json) {
121
+ logger.data("media_files", sources);
122
+ logger.data("variant_default", variant);
123
+ }
124
+
125
+ // generate ABR profile
126
+ const genProfileRetVal = ABR.ABRProfileForVariant(sources, variant, libABRProfile);
127
+ if(!genProfileRetVal.ok) throw Error(`Error(s) encountered while generating ABR profile: ${genProfileRetVal.errors.join("\n")}`);
128
+
129
+ // filter DRM/clear as needed
130
+ const filterProfileRetVal = drm ?
131
+ ABR.ProfileExcludeClear(genProfileRetVal.result) :
132
+ ABR.ProfileExcludeDRM(genProfileRetVal.result);
133
+ if(!filterProfileRetVal.ok) throw Error(`Error(s) encountered while setting playout formats: ${filterProfileRetVal.errors.join("\n")}`);
134
+
135
+ // set up mezzanine offering
136
+ logger.log("Setting up media file conversion...");
137
+ const createMezResponse = await client.CreateABRMezzanine({
138
+ name: title,
139
+ libraryId,
140
+ objectId: id,
141
+ type,
142
+ masterVersionHash: hash,
143
+ variant: "default",
144
+ offeringKey: "default",
145
+ abrProfile: filterProfileRetVal.result
146
+ });
147
+
148
+ logger.errorsAndWarnings(createMezResponse);
149
+ const createMezErrors = createMezResponse.errors;
150
+ if(!R.isNil(createMezErrors) && !R.isEmpty(createMezErrors)) throw Error(`Error(s) encountered while setting up media file conversion: ${createMezErrors.join("\n")}`);
151
+
152
+ await this.concerns.Finalize.waitForPublish({
153
+ latestHash: createMezResponse.hash,
154
+ libraryId,
155
+ objectId: id
156
+ });
157
+
158
+
159
+ logger.log("Starting conversion to streaming format...");
160
+
161
+ const startJobsResponse = await client.StartABRMezzanineJobs({
162
+ libraryId,
163
+ objectId: id,
164
+ offeringKey: "default"
165
+ });
166
+
167
+ logger.errorsAndWarnings(startJobsResponse);
168
+ const startJobsErrors = createMezResponse.errors;
169
+ if(!R.isNil(startJobsErrors) && !R.isEmpty(startJobsErrors)) throw Error(`Error(s) encountered while starting file conversion: ${startJobsErrors.join("\n")}`);
170
+
171
+ const lroWriteToken = R.path(["lro_draft", "write_token"], startJobsResponse);
172
+ const lroNode = R.path(["lro_draft", "node"], startJobsResponse);
173
+
174
+ logger.data("library_id", libraryId);
175
+ logger.data("object_id", id);
176
+ logger.data("offering_key", "default");
177
+ logger.data("write_token", lroWriteToken);
178
+ logger.data("write_node", lroNode);
179
+
180
+ logger.logList(
181
+ "",
182
+ `Library ID: ${libraryId}`,
183
+ `Object ID: ${id}`,
184
+ "Offering: default",
185
+ `Write Token: ${lroWriteToken}`,
186
+ `Write Node: ${lroNode}`,
187
+ ""
188
+ );
189
+
190
+ // wait for latest version hash to become visible (if publish not finished, then checking progress can fail
191
+ // as metadata /lro_draft_default will not be found)
192
+
193
+ await this.concerns.Finalize.waitForPublish({
194
+ latestHash: startJobsResponse.hash,
195
+ libraryId,
196
+ objectId: id
197
+ });
198
+
199
+ logger.log("Progress:");
200
+
201
+ const lro = this.concerns.LRO;
202
+ let done = false;
203
+ let lastStatus;
204
+ while(!done) {
205
+ const statusMap = await lro.status({libraryId, objectId: id}); // TODO: check how offering key is used, if at all
206
+ const statusSummary = lro.statusSummary(statusMap);
207
+ lastStatus = statusSummary.run_state;
208
+ if(lastStatus !== LRO.STATE_RUNNING) done = true;
209
+ logger.log(`run_state: ${lastStatus}`);
210
+ const eta = statusSummary.estimated_time_left_h_m_s;
211
+ if(eta) logger.log(`estimated time left: ${eta}`);
212
+ await seconds(15);
213
+ }
214
+
215
+ const finalizeAbrResponse = await client.FinalizeABRMezzanine({
216
+ libraryId,
217
+ objectId: id,
218
+ offeringKey: "default"
219
+ });
220
+ const latestHash = finalizeAbrResponse.hash;
221
+
222
+ logger.errorsAndWarnings(finalizeAbrResponse);
223
+ const finalizeErrors = finalizeAbrResponse.errors;
224
+ if(!R.isNil(finalizeErrors) && !R.isEmpty(finalizeErrors)) throw Error(`Error(s) encountered while finalizing object: ${finalizeErrors.join("\n")}`);
225
+
226
+ if(libMezManageGroups && libMezManageGroups.length > 0){
227
+ for(const groupAddress of libMezManageGroups){
228
+ logger.log("Setting access permissions for managers");
229
+ await client.AddContentObjectGroupPermission({
230
+ objectId: id,
231
+ groupAddress,
232
+ permission: "manage"
233
+ });
234
+
235
+ }
236
+ }
237
+
238
+ if(libMezPermission) {
239
+ if(!["owner", "editable", "viewable", "listable", "public"].includes(libMezPermission)) {
240
+ logger.warn(`Bad value for mez_permission_level: '${libMezPermission}', skipping permission setting`);
241
+ } else {
242
+ logger.log(`Setting object permission to '${libMezPermission}'`);
243
+ const prevHash = await client.LatestVersionHash({objectId: id});
244
+
245
+ await client.SetPermission({
246
+ objectId: id,
247
+ permission: libMezPermission
248
+ });
249
+
250
+ const newHash = await client.LatestVersionHash({objectId: id});
251
+
252
+ if(prevHash === newHash) {
253
+ logger.log("Version hash unchanged: " + newHash );
254
+ } else {
255
+ logger.log("Previous version hash: " + prevHash );
256
+ logger.log("New version hash: " + newHash );
257
+ }
258
+ logger.data("version_hash", newHash);
259
+ }
260
+ }
261
+
262
+ logger.logList(
263
+ "",
264
+ "Playable media object created:",
265
+ ` Object ID: ${id}`,
266
+ ` Version Hash: ${latestHash}`,
267
+ ""
268
+ );
269
+ logger.data("version_hash", latestHash);
270
+ await this.concerns.Finalize.waitForPublish({
271
+ latestHash,
272
+ libraryId,
273
+ objectId: id
274
+ });
275
+ }
276
+
277
+ header() {
278
+ return "Create playable media object via simple ingest";
279
+ }
280
+ }
281
+
282
+ if(require.main === module) {
283
+ Utility.cmdLineInvoke(SimpleIngest);
284
+ } else {
285
+ module.exports = SimpleIngest;
286
+ }