@eluvio/elv-client-js 4.2.17 → 4.2.19

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.19",
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) {
@@ -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
  *
@@ -1,281 +0,0 @@
1
- var _regeneratorRuntime = require("@babel/runtime/regenerator");
2
- var _asyncToGenerator = require("@babel/runtime/helpers/asyncToGenerator");
3
- function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
4
- function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
5
- function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
6
- var CBOR = require("cbor");
7
- var SJCL = require("sjcl");
8
- var MultiHash = require("multihashes");
9
- var DeepEqual = require("deep-equal");
10
- var Utils = require("./Utils");
11
- var ContentObjectVerification = {
12
- VerifyContentObject: function VerifyContentObject(_ref) {
13
- return _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
14
- var client, libraryId, objectId, versionHash, response, partHash, qpartsResponse, partVerification, qmdHash, metadataPartHash, metadataPartResponse, metadataVerification, metadata, qstructHash, structPartHash, structPartResponse, structVerification;
15
- return _regeneratorRuntime.wrap(function _callee$(_context) {
16
- while (1) switch (_context.prev = _context.next) {
17
- case 0:
18
- client = _ref.client, libraryId = _ref.libraryId, objectId = _ref.objectId, versionHash = _ref.versionHash;
19
- response = {
20
- hash: versionHash
21
- };
22
- partHash = Utils.DecodeVersionHash(versionHash).partHash;
23
- _context.next = 5;
24
- return client.QParts({
25
- libraryId: libraryId,
26
- objectId: objectId,
27
- partHash: partHash,
28
- format: "arrayBuffer"
29
- }).then(function (response) {
30
- return Buffer.from(response);
31
- });
32
- case 5:
33
- qpartsResponse = _context.sent;
34
- partVerification = ContentObjectVerification._VerifyPart({
35
- partHash: partHash,
36
- qpartsResponse: qpartsResponse
37
- });
38
- if (partVerification.valid) {
39
- response.qref = {
40
- valid: true
41
- };
42
- } else {
43
- response.qref = {
44
- valid: false,
45
- error: partVerification.error.message
46
- };
47
- }
48
- response.qref.hash = partHash;
49
- if (!response.qref.valid) {
50
- _context.next = 32;
51
- break;
52
- }
53
- // Validate Metadata
54
- qmdHash = partVerification.cbor.QmdHash.value;
55
- metadataPartHash = "hqp_" + MultiHash.toB58String(qmdHash.slice(1, qmdHash.length));
56
- _context.next = 14;
57
- return client.QParts({
58
- libraryId: libraryId,
59
- objectId: objectId,
60
- partHash: metadataPartHash,
61
- format: "arrayBuffer"
62
- }).then(function (response) {
63
- return Buffer.from(response);
64
- });
65
- case 14:
66
- metadataPartResponse = _context.sent;
67
- metadataVerification = ContentObjectVerification._VerifyPart({
68
- partHash: metadataPartHash,
69
- qpartsResponse: metadataPartResponse
70
- });
71
- if (metadataVerification.valid) {
72
- response.qmd = {
73
- valid: true
74
- };
75
- } else {
76
- response.qmd = {
77
- valid: false,
78
- error: metadataVerification.error.message
79
- };
80
- }
81
- response.qmd.hash = metadataPartHash;
82
- if (!(response.qmd.valid && libraryId)) {
83
- _context.next = 23;
84
- break;
85
- }
86
- _context.next = 21;
87
- return client.ContentObjectMetadata({
88
- libraryId: libraryId,
89
- objectId: objectId,
90
- versionHash: partHash.replace("hqp_", "hq__")
91
- });
92
- case 21:
93
- metadata = _context.sent;
94
- response.qmd.check = ContentObjectVerification._VerifyMetadata({
95
- metadataCbor: metadataVerification.cbor,
96
- metadata: metadata
97
- });
98
- case 23:
99
- // Validate Qstruct
100
- qstructHash = partVerification.cbor.QstructHash.value;
101
- structPartHash = "hqp_" + MultiHash.toB58String(qstructHash.slice(1, qstructHash.length));
102
- _context.next = 27;
103
- return client.QParts({
104
- libraryId: libraryId,
105
- objectId: objectId,
106
- partHash: structPartHash,
107
- format: "arrayBuffer"
108
- }).then(function (response) {
109
- return Buffer.from(response);
110
- });
111
- case 27:
112
- structPartResponse = _context.sent;
113
- structVerification = ContentObjectVerification._VerifyPart({
114
- partHash: structPartHash,
115
- qpartsResponse: structPartResponse
116
- });
117
- if (structVerification.valid) {
118
- response.qstruct = {
119
- valid: true
120
- };
121
- } else {
122
- response.qstruct = {
123
- valid: false,
124
- error: structVerification.error.message
125
- };
126
- }
127
- response.qstruct.hash = structPartHash;
128
- if (response.qstruct.valid) {
129
- response.qstruct.parts = ContentObjectVerification._FormatQStruct(structVerification.cbor.Parts);
130
- }
131
- case 32:
132
- response.valid = response.qref.valid && response.qmd.valid && response.qstruct.valid && (!response.qmd.check || response.qmd.check.valid);
133
- return _context.abrupt("return", response);
134
- case 34:
135
- case "end":
136
- return _context.stop();
137
- }
138
- }, _callee);
139
- }))();
140
- },
141
- // Content verification methods //
142
- _FormatQStruct: function _FormatQStruct(structParts) {
143
- if (!structParts) {
144
- return [];
145
- }
146
- return structParts.map(function (structPart) {
147
- return {
148
- hash: "hqp_" + MultiHash.toB58String(structPart.Hash.value.slice(1, structPart.Hash.length)),
149
- size: structPart.Size
150
- };
151
- });
152
- },
153
- _Hash: function _Hash(thing) {
154
- function fromBits(arr) {
155
- var out = [],
156
- bl = SJCL.bitArray.bitLength(arr),
157
- i,
158
- tmp;
159
- for (i = 0; i < bl / 8; i++) {
160
- if ((i & 3) === 0) {
161
- tmp = arr[i / 4];
162
- }
163
- out.push(tmp >>> 24);
164
- tmp <<= 8;
165
- }
166
- return out;
167
- }
168
- function toBits(bytes) {
169
- var out = [],
170
- i,
171
- tmp = 0;
172
- for (i = 0; i < bytes.length; i++) {
173
- tmp = tmp << 8 | bytes[i];
174
- if ((i & 3) === 3) {
175
- out.push(tmp);
176
- tmp = 0;
177
- }
178
- }
179
- if (i & 3) {
180
- out.push(SJCL.bitArray.partial(8 * (i & 3), tmp));
181
- }
182
- return out;
183
- }
184
- var digest = SJCL.hash.sha256.hash(toBits(thing));
185
- var bytes = fromBits(digest);
186
- var out = Buffer.from(bytes, "binary");
187
- return MultiHash.toB58String(MultiHash.encode(out, "sha2-256"));
188
- },
189
- _ParseCBOR: function _ParseCBOR(cborResponse) {
190
- var buffer = cborResponse.slice(7, cborResponse.length);
191
- var hex = buffer.toString("hex");
192
- return CBOR.decodeFirstSync(hex);
193
- },
194
- _VerifyPart: function _VerifyPart(_ref2) {
195
- var partHash = _ref2.partHash,
196
- qpartsResponse = _ref2.qpartsResponse;
197
- try {
198
- if (ContentObjectVerification._Hash(qpartsResponse) !== partHash.replace("hqp_", "")) {
199
- throw Error("Hashes do not match");
200
- }
201
- var cbor = ContentObjectVerification._ParseCBOR(qpartsResponse);
202
- return {
203
- valid: true,
204
- cbor: cbor
205
- };
206
- } catch (error) {
207
- return {
208
- valid: false,
209
- error: error
210
- };
211
- }
212
- },
213
- _VerifyMetadata: function _VerifyMetadata(_ref3) {
214
- var metadataCbor = _ref3.metadataCbor,
215
- metadata = _ref3.metadata;
216
- if (!metadataCbor) {
217
- metadataCbor = {};
218
- }
219
- if (!metadata) {
220
- metadata = {};
221
- }
222
- var response = {
223
- valid: true,
224
- invalidValues: []
225
- };
226
- var cborKeys = Object.keys(metadataCbor);
227
- var metadataKeys = Object.keys(metadata);
228
-
229
- // Find any difference between top level keys
230
- var differentKeys = cborKeys.filter(function (x) {
231
- return !metadataKeys.includes(x);
232
- }).concat(metadataKeys.filter(function (x) {
233
- return !cborKeys.includes(x);
234
- }));
235
- var _iterator = _createForOfIteratorHelper(differentKeys),
236
- _step;
237
- try {
238
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
239
- var key = _step.value;
240
- var cborValue = metadataCbor[key];
241
- var metadataValue = metadata[key];
242
- response.invalidValues.push({
243
- key: key,
244
- cborValue: cborValue,
245
- metadataValue: metadataValue
246
- });
247
- }
248
-
249
- // Deep comparison of up to 5 keys
250
- } catch (err) {
251
- _iterator.e(err);
252
- } finally {
253
- _iterator.f();
254
- }
255
- var _iterator2 = _createForOfIteratorHelper(Object.keys(metadataCbor).slice(0, 5)),
256
- _step2;
257
- try {
258
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
259
- var fieldToValidate = _step2.value;
260
- var _cborValue = metadataCbor[fieldToValidate];
261
- var _metadataValue = metadata[fieldToValidate];
262
- if (!DeepEqual(_cborValue, _metadataValue)) {
263
- response.invalidValues.push({
264
- key: fieldToValidate,
265
- cborValue: _cborValue,
266
- metadataValue: _metadataValue
267
- });
268
- }
269
- }
270
- } catch (err) {
271
- _iterator2.e(err);
272
- } finally {
273
- _iterator2.f();
274
- }
275
- if (response.invalidValues.length !== 0) {
276
- response.valid = false;
277
- }
278
- return response;
279
- }
280
- };
281
- module.exports = ContentObjectVerification;
@@ -1,8 +0,0 @@
1
- var networks = {
2
- "main": "https://main.net955305.contentfabric.io",
3
- "demo": "https://demov3.net955210.contentfabric.io",
4
- "demov3": "https://demov3.net955210.contentfabric.io",
5
- "local": "http://127.0.0.1:8008/config?qspace=dev&self",
6
- "test": "https://test.net955203.contentfabric.io"
7
- };
8
- module.exports = networks;
@@ -1,45 +0,0 @@
1
- var LiveRecordingConfigDefault = {
2
- drm_type: "clear",
3
- recording_config: {
4
- part_ttl: 86400,
5
- reconnect_timeout: 3600,
6
- connection_timeout: 3600,
7
- copy_mpegts: false
8
- },
9
- profile: {
10
- ladder_specs: {
11
- audio: [{
12
- bit_rate: 192000,
13
- channels: 2,
14
- codecs: "mp4a.40.2"
15
- }, {
16
- bit_rate: 384000,
17
- channels: 6,
18
- codecs: "mp4a.40.2"
19
- }],
20
- video: [{
21
- bit_rate: 9500000,
22
- codecs: "avc1.640028,mp4a.40.2",
23
- height: 1080,
24
- width: 1920
25
- }, {
26
- bit_rate: 4500000,
27
- codecs: "avc1.640028,mp4a.40.2",
28
- height: 720,
29
- width: 1280
30
- }, {
31
- bit_rate: 2000000,
32
- codecs: "avc1.640028,mp4a.40.2",
33
- height: 540,
34
- width: 960
35
- }, {
36
- bit_rate: 900000,
37
- codecs: "avc1.640028,mp4a.40.2",
38
- height: 540,
39
- width: 960
40
- }]
41
- },
42
- name: "Default"
43
- }
44
- };
45
- module.exports = LiveRecordingConfigDefault;