@eluvio/elv-client-js 4.0.144 → 4.0.146

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 -14
  2. package/dist/ElvClient-node-min.js +14 -13
  3. package/dist/ElvFrameClient-min.js +10 -10
  4. package/dist/ElvPermissionsClient-min.js +9 -9
  5. package/dist/ElvWalletClient-min.js +15 -14
  6. package/dist/ElvWalletClient-node-min.js +14 -13
  7. package/dist/src/AuthorizationClient.js +9 -12
  8. package/dist/src/ContentObjectAudit.js +3 -3
  9. package/dist/src/ContentObjectVerification.js +3 -3
  10. package/dist/src/Crypto.js +2 -2
  11. package/dist/src/ElvClient.js +49 -30
  12. package/dist/src/ElvWallet.js +7 -5
  13. package/dist/src/EthClient.js +8 -9
  14. package/dist/src/FrameClient.js +8 -9
  15. package/dist/src/HttpClient.js +1 -2
  16. package/dist/src/Id.js +1 -2
  17. package/dist/src/PermissionsClient.js +31 -19
  18. package/dist/src/RemoteSigner.js +6 -8
  19. package/dist/src/UserProfileClient.js +35 -20
  20. package/dist/src/Utils.js +2 -3
  21. package/dist/src/Validation.js +10 -2
  22. package/dist/src/client/ABRPublishing.js +309 -253
  23. package/dist/src/client/AccessGroups.js +2 -2
  24. package/dist/src/client/ContentAccess.js +2 -3
  25. package/dist/src/client/ContentManagement.js +3 -3
  26. package/dist/src/client/Contracts.js +11 -12
  27. package/dist/src/client/Files.js +2 -2
  28. package/dist/src/client/LiveConf.js +3 -5
  29. package/dist/src/client/LiveStream.js +0 -2
  30. package/dist/src/client/NFT.js +2 -2
  31. package/dist/src/client/Shares.js +2 -2
  32. package/dist/src/walletClient/ClientMethods.js +2 -2
  33. package/dist/src/walletClient/Profile.js +2 -2
  34. package/dist/src/walletClient/Utils.js +2 -2
  35. package/dist/src/walletClient/index.js +17 -14
  36. package/package.json +2 -2
  37. package/src/Validation.js +9 -3
  38. package/src/client/ABRPublishing.js +178 -161
@@ -13,7 +13,8 @@ const {
13
13
  ValidateLibrary,
14
14
  ValidateVersion,
15
15
  ValidateParameters,
16
- ValidateWriteToken
16
+ ValidateWriteToken,
17
+ ValidateObject
17
18
  } = require("../Validation");
18
19
 
19
20
  // When `/abr_mezzanine/offerings` contains more than one entry, only 1 is the 'real' offering, the others are
@@ -98,13 +99,15 @@ exports.CreateProductionMaster = async function({
98
99
  structLogLevel="none"
99
100
  }) {
100
101
  ValidateLibrary(libraryId);
101
- let id;
102
+ let objectId;
102
103
  const finalize = !writeToken;
103
104
 
104
105
  if(writeToken) {
105
- id = this.utils.DecodeWriteToken(writeToken).objectId;
106
+ // if write token passed in, don't create a new object
107
+ objectId = this.utils.DecodeWriteToken(writeToken).objectId;
106
108
  } else {
107
- ({id, writeToken} = await this.CreateContentObject({
109
+ // no write token supplied - create a new object and get write token
110
+ ({objectId, writeToken} = await this.CreateContentObject({
108
111
  libraryId,
109
112
  options: type ? { type } : {}
110
113
  }));
@@ -166,7 +169,7 @@ exports.CreateProductionMaster = async function({
166
169
 
167
170
  await this.UploadFilesFromS3({
168
171
  libraryId,
169
- objectId: id,
172
+ objectId,
170
173
  writeToken,
171
174
  fileInfo: credentialSet.matched,
172
175
  region,
@@ -183,7 +186,7 @@ exports.CreateProductionMaster = async function({
183
186
  } else {
184
187
  await this.UploadFiles({
185
188
  libraryId,
186
- objectId: id,
189
+ objectId,
187
190
  writeToken,
188
191
  fileInfo,
189
192
  callback,
@@ -192,11 +195,11 @@ exports.CreateProductionMaster = async function({
192
195
  }
193
196
  }
194
197
 
195
- await this.CreateEncryptionConk({libraryId, objectId: id, writeToken, createKMSConk: true});
198
+ await this.CreateEncryptionConk({libraryId, objectId, writeToken, createKMSConk: true});
196
199
 
197
200
  const { logs, errors, warnings } = await this.CallBitcodeMethod({
198
201
  libraryId,
199
- objectId: id,
202
+ objectId,
200
203
  writeToken,
201
204
  method: UrlJoin("media", "production_master", "init"),
202
205
  queryParams: {
@@ -211,7 +214,7 @@ exports.CreateProductionMaster = async function({
211
214
 
212
215
  await this.MergeMetadata({
213
216
  libraryId,
214
- objectId: id,
217
+ objectId,
215
218
  writeToken,
216
219
  metadata: {
217
220
  ...(metadata || {}),
@@ -227,22 +230,22 @@ exports.CreateProductionMaster = async function({
227
230
  }
228
231
  });
229
232
 
230
- let finalizeResponse;
233
+ let additionalReturnVals;
231
234
 
232
235
  if(finalize) {
233
- finalizeResponse = await this.FinalizeContentObject({
236
+ additionalReturnVals = await this.FinalizeContentObject({
234
237
  libraryId,
235
- objectId: id,
238
+ objectId,
236
239
  writeToken,
237
240
  commitMessage: "Create master",
238
241
  awaitCommitConfirmation: false
239
242
  });
240
243
  } else {
241
- finalizeResponse = {
244
+ additionalReturnVals = {
242
245
  write_token: writeToken,
243
246
  type,
244
247
  qlib_id: libraryId,
245
- id
248
+ id: objectId
246
249
  };
247
250
  }
248
251
 
@@ -250,7 +253,7 @@ exports.CreateProductionMaster = async function({
250
253
  errors: errors || [],
251
254
  logs: logs || [],
252
255
  warnings: warnings || [],
253
- ...finalizeResponse
256
+ ...additionalReturnVals
254
257
  };
255
258
  };
256
259
 
@@ -265,8 +268,8 @@ exports.CreateProductionMaster = async function({
265
268
  * @param {boolean=} keepOtherStreams=false - If objectId is specified, whether to preserve existing streams with keys other than the ones specified in production master
266
269
  * @param {string} libraryId - ID of the mezzanine library
267
270
  * @param {string} masterVersionHash - The version hash of the production master content object
268
- * @param {string=} masterWriteToken - The write token of the production master content object draft. If provided, the object will not be finalized
269
- * @param {string=} writeToken - The write token of the mezzanine object draft. If specified, the object will not be finalized
271
+ * @param {string=} masterWriteToken - The write token of the production master content object draft.
272
+ * @param {string=} writeToken - The write token of the mezzanine object draft. If specified, the object will not be finalized, and no extra version of the object will be created to hold "bread crumb" metadata entry /lro_draft
270
273
  * @param {Object=} metadata - Additional metadata for mezzanine content object
271
274
  * @param {string} name - Name for mezzanine content object
272
275
  * @param {string=} objectId - ID of existing object (if not specified, new object will be created)
@@ -298,91 +301,94 @@ exports.CreateABRMezzanine = async function({
298
301
  structLogLevel="none",
299
302
  streamKeys
300
303
  }) {
304
+
305
+ if(!masterVersionHash && !masterWriteToken) {
306
+ throw Error("Neither master version hash nor master write token specified. One must be provided");
307
+ }
308
+
309
+ if(masterVersionHash && masterWriteToken) {
310
+ throw Error("Both master version hash and master write token specified. Only one can be provided");
311
+ }
312
+
313
+ // double-check library id for mez
301
314
  ValidateLibrary(libraryId);
302
315
 
316
+ // The following 2 vars are used only if masterWriteToken is passed in (to retrieve master name)
303
317
  let masterObjectId;
318
+ let masterLibId;
304
319
  if(masterVersionHash) {
320
+ // NOTE: masterObjectId is left undefined if masterWriteToken not passed in
305
321
  ValidateVersion(masterVersionHash);
306
322
  } else if(masterWriteToken) {
323
+ // determine master object id if a master write token was passed in
307
324
  ValidateWriteToken(masterWriteToken);
308
-
309
325
  masterObjectId = this.utils.DecodeWriteToken(masterWriteToken).objectId;
326
+ masterLibId = await client.ContentObjectLibraryId({masterObjectId});
310
327
  }
311
328
 
312
- if(writeToken) {
313
- ValidateWriteToken(writeToken);
314
-
315
- if(!objectId) {
316
- objectId = this.utils.DecodeWriteToken(writeToken).objectId;
317
- }
318
- }
329
+ // if pre-existing mez object id passed in, validate
330
+ if(objectId) ValidateObject(objectId);
319
331
 
320
- if(!masterVersionHash && !masterWriteToken) {
321
- throw Error("Master version hash and master write token not specified. One must be provided");
322
- }
332
+ // if mez write token supplied, validate it
333
+ if(writeToken) {
334
+ ValidateWriteToken(writeToken, objectId);
323
335
 
324
- if(!objectId && (keepOtherStreams)) {
325
- throw Error("Existing mezzanine object ID required in order to use 'keepOtherStreams'");
336
+ // if pre-existing mez object id not passed in, decode it from write token
337
+ objectId = objectId || this.utils.DecodeWriteToken(writeToken).objectId;
326
338
  }
339
+ // After above, if objectId is undefined then no mez writeToken was passed in, no mez objectId was passed in,
340
+ // meaning we must create a new object
341
+ const useExistingMez = !!objectId; // type coerce string or undefined to boolean: "" -> false, undefined -> false, all else -> true
327
342
 
328
- if(addlOfferingSpecs && !abrProfile) {
329
- throw Error("abrProfile required when using addlOfferingSpecs");
330
- }
343
+ if(!objectId && (keepOtherStreams)) throw Error("Existing mezzanine object ID required in order to use 'keepOtherStreams'");
331
344
 
332
- const existingMez = !!objectId;
345
+ if(addlOfferingSpecs && !abrProfile) throw Error("abrProfile required when using addlOfferingSpecs");
333
346
 
334
347
  let options = type ? { type } : {};
335
348
 
336
- let id;
337
349
  const finalize = !writeToken;
338
350
 
339
- if(existingMez) {
340
- // Edit existing
341
- if(writeToken) {
342
- id = objectId;
343
- } else {
344
- ({writeToken, id} = await this.EditContentObject({
345
- libraryId,
346
- objectId,
347
- options
348
- }));
349
- }
350
- } else {
351
- // Create new
352
- const createResponse = await this.CreateContentObject({
351
+ // are we ingesting to an existing mez (and did NOT supply a write token?)
352
+ if(useExistingMez && !writeToken) {
353
+ // then we need to obtain a write token for the existing object
354
+ ({writeToken} = await this.EditContentObject({
353
355
  libraryId,
356
+ objectId,
354
357
  options
355
- });
358
+ }));
359
+ }
356
360
 
357
- id = createResponse.id;
358
- writeToken = createResponse.write_token;
361
+ // if we are not using an existing mez, we need to create a brand new object (in draft form)
362
+ if(!useExistingMez) {
363
+ // Create new mez object
364
+ ({objectId, writeToken} = await this.CreateContentObject({
365
+ libraryId,
366
+ options
367
+ }));
359
368
  }
360
369
 
361
- await this.CreateEncryptionConk({libraryId, objectId: id, writeToken, createKMSConk: true});
370
+ // from here forwards, objectId and writeToken can be assumed to be populated (both refer to mez)
362
371
 
372
+ // ensure this object has encryption/kms conk
373
+ await this.CreateEncryptionConk({libraryId, objectId, writeToken, createKMSConk: true});
363
374
 
364
- let nameMetaPayload;
365
- if(masterWriteToken) {
366
- nameMetaPayload = {
367
- libraryId,
368
- objectId: masterObjectId,
369
- writeToken: masterWriteToken
370
- };
371
- } else if(masterVersionHash) {
372
- nameMetaPayload = {
373
- versionHash: masterVersionHash
374
- };
375
- }
375
+ // figure out where to read master metadata from
376
+ const masterMetaSource = masterWriteToken ? {
377
+ libraryId: masterLibId,
378
+ objectId: masterObjectId,
379
+ writeToken: masterWriteToken
380
+ } : {versionHash: masterVersionHash};
376
381
 
377
382
  const masterName = await this.ContentObjectMetadata({
378
- ...nameMetaPayload,
383
+ ...masterMetaSource,
379
384
  metadataSubtree: "public/name"
380
385
  });
381
386
 
382
387
  // Include authorization for library, master, and mezzanine
383
388
  let authorizationTokens = [];
384
- authorizationTokens.push(await this.authClient.AuthorizationToken({libraryId, objectId: id, update: true}));
389
+ authorizationTokens.push(await this.authClient.AuthorizationToken({libraryId, objectId, update: true}));
385
390
  authorizationTokens.push(await this.authClient.AuthorizationToken({libraryId}));
391
+ // only 1 of the vars masterVersionHash and masterObjectId below will contain a value
386
392
  authorizationTokens.push(await this.authClient.AuthorizationToken({versionHash: masterVersionHash, objectId: masterObjectId}));
387
393
 
388
394
  const headers = {
@@ -398,7 +404,7 @@ exports.CreateABRMezzanine = async function({
398
404
  variant_key: variant
399
405
  };
400
406
 
401
- let storeClear = false;
407
+ let storeClear;
402
408
  if(abrProfile) {
403
409
  body.abr_profile = abrProfile;
404
410
  storeClear = abrProfile.store_clear;
@@ -415,14 +421,14 @@ exports.CreateABRMezzanine = async function({
415
421
  // If mez parts are to be encrypted, generate encryption conks
416
422
  await this.EncryptionConk({
417
423
  libraryId,
418
- objectId: id,
424
+ objectId,
419
425
  writeToken
420
426
  });
421
427
  }
422
428
 
423
429
  const {logs, errors, warnings} = await this.CallBitcodeMethod({
424
430
  libraryId,
425
- objectId: id,
431
+ objectId,
426
432
  writeToken,
427
433
  method: UrlJoin("media", "abr_mezzanine", "init"),
428
434
  queryParams: {
@@ -465,12 +471,12 @@ exports.CreateABRMezzanine = async function({
465
471
  ...metadata.public.asset_metadata
466
472
  };
467
473
 
468
- if(name || !existingMez) {
474
+ if(name || !useExistingMez) {
469
475
  metadata.name = name || `${masterName} Mezzanine`;
470
476
  metadata.public.name = name || `${masterName} Mezzanine`;
471
477
  }
472
478
 
473
- if(description || !existingMez) {
479
+ if(description || !useExistingMez) {
474
480
  metadata.description = description || "";
475
481
  metadata.public.description = description || "";
476
482
  }
@@ -478,14 +484,14 @@ exports.CreateABRMezzanine = async function({
478
484
  // retrieve existing metadata to merge with updated metadata
479
485
  const existingMetadata = await this.ContentObjectMetadata({
480
486
  libraryId,
481
- objectId: id,
487
+ objectId,
482
488
  writeToken,
483
489
  });
484
490
  // newer metadata values replace existing metadata, unless both new and old values are objects,
485
491
  // in which case their keys are merged recursively
486
492
  metadata = R.mergeDeepRight(existingMetadata, metadata);
487
493
 
488
- if(!existingMez) {
494
+ if(!useExistingMez) {
489
495
  // set creation date
490
496
  metadata.elv_created_at = new Date().getTime();
491
497
  }
@@ -493,25 +499,25 @@ exports.CreateABRMezzanine = async function({
493
499
  // write metadata to mezzanine object
494
500
  await this.ReplaceMetadata({
495
501
  libraryId,
496
- objectId: id,
502
+ objectId,
497
503
  writeToken,
498
504
  metadata
499
505
  });
500
506
 
501
- let finalizeResponse;
507
+ let additionalReturnVals;
502
508
  if(finalize) {
503
- finalizeResponse = await this.FinalizeContentObject({
509
+ additionalReturnVals = await this.FinalizeContentObject({
504
510
  libraryId,
505
- objectId: id,
511
+ objectId,
506
512
  writeToken,
507
513
  commitMessage: "Create ABR mezzanine"
508
514
  });
509
515
  } else {
510
- finalizeResponse = {
516
+ additionalReturnVals = {
511
517
  write_token: writeToken,
512
518
  type,
513
519
  qlib_id: libraryId,
514
- id
520
+ id: objectId
515
521
  };
516
522
  }
517
523
 
@@ -519,7 +525,7 @@ exports.CreateABRMezzanine = async function({
519
525
  logs: logs || [],
520
526
  warnings: warnings || [],
521
527
  errors: errors || [],
522
- ...finalizeResponse
528
+ ...additionalReturnVals
523
529
  };
524
530
  };
525
531
 
@@ -530,7 +536,7 @@ exports.CreateABRMezzanine = async function({
530
536
  * @namedParams
531
537
  * @param {string} libraryId - ID of the mezzanine library
532
538
  * @param {string} objectId - ID of the mezzanine object
533
- * @param {string=} writeToken - Write token of the mezzanine object draft. If provided, the object will not be finalized
539
+ * @param {string=} writeToken - Write token of the mezzanine object draft. If provided, no "bread crumb" metadata /lro_draft will be created, nor saved as an extra finalized version
534
540
  * @param {Array<Object>=} access - Array of S3 credentials, along with path matching regexes - Required if any files in the masters are S3 references (See CreateProductionMaster method)
535
541
  * - Format: {region, bucket, accessKey, secret}
536
542
  * @param {number[]} jobIndexes - Array of LRO job indexes to start. LROs are listed in a map under metadata key /abr_mezzanine/offerings/(offeringKey)/mez_prep_specs/, and job indexes start with 0, corresponding to map keys in alphabetical order
@@ -546,12 +552,11 @@ exports.StartABRMezzanineJobs = async function({
546
552
  }) {
547
553
  ValidateParameters({libraryId, objectId});
548
554
 
549
- if(writeToken) {
550
- ValidateWriteToken(writeToken);
555
+ const writeTokenSupplied = !!writeToken;
551
556
 
552
- if(!objectId) {
553
- objectId = this.utils.DecodeWriteToken(writeToken).objectId;
554
- }
557
+ if(writeTokenSupplied) {
558
+ ValidateWriteToken(writeToken, objectId);
559
+ objectId = objectId || this.utils.DecodeWriteToken(writeToken).objectId;
555
560
  }
556
561
 
557
562
  const lastJobOfferingsInfo = await this.ContentObjectMetadata({
@@ -595,60 +600,51 @@ exports.StartABRMezzanineJobs = async function({
595
600
  ...authorizationTokens
596
601
  ];
597
602
 
598
- const headers = {
599
- Authorization: authorizationTokens.map(token => `Bearer ${token}`).join(",")
600
- };
601
-
602
- let processingDraft;
603
- if(writeToken) {
604
- const nodeUrl = await this.WriteTokenNodeUrlNetwork({writeToken});
605
-
606
- processingDraft = {
607
- write_token: writeToken,
608
- nodeUrl
603
+ const headers = {Authorization: authorizationTokens.map(token => `Bearer ${token}`).join(",")};
604
+
605
+ let breadCrumb;
606
+ let breadCrumbHash;
607
+ // If we did not have a write token passed in, get a new write token for the LROs to write to
608
+ if(!writeTokenSupplied) {
609
+ const lroWorkingDraft = await this.EditContentObject({libraryId, objectId});
610
+ writeToken = lroWorkingDraft.writeToken;
611
+
612
+ // Save this write token (and node url, and offering key) to ANOTHER, NEW write token in order to
613
+ // record "bread crumb" metadata at /lro_draft, and save (finalize) this second write token
614
+ const breadCrumbDraft = await this.EditContentObject({libraryId, objectId});
615
+ breadCrumb = {
616
+ write_token: lroWorkingDraft.writeToken,
617
+ node: lroWorkingDraft.nodeUrl,
618
+ offering: offeringKey
609
619
  };
610
- } else {
611
- processingDraft = await this.EditContentObject({libraryId, objectId});
612
- }
613
-
614
- const lroInfo = {
615
- write_token: processingDraft.write_token,
616
- node: processingDraft.nodeUrl,
617
- offering: offeringKey
618
- };
619
620
 
620
- // Update metadata with LRO version write token
621
- let finalizeResponse;
622
- if(writeToken) {
623
- await this.ReplaceMetadata({
624
- libraryId,
625
- objectId,
626
- writeToken,
627
- metadataSubtree: "lro_draft",
628
- metadata: lroInfo
629
- });
630
- } else {
631
- const statusDraft = await this.EditContentObject({libraryId, objectId});
632
621
  await this.ReplaceMetadata({
633
622
  libraryId,
634
623
  objectId,
635
- writeToken: statusDraft.write_token,
624
+ writeToken: breadCrumbDraft.writeToken,
636
625
  metadataSubtree: "lro_draft",
637
- metadata: lroInfo
626
+ metadata: breadCrumb
638
627
  });
639
628
 
640
- finalizeResponse = await this.FinalizeContentObject({
629
+ const finalizeResponse = await this.FinalizeContentObject({
641
630
  libraryId,
642
631
  objectId,
643
- writeToken: statusDraft.write_token,
644
- commitMessage: "Mezzanine LRO status"
632
+ writeToken: breadCrumbDraft.write_token,
633
+ commitMessage: "Save mez LRO write token to /lro_draft"
645
634
  });
635
+ breadCrumbHash = finalizeResponse.hash;
646
636
  }
647
637
 
638
+ // make sure we have correct nodeUrl for this token
639
+ const nodeUrl = await this.WriteTokenNodeUrlNetwork({writeToken});
640
+ // tell http client what node to contact for this write token
641
+ this.RecordWriteToken({writeToken, fabricNodeUrl: nodeUrl});
642
+
643
+ // Make the API call that actually starts the LROs
648
644
  const {data, errors, warnings, logs} = await this.CallBitcodeMethod({
649
645
  libraryId,
650
646
  objectId,
651
- writeToken: processingDraft.write_token,
647
+ writeToken,
652
648
  headers,
653
649
  method: UrlJoin("media", "abr_mezzanine", "prep_start"),
654
650
  constant: false,
@@ -660,10 +656,10 @@ exports.StartABRMezzanineJobs = async function({
660
656
  });
661
657
 
662
658
  return {
663
- hash: finalizeResponse ? finalizeResponse.hash : "",
664
- lro_draft: lroInfo,
665
- writeToken: processingDraft.write_token,
666
- nodeUrl: processingDraft.nodeUrl,
659
+ hash: breadCrumbHash,
660
+ lro_draft: breadCrumb,
661
+ writeToken,
662
+ nodeUrl,
667
663
  data,
668
664
  logs: logs || [],
669
665
  warnings: warnings || [],
@@ -675,6 +671,10 @@ exports.StartABRMezzanineJobs = async function({
675
671
  * Retrieve node and write token for a mezzanine's current offering preparation job (if any).
676
672
  * Also returns the offering key.
677
673
  *
674
+ * This information is contained in metadata at /lro_draft, in an extra version of the object
675
+ * that gets created and finalized as part of creating a mezzanine. This serves as a "bread crumb trail" to
676
+ * allow users to find the active draft/write token that the ingest LROs are writing to.
677
+ *
678
678
  * @methodGroup ABR Publishing
679
679
  * @namedParams
680
680
  * @param {string} libraryId - ID of the library
@@ -682,11 +682,10 @@ exports.StartABRMezzanineJobs = async function({
682
682
  *
683
683
  * @return {Promise<Object>} - LRO status
684
684
  */
685
- exports.LRODraftInfo = async function({libraryId, objectId, writeToken}) {
685
+ exports.LRODraftInfo = async function({libraryId, objectId}) {
686
686
  const standardPathContents = await this.ContentObjectMetadata({
687
687
  libraryId,
688
688
  objectId,
689
- writeToken,
690
689
  metadataSubtree: "lro_draft"
691
690
  });
692
691
 
@@ -728,53 +727,74 @@ exports.LRODraftInfo = async function({libraryId, objectId, writeToken}) {
728
727
 
729
728
  /**
730
729
  * Retrieve status information for mezzanine transcoding jobs, aka long running operations (LROs) on the given object.
730
+ * If a write token is passed in, its draft will be checked directly by lookiing at /lro_status.
731
+ * If a write token is not passed in, then the latest version of objectId will be checked for "bread crumb" entry
732
+ * at /lro_draft, to determine the write token that current ingest LRO(s) are writing to.
731
733
  *
732
734
  * @methodGroup ABR Publishing
733
735
  * @namedParams
734
736
  * @param {string} libraryId - ID of the library
735
737
  * @param {string} objectId - ID of the object
738
+ * @param {string=} writeToken - Write token of the active draft (that the transcode LROs are writing to)
736
739
  *
737
740
  * @return {Promise<Object>} - LRO status
738
741
  */
739
- exports.LROStatus = async function({libraryId, objectId, writeToken}) {
742
+ exports.LROStatus = async function({libraryId, objectId, writeToken= ""}) {
740
743
  ValidateParameters({libraryId, objectId});
741
744
 
742
- const lroDraft = await this.LRODraftInfo({libraryId, objectId, writeToken});
743
-
744
- this.RecordWriteToken({writeToken: lroDraft.write_token, fabricNodeUrl: lroDraft.node});
745
+ let activeWriteToken;
746
+ if(writeToken) {
747
+ ValidateWriteToken(writeToken, objectId);
748
+ activeWriteToken = writeToken;
749
+ } else {
750
+ const lroDraft = await this.LRODraftInfo({libraryId, objectId});
751
+ activeWriteToken = lroDraft.write_token;
752
+ const fabricNodeUrl = await this.WriteTokenNodeUrlNetwork({writeToken: activeWriteToken});
753
+ this.RecordWriteToken({writeToken: lroDraft.write_token, fabricNodeUrl});
754
+ }
745
755
 
746
756
  return await this.ContentObjectMetadata({
747
757
  libraryId,
748
758
  objectId,
749
- writeToken,
759
+ writeToken: activeWriteToken,
750
760
  metadataSubtree: "lro_status"
751
761
  });
752
762
  };
753
763
 
754
764
  /**
755
- * Finalize a mezzanine object after all jobs have finished
765
+ * Process completed mezzanine transcoding LRO(s), creating the final offering(s) from the transcode(s)
766
+ * Unless a writeToken is passed in, finalize the mez object working draft as well.
767
+ *
768
+ * If a writeToken is not passed in, then it will be retrieved from /lro_draft of most recent version of object.
769
+ * This is a "bread crumb" entry that gets created by StartABRMezzanineJobs() - unless a writeToken was passed in to StartABRMezzanineJobs() also.
756
770
  *
757
771
  * @methodGroup ABR Publishing
758
772
  * @namedParams
759
773
  * @param {string} libraryId - ID of the mezzanine library
760
774
  * @param {string} objectId - ID of the mezzanine object
761
- * @param {string} writeToken - Write token for the mezzanine object. If specified, the object will not be finalized.
775
+ * @param {string=} writeToken - Write token for the mezzanine object. If specified, the object will not be finalized.
762
776
  * @param {function=} preFinalizeFn - A function to call before finalizing changes, to allow further modifications to offering. The function will be invoked with {elvClient, nodeUrl, writeToken} to allow access to the draft and MUST NOT finalize the draft.
763
777
  * @param {boolean=} preFinalizeThrow - If set to `true` then any error thrown by preFinalizeFn will not be caught. Otherwise, any exception will be appended to the `warnings` array returned after finalization.
764
778
  *
765
779
  * @return {Promise<Object>} - The finalize response for the mezzanine object, as well as any logs, warnings and errors from the finalization
766
780
  */
767
- exports.FinalizeABRMezzanine = async function({libraryId, objectId, preFinalizeFn, preFinalizeThrow, writeToken}) {
781
+ exports.FinalizeABRMezzanine = async function({libraryId, objectId, preFinalizeFn, preFinalizeThrow, writeToken=""}) {
768
782
  ValidateParameters({libraryId, objectId});
769
783
 
770
- if(writeToken) {
771
- ValidateWriteToken(writeToken);
772
- }
784
+ const writeTokenSupplied = !!writeToken;
773
785
 
774
- const nodeUrl = await this.WriteTokenNodeUrlNetwork({writeToken});
786
+ if(writeTokenSupplied) {
787
+ ValidateWriteToken(writeToken, objectId);
788
+ objectId = objectId || this.utils.DecodeWriteToken(writeToken).objectId;
789
+ } else {
790
+ // get write token from draft info
791
+ const lroDraft = await this.LRODraftInfo({libraryId, objectId});
792
+ writeToken = lroDraft.write_token;
793
+ }
775
794
 
795
+ const fabricNodeUrl = await this.WriteTokenNodeUrlNetwork({writeToken});
776
796
  // tell http client what node to contact for this write token
777
- this.RecordWriteToken({writeToken: writeToken, fabricNodeUrl: nodeUrl});
797
+ this.RecordWriteToken({writeToken, fabricNodeUrl});
778
798
 
779
799
  const lastJobOfferingsInfo = await this.ContentObjectMetadata({
780
800
  libraryId,
@@ -786,22 +806,20 @@ exports.FinalizeABRMezzanine = async function({libraryId, objectId, preFinalizeF
786
806
  const offeringKey = MezJobMainOfferingKey(lastJobOfferingsInfo);
787
807
  const masterHash = lastJobOfferingsInfo[offeringKey].prod_master_hash;
788
808
 
789
- let authPayload = {};
809
+ let masterIdentifier = {};
790
810
  if(masterHash.startsWith("tqw__")) {
791
- authPayload["objectId"] = this.utils.DecodeWriteToken(masterHash).objectId;
811
+ masterIdentifier["objectId"] = this.utils.DecodeWriteToken(masterHash).objectId;
792
812
  } else {
793
- authPayload["versionHash"] = masterHash;
813
+ masterIdentifier["versionHash"] = masterHash;
794
814
  }
795
815
 
796
816
  // Authorization token for mezzanine and master
797
817
  let authorizationTokens = [
798
818
  await this.authClient.AuthorizationToken({libraryId, objectId, update: true}),
799
- await this.authClient.AuthorizationToken({...authPayload})
819
+ await this.authClient.AuthorizationToken({...masterIdentifier})
800
820
  ];
801
821
 
802
- const headers = {
803
- Authorization: authorizationTokens.map(token => `Bearer ${token}`).join(",")
804
- };
822
+ const headers = {Authorization: authorizationTokens.map(token => `Bearer ${token}`).join(",")};
805
823
 
806
824
  const {data, errors, warnings, logs} = await this.CallBitcodeMethod({
807
825
  objectId,
@@ -831,16 +849,15 @@ exports.FinalizeABRMezzanine = async function({libraryId, objectId, preFinalizeF
831
849
  }
832
850
  }
833
851
 
834
- let finalizeResponse = {};
835
- if(!writeToken) {
836
- finalizeResponse = await this.FinalizeContentObject({
837
- libraryId,
838
- objectId: objectId,
839
- writeToken,
840
- commitMessage: "Finalize ABR mezzanine",
841
- awaitCommitConfirmation: false
842
- });
843
- }
852
+ // only finalize if we did NOT have a writeToken passed in
853
+ const finalizeResponse = writeTokenSupplied ? {} : await this.FinalizeContentObject({
854
+ libraryId,
855
+ objectId,
856
+ writeToken,
857
+ commitMessage: "Finalize ABR mezzanine",
858
+ awaitCommitConfirmation: false
859
+ });
860
+
844
861
 
845
862
  return {
846
863
  data,