@nomad-media/public 1.1.16 → 2026.9.2-beta.0
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/README.md +67 -93
- package/dist/nomad-media-sdk-debug.js +1310 -235
- package/dist/nomad-media-sdk.min.js +5 -5
- package/package.json +8 -2
|
@@ -1,3 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} SearchFilter
|
|
3
|
+
* @property {string} fieldName - The content field to filter on
|
|
4
|
+
* @property {string} operator - Comparison operator (e.g. "Equals", "Contains", "GreaterThan")
|
|
5
|
+
* @property {string|string[]} values - The value or array of values to match
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {Object} SortField
|
|
10
|
+
* @property {string} fieldName - The field to sort on
|
|
11
|
+
* @property {string} sortType - Sort direction: "Ascending" or "Descending"
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Object} SearchResultField
|
|
16
|
+
* @property {string} name - Field name to include in results
|
|
17
|
+
* @property {SearchResultField[]} [SearchResultFields] - Nested sub-fields for object fields
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {Object} TagRef
|
|
22
|
+
* @property {string} id - Tag ID
|
|
23
|
+
* @property {string} [description] - Tag description
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {Object} CollectionRef
|
|
28
|
+
* @property {string} id - Collection ID
|
|
29
|
+
* @property {string} [description] - Collection description
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @typedef {Object} RelatedContentRef
|
|
34
|
+
* @property {string} id - Related content ID
|
|
35
|
+
* @property {string} [description] - Description
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @typedef {Object} AudioTrack
|
|
40
|
+
* @property {string} id - Audio track ID
|
|
41
|
+
* @property {string} [name] - Track name
|
|
42
|
+
* @property {string} [language] - Language code
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @typedef {Object} ExternalOutputProfile
|
|
47
|
+
* @property {string} id - Output profile ID
|
|
48
|
+
* @property {string} [name] - Profile name
|
|
49
|
+
* @property {string} [profileType] - Profile type identifier
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @typedef {Object} LiveInputSource
|
|
54
|
+
* @property {string} url - Source stream URL
|
|
55
|
+
* @property {string} [id] - Source ID
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @typedef {Object} LiveInputDestination
|
|
60
|
+
* @property {string} url - Destination stream URL
|
|
61
|
+
* @property {string} [id] - Destination ID
|
|
62
|
+
* @property {string} [name] - Destination name
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @typedef {Object} LiveOutputProfile
|
|
67
|
+
* @property {string} id - Output profile ID
|
|
68
|
+
* @property {string} [name] - Profile name
|
|
69
|
+
* @property {string} [type] - Profile type
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @typedef {Object} MediaSource
|
|
74
|
+
* @property {string} assetId - The asset ID to include in the composition
|
|
75
|
+
* @property {string} startTimeCode - Start timecode (HH:MM:SS:FF)
|
|
76
|
+
* @property {string} endTimeCode - End timecode (HH:MM:SS:FF)
|
|
77
|
+
* @property {string} [id] - Source segment ID
|
|
78
|
+
*/
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @typedef {Object} MediaBuilderItem
|
|
82
|
+
* @property {string} sourceAssetId - The asset ID to use as the source for this media builder item
|
|
83
|
+
* @property {string} startTimeCode - Start timecode for the segment (HH:MM:SS:FF)
|
|
84
|
+
* @property {string} endTimeCode - End timecode for the segment (HH:MM:SS:FF)
|
|
85
|
+
*/
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @typedef {Object} Annotation
|
|
89
|
+
* @property {string} [id] - Annotation ID
|
|
90
|
+
* @property {string} startTimeCode - Start timecode for the annotation (HH:MM:SS:FF)
|
|
91
|
+
* @property {string} [endTimeCode] - End timecode for the annotation (HH:MM:SS:FF)
|
|
92
|
+
* @property {Object} properties - Annotation properties (e.g. { title: "..." })
|
|
93
|
+
*/
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @typedef {Object} ContentRef
|
|
97
|
+
* @property {string} id - Content ID
|
|
98
|
+
*/
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @typedef {Object} NomadUser
|
|
102
|
+
* @property {string} [id] - User ID
|
|
103
|
+
*/
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @typedef {Object} ExternalUser
|
|
107
|
+
* @property {string} email - External user email address
|
|
108
|
+
* @property {string} [id] - External user ID
|
|
109
|
+
* @property {string} [externalUserType] - Type of external user
|
|
110
|
+
*/
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @typedef {Object} ContentField
|
|
114
|
+
* @property {string} contentFieldId - Unique identifier of the content field
|
|
115
|
+
* @property {Object} properties - Field property definitions (title, type, etc.)
|
|
116
|
+
*/
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* @typedef {Object} Transfer
|
|
120
|
+
* @property {string} id - The transfer id (UUID), which cancel, requeue and progress calls address
|
|
121
|
+
* @property {integer} direction - Which way the asset moves: 1 = Upload, 2 = Download
|
|
122
|
+
* @property {string} transferStatus - Lifecycle state: one of "Queued", "Downloading", "Uploading", "Error", "Complete", "Cancelled"
|
|
123
|
+
* @property {string} [displayName] - A human readable label, normally the file name
|
|
124
|
+
* @property {number} [completePercentage] - Percent complete, 0 to 100, when known
|
|
125
|
+
* @property {integer} bytesTransferred - Bytes moved so far
|
|
126
|
+
* @property {integer} bytesTotal - Total size of the transfer, or 0 when not known up front
|
|
127
|
+
* @property {integer} attemptCount - How many times this transfer has been attempted
|
|
128
|
+
* @property {string} [errorMessage] - The failure reason. Only populated for a failed transfer
|
|
129
|
+
* @property {string} createdDate - When the record was created (ISO 8601)
|
|
130
|
+
* @property {string} [queueDate] - When the transfer was queued (ISO 8601)
|
|
131
|
+
* @property {string} [startTransferDate] - When bytes first started moving (ISO 8601)
|
|
132
|
+
* @property {string} [completeTransferDate] - When the transfer finished successfully (ISO 8601)
|
|
133
|
+
* @property {string} [cancelledDate] - When the transfer was cancelled (ISO 8601)
|
|
134
|
+
* @property {string} [errorDate] - When the transfer failed (ISO 8601)
|
|
135
|
+
* @property {string} [lastProgressDate] - When progress was last recorded (ISO 8601)
|
|
136
|
+
* @property {string} [assetId] - The asset this transfer concerns (UUID), in either direction
|
|
137
|
+
* @property {boolean} [downloadProxy] - Whether a download is taking the proxy rendition rather than the original. Download only
|
|
138
|
+
* @property {string} [relatedAssetId] - The asset this upload is a related file of (UUID). Upload only
|
|
139
|
+
* @property {string} [displayObjectKey] - The destination object key as shown to a user. Upload only
|
|
140
|
+
* @property {string} [assetName] - The display name of the asset. Download only
|
|
141
|
+
* @property {string} [assetUrl] - The internal URL of the asset in `{bucketName}::{objectKey}` format. Download only
|
|
142
|
+
* @property {string} [fullPreviewUrl] - A signed URL of the asset's thumbnail image, when one is available
|
|
143
|
+
*/
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @typedef {Object} UploadQueueItem
|
|
147
|
+
* @property {string} fileName - The file name, including its extension
|
|
148
|
+
* @property {integer} fileSize - The file size in bytes
|
|
149
|
+
* @property {string} [mimeType] - The MIME type, when known; otherwise inferred from the file name
|
|
150
|
+
* @property {string} [destinationFolderId] - The destination folder (UUID); omit for the configured default upload folder
|
|
151
|
+
* @property {integer} chunkSize - The multipart part size in bytes, e.g. 8388608
|
|
152
|
+
* @property {string} [uploadOverwriteOption] - How to treat an existing file: "continue", "replace" (default) or "cancel"
|
|
153
|
+
*/
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @typedef {Object} TransferList
|
|
157
|
+
* @property {Array<Transfer>} items - The page of transfers
|
|
158
|
+
* @property {integer} totalItemCount - The number of transfers matching the filters; approximate for very large histories
|
|
159
|
+
* @property {string} [nextPageOffset] - The opaque cursor to pass back as `cursor` for the next page. A null value means the end has been reached
|
|
160
|
+
* @property {boolean} hasItems - Whether the items collection contains any items
|
|
161
|
+
* @property {string} [message] - An optional message providing additional information about the result
|
|
162
|
+
*/
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @typedef {Object} TransferSummary
|
|
166
|
+
* @property {Object} [uploadCounts] - Per-status counts for uploads, keyed by status name (Queued, Downloading, Uploading, Error, Complete, Cancelled)
|
|
167
|
+
* @property {Object} [downloadCounts] - Per-status counts for downloads, keyed by status name
|
|
168
|
+
* @property {integer} activeCount - Number of transfers that have not reached a terminal state
|
|
169
|
+
* @property {integer} errorCount - Number of transfers that ended in failure
|
|
170
|
+
* @property {string} [changeToken] - A token that changes whenever any counted value changes; usable for If-None-Match polling
|
|
171
|
+
*/
|
|
172
|
+
|
|
1
173
|
// account
|
|
2
174
|
|
|
3
175
|
|
|
@@ -135,6 +307,10 @@
|
|
|
135
307
|
|
|
136
308
|
|
|
137
309
|
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
|
|
138
314
|
|
|
139
315
|
|
|
140
316
|
|
|
@@ -187,6 +363,20 @@
|
|
|
187
363
|
|
|
188
364
|
|
|
189
365
|
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
|
|
190
380
|
|
|
191
381
|
|
|
192
382
|
|
|
@@ -276,6 +466,10 @@
|
|
|
276
466
|
|
|
277
467
|
|
|
278
468
|
|
|
469
|
+
import axios from "axios";
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
|
|
279
473
|
/**
|
|
280
474
|
* @class NomadSDK
|
|
281
475
|
* @classdesc This class is used to interact with the Nomad API.
|
|
@@ -283,11 +477,16 @@
|
|
|
283
477
|
|
|
284
478
|
class NomadMediaSDK {
|
|
285
479
|
constructor(config = {}) {
|
|
286
|
-
if (NomadMediaSDK.instance)
|
|
480
|
+
if (NomadMediaSDK.instance)
|
|
287
481
|
{
|
|
288
482
|
return NomadMediaSDK.instance;
|
|
289
483
|
}
|
|
290
484
|
|
|
485
|
+
if (!config.serviceApiUrl || typeof config.serviceApiUrl !== "string")
|
|
486
|
+
{
|
|
487
|
+
throw new MissingCredentialsException(["serviceApiUrl"]);
|
|
488
|
+
}
|
|
489
|
+
|
|
291
490
|
this.config = config;
|
|
292
491
|
this.token = null;
|
|
293
492
|
this.refreshTokenVal = null;
|
|
@@ -296,42 +495,104 @@ class NomadMediaSDK {
|
|
|
296
495
|
this.id = null
|
|
297
496
|
this.debug = config.debugMode || false;
|
|
298
497
|
|
|
498
|
+
// SSO login params
|
|
499
|
+
this.ssoProvider = this.config.ssoProvider || null;
|
|
500
|
+
this.ssoCode = this.config.ssoCode || null;
|
|
501
|
+
this.ssoState = this.config.ssoState || null;
|
|
502
|
+
this.ssoSessionState = this.config.ssoSessionState || null;
|
|
503
|
+
this.ssoRedirectUrl = this.config.ssoRedirectUrl || null;
|
|
504
|
+
|
|
299
505
|
if (this.config.serviceApiUrl[this.config.serviceApiUrl.length - 1] === "/")
|
|
300
506
|
{
|
|
301
507
|
this.config.serviceApiUrl = this.config.serviceApiUrl.slice(0, -1);
|
|
302
508
|
}
|
|
303
509
|
|
|
510
|
+
if (this.ssoProvider || this.ssoCode || this.ssoState || this.ssoSessionState || this.ssoRedirectUrl)
|
|
511
|
+
{
|
|
512
|
+
const missingCredentials = [];
|
|
513
|
+
const hasRedirectUrl = !!this.ssoRedirectUrl;
|
|
514
|
+
const hasAllFields = this.ssoProvider && this.ssoCode && this.ssoState && this.ssoSessionState;
|
|
515
|
+
if (!hasRedirectUrl && !hasAllFields) {
|
|
516
|
+
if (!this.ssoProvider) missingCredentials.push("ssoProvider");
|
|
517
|
+
if (!this.ssoCode) missingCredentials.push("ssoCode");
|
|
518
|
+
if (!this.ssoState) missingCredentials.push("ssoState");
|
|
519
|
+
if (!this.ssoSessionState) missingCredentials.push("ssoSessionState");
|
|
520
|
+
|
|
521
|
+
throw new MissingCredentialsException(missingCredentials, "SSO login requires either ssoRedirectUrl or all of ssoProvider, ssoCode, ssoState, ssoSessionState");
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
else if (!this.config.apiKey)
|
|
525
|
+
{
|
|
526
|
+
const missingCredentials = [];
|
|
527
|
+
if (!this.config.username) missingCredentials.push("username");
|
|
528
|
+
if (!this.config.password && !this.config.apiKey) missingCredentials.push("password");
|
|
529
|
+
|
|
530
|
+
if (missingCredentials.length > 0) {
|
|
531
|
+
throw new MissingCredentialsException(missingCredentials);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
304
535
|
if (this.config.singleton) NomadMediaSDK.instance = this;
|
|
305
|
-
|
|
536
|
+
|
|
306
537
|
return this;
|
|
307
|
-
}
|
|
538
|
+
}
|
|
308
539
|
|
|
309
540
|
/**
|
|
310
541
|
* @function init
|
|
311
542
|
* @async
|
|
312
543
|
* @private
|
|
313
|
-
* @description Initializes the SDK. Logs in using the username and password from the config.
|
|
544
|
+
* @description Initializes the SDK. Logs in using the username and password from the config.
|
|
314
545
|
* Then schedules a token refresh for the duration set by the value returned in the login.
|
|
315
546
|
* @returns {Promise<void>} - A promise that resolves when the SDK is initialized.
|
|
316
547
|
* @throws {Error} - An error is thrown if the SDK fails to initialize.
|
|
317
548
|
*/
|
|
318
|
-
async _init()
|
|
549
|
+
async _init()
|
|
550
|
+
{
|
|
551
|
+
// Calls made at the same time on a fresh SDK share one login instead of each starting their own.
|
|
552
|
+
if (!this._initPromise)
|
|
553
|
+
{
|
|
554
|
+
this._initPromise = this._runInit().finally(() => { this._initPromise = null; });
|
|
555
|
+
}
|
|
556
|
+
return this._initPromise;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
async _runInit()
|
|
319
560
|
{
|
|
320
561
|
if (this.config.username && this.config.password)
|
|
321
562
|
{
|
|
563
|
+
if (!this.config.serviceApiUrl)
|
|
564
|
+
{
|
|
565
|
+
throw new MissingCredentialsException(["serviceApiUrl"]);
|
|
566
|
+
}
|
|
322
567
|
await this.login();
|
|
323
568
|
}
|
|
569
|
+
else if (this.ssoProvider || this.ssoCode || this.ssoState || this.ssoSessionState || this.ssoRedirectUrl)
|
|
570
|
+
{
|
|
571
|
+
await this.authenticateWithSSO();
|
|
572
|
+
}
|
|
324
573
|
}
|
|
325
|
-
|
|
574
|
+
|
|
326
575
|
// account functions
|
|
327
576
|
/**
|
|
328
577
|
* @function login
|
|
329
578
|
* @async
|
|
330
|
-
* @description
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
|
|
579
|
+
* @description Authenticate a user and obtain a JWT access token.
|
|
580
|
+
* Validates the user's credentials and, if successful, creates a new session and returns a JWT
|
|
581
|
+
* access token with a refresh token. Accounts that are disabled, expired, pending invitation,
|
|
582
|
+
* or pending approval will be rejected. Guest users must have a valid profile and permitted
|
|
583
|
+
* application access.
|
|
584
|
+
* @returns {Promise<Object>} Authentication response. User-identity fields (from the backend `AccountLoginResponseModel`): `id`, `userName`, `firstName`, `lastName`, `loginStatus`, `cookies`. Session/token fields consumed by the SDK: `token`, `refreshToken`, `expirationSeconds`, `userSessionId`. Note: there is no `userId` or `expiresIn` field; use `id` and `expirationSeconds` respectively.
|
|
585
|
+
* @throws {Error} 400 - The request body is null or missing required fields (UserName, Password)
|
|
586
|
+
* @throws {Error} 404 - Authentication failed — invalid credentials or user not found
|
|
587
|
+
* @throws {Error} 403 - User account is Disabled, Expired, PendingInvitation, or PendingApproval
|
|
588
|
+
* @example
|
|
589
|
+
* const response = await nomadSDK.login();
|
|
590
|
+
* console.log('Access Token:', response.token);
|
|
591
|
+
* console.log('User ID:', response.id);
|
|
592
|
+
* console.log('User Name:', response.userName);
|
|
593
|
+
*
|
|
594
|
+
* @see {@link https://docs.nomad-cms.com/api/account/login Admin API} POST /account/login
|
|
595
|
+
*/
|
|
335
596
|
async login()
|
|
336
597
|
{
|
|
337
598
|
|
|
@@ -339,17 +600,39 @@ class NomadMediaSDK {
|
|
|
339
600
|
|
|
340
601
|
try
|
|
341
602
|
{
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
603
|
+
// _login()'s underlying _sendRequest() now throws on any
|
|
604
|
+
// failure (e.g. a 409 "Invalid username or password") rather
|
|
605
|
+
// than resolving to a falsy value — but this method's own
|
|
606
|
+
// documented contract is to throw the specific, public
|
|
607
|
+
// LoginException on any login failure, not whatever generic
|
|
608
|
+
// error the transport layer happens to throw. Any failure here
|
|
609
|
+
// is treated as a login failure, matching this method's
|
|
610
|
+
// original behavior when _login() resolved to a falsy value
|
|
611
|
+
// for the same case.
|
|
612
|
+
let LOGIN_INFO;
|
|
613
|
+
let LOGIN_ERROR;
|
|
614
|
+
try
|
|
615
|
+
{
|
|
616
|
+
LOGIN_INFO = await _login(this);
|
|
617
|
+
}
|
|
618
|
+
catch (error)
|
|
619
|
+
{
|
|
620
|
+
LOGIN_INFO = null;
|
|
621
|
+
LOGIN_ERROR = error;
|
|
622
|
+
}
|
|
623
|
+
// The original error (network failure, 403 disabled account, ...) is kept
|
|
624
|
+
// on `.cause`, with its HTTP status on `.status`.
|
|
625
|
+
if (!LOGIN_INFO) throw new LoginException(undefined, LOGIN_ERROR);
|
|
626
|
+
|
|
345
627
|
this.token = LOGIN_INFO["token"];
|
|
346
628
|
this.refreshTokenVal = LOGIN_INFO["refreshToken"];
|
|
347
629
|
this.expirationSeconds = LOGIN_INFO["expirationSeconds"];
|
|
348
630
|
this.userSessionId = LOGIN_INFO["userSessionId"];
|
|
349
631
|
this.id = LOGIN_INFO["id"];
|
|
350
632
|
_printDatetime(`Login successful`);
|
|
633
|
+
return LOGIN_INFO;
|
|
351
634
|
}
|
|
352
|
-
catch (error)
|
|
635
|
+
catch (error)
|
|
353
636
|
{
|
|
354
637
|
_printDatetime(`Login failed`);
|
|
355
638
|
throw error;
|
|
@@ -359,9 +642,16 @@ class NomadMediaSDK {
|
|
|
359
642
|
/**
|
|
360
643
|
* @function logout
|
|
361
644
|
* @async
|
|
362
|
-
* @description
|
|
363
|
-
*
|
|
364
|
-
*
|
|
645
|
+
* @description Log out the currently authenticated user and end their session.
|
|
646
|
+
* Ends the user's active session and invalidates the authentication token. If a UserSessionId
|
|
647
|
+
* is provided, that specific session is terminated.
|
|
648
|
+
* @returns {Promise<Object>} Confirmation that the user was successfully logged out and the session was deleted
|
|
649
|
+
* @throws {Error} 401 - The caller is not authenticated
|
|
650
|
+
* @example
|
|
651
|
+
* await nomadSDK.logout();
|
|
652
|
+
* console.log('User logged out successfully');
|
|
653
|
+
*
|
|
654
|
+
* @see {@link https://docs.nomad-cms.com/api/account/logout Admin API} POST /account/logout
|
|
365
655
|
*/
|
|
366
656
|
async logout()
|
|
367
657
|
{
|
|
@@ -374,14 +664,15 @@ class NomadMediaSDK {
|
|
|
374
664
|
|
|
375
665
|
try
|
|
376
666
|
{
|
|
377
|
-
await _logout(this);
|
|
667
|
+
const RESULT = await _logout(this);
|
|
378
668
|
this.token = null;
|
|
379
669
|
this.refreshTokenVal = null;
|
|
380
670
|
this.expirationSeconds = null;
|
|
381
671
|
this.userSessionId = null;
|
|
382
672
|
this.id = null;
|
|
383
|
-
|
|
673
|
+
|
|
384
674
|
_printDatetime(`Logout successful`);
|
|
675
|
+
return RESULT;
|
|
385
676
|
}
|
|
386
677
|
catch (error)
|
|
387
678
|
{
|
|
@@ -393,11 +684,20 @@ class NomadMediaSDK {
|
|
|
393
684
|
/**
|
|
394
685
|
* @function refreshToken
|
|
395
686
|
* @async
|
|
396
|
-
* @description
|
|
397
|
-
*
|
|
398
|
-
*
|
|
399
|
-
* @
|
|
400
|
-
|
|
687
|
+
* @description Retrieve a new JWT access token using a refresh token.
|
|
688
|
+
* Exchanges a valid refresh token for a new JWT access token. Automatically updates the SDK
|
|
689
|
+
* instance's token property on success.
|
|
690
|
+
* @returns {Promise<boolean>} True if the token was successfully refreshed, false otherwise
|
|
691
|
+
* @throws {Error} 400 - The request body is null or RefreshToken is missing
|
|
692
|
+
* @throws {Error} 404 - The refresh token is invalid or has expired
|
|
693
|
+
* @example
|
|
694
|
+
* const success = await nomadSDK.refreshToken();
|
|
695
|
+
* if (success) {
|
|
696
|
+
* console.log('Token refreshed successfully');
|
|
697
|
+
* }
|
|
698
|
+
*
|
|
699
|
+
* @see {@link https://docs.nomad-cms.com/api/account/refresh-token Admin API} POST /account/refresh-token
|
|
700
|
+
*/
|
|
401
701
|
async refreshToken()
|
|
402
702
|
{
|
|
403
703
|
_printDatetime(`Refreshing token`);
|
|
@@ -411,6 +711,7 @@ class NomadMediaSDK {
|
|
|
411
711
|
{
|
|
412
712
|
const REFRESHED = await _refreshToken(this);
|
|
413
713
|
if (REFRESHED) _printDatetime(`Token refresh successful`);
|
|
714
|
+
return REFRESHED;
|
|
414
715
|
}
|
|
415
716
|
catch (error)
|
|
416
717
|
{
|
|
@@ -419,15 +720,59 @@ class NomadMediaSDK {
|
|
|
419
720
|
}
|
|
420
721
|
}
|
|
421
722
|
|
|
723
|
+
/**
|
|
724
|
+
* @function authenticateWithSSO
|
|
725
|
+
* @async
|
|
726
|
+
* @description Authenticates the user using SSO.
|
|
727
|
+
* @throws {LoginException} - If SSO authentication fails.
|
|
728
|
+
*/
|
|
729
|
+
async authenticateWithSSO()
|
|
730
|
+
{
|
|
731
|
+
if (!this.ssoRedirectUrl)
|
|
732
|
+
{
|
|
733
|
+
this.ssoRedirectUrl = `${this.config.serviceApiUrl}/api/auth/${this.ssoProvider}/callback?code=${this.ssoCode}&state=${this.ssoState}&session_state=${this.ssoSessionState}`;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
try
|
|
737
|
+
{
|
|
738
|
+
const response = await axios.get(this.ssoRedirectUrl, {
|
|
739
|
+
maxRedirects: 0,
|
|
740
|
+
timeout: 10000
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
throw new LoginException(`SSO authentication failed with status code: ${response.status}`);
|
|
744
|
+
}
|
|
745
|
+
catch (error)
|
|
746
|
+
{
|
|
747
|
+
if (error.response && error.response.status === 302) {
|
|
748
|
+
const location = error.response.headers['location'];
|
|
749
|
+
if (!location) {
|
|
750
|
+
throw new LoginException('SSO authentication failed: Missing location header in redirect.');
|
|
751
|
+
}
|
|
752
|
+
this.token = location.split("azurePortal_token=")[1]?.split("&")[0] || null;
|
|
753
|
+
this.refreshTokenVal = location.split("azurePortal_refresh_token=")[1]?.split("&")[0] || null;
|
|
754
|
+
this.expirationSeconds = location.split("expires=")[1]?.split("&")[0] || null;
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
throw new LoginException(`SSO config parameters are invalid: ${error.message}`);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
422
761
|
/**
|
|
423
762
|
* @function forgotPassword
|
|
424
763
|
* @async
|
|
425
|
-
* @description
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
* @
|
|
430
|
-
|
|
764
|
+
* @description Trigger a password reset code to be sent to the user's email address.
|
|
765
|
+
* Initiates the password reset flow for the specified user account. If the account is active, a
|
|
766
|
+
* 6-digit verification code is sent to the user's email address. The code should then be
|
|
767
|
+
* submitted to the reset-password endpoint along with the new password.
|
|
768
|
+
* @returns {Promise<Object>} Response indicating the reset code was sent, or a pending account state
|
|
769
|
+
* @throws {Error} 400 - The request is null or UserName is missing
|
|
770
|
+
* @example
|
|
771
|
+
* const result = await nomadSDK.forgotPassword();
|
|
772
|
+
* console.log('Password reset code sent to email');
|
|
773
|
+
*
|
|
774
|
+
* @see {@link https://docs.nomad-cms.com/api/account/forgot-password Admin API} POST /account/forgot-password
|
|
775
|
+
*/
|
|
431
776
|
async forgotPassword()
|
|
432
777
|
{
|
|
433
778
|
const USERNAME = this.config.username;
|
|
@@ -436,8 +781,9 @@ class NomadMediaSDK {
|
|
|
436
781
|
|
|
437
782
|
try
|
|
438
783
|
{
|
|
439
|
-
await _forgotPassword(this);
|
|
784
|
+
const RESULT = await _forgotPassword(this);
|
|
440
785
|
_printDatetime(`Code sent to ${USERNAME}`);
|
|
786
|
+
return RESULT;
|
|
441
787
|
}
|
|
442
788
|
catch (error)
|
|
443
789
|
{
|
|
@@ -449,14 +795,20 @@ class NomadMediaSDK {
|
|
|
449
795
|
/**
|
|
450
796
|
* @function resetPassword
|
|
451
797
|
* @async
|
|
452
|
-
* @description
|
|
453
|
-
*
|
|
454
|
-
*
|
|
455
|
-
*
|
|
456
|
-
* @
|
|
457
|
-
*
|
|
458
|
-
* @
|
|
459
|
-
|
|
798
|
+
* @description Reset the user's password using a verification code from the forgot-password email.
|
|
799
|
+
* Completes the password reset flow by validating the verification code from the
|
|
800
|
+
* forgot-password email and setting the new password. For accounts pending approval, this also
|
|
801
|
+
* completes the email verification step.
|
|
802
|
+
* @param {string} CODE - The 6-digit verification code from the forgot-password email
|
|
803
|
+
* @param {string} NEW_PASSWORD - The new password to set for the user
|
|
804
|
+
* @returns {Promise<Object>} Response confirming the password was successfully reset
|
|
805
|
+
* @throws {Error} 400 - The request is null, UserName is missing, reset token is invalid/expired, or user is disabled
|
|
806
|
+
* @example
|
|
807
|
+
* const result = await nomadSDK.resetPassword('123456', 'newSecurePassword!');
|
|
808
|
+
* console.log('Password reset successfully');
|
|
809
|
+
*
|
|
810
|
+
* @see {@link https://docs.nomad-cms.com/api/account/reset-password Admin API} POST /account/reset-password
|
|
811
|
+
*/
|
|
460
812
|
async resetPassword(CODE, NEW_PASSWORD)
|
|
461
813
|
{
|
|
462
814
|
const USERNAME = this.config.username;
|
|
@@ -465,8 +817,9 @@ class NomadMediaSDK {
|
|
|
465
817
|
|
|
466
818
|
try
|
|
467
819
|
{
|
|
468
|
-
await _resetPassword(this, CODE, NEW_PASSWORD);
|
|
820
|
+
const RESULT = await _resetPassword(this, CODE, NEW_PASSWORD);
|
|
469
821
|
_printDatetime(`Password reset for ${USERNAME}`);
|
|
822
|
+
return RESULT;
|
|
470
823
|
}
|
|
471
824
|
catch (error)
|
|
472
825
|
{
|
|
@@ -479,14 +832,16 @@ class NomadMediaSDK {
|
|
|
479
832
|
/**
|
|
480
833
|
* @function clearContinueWatching
|
|
481
834
|
* @async
|
|
482
|
-
* @description
|
|
483
|
-
*
|
|
484
|
-
*
|
|
485
|
-
* @param {string
|
|
486
|
-
*
|
|
487
|
-
* @
|
|
488
|
-
* @
|
|
489
|
-
*
|
|
835
|
+
* @description Clear a "continue watching" entry for a user.
|
|
836
|
+
* Removes a specific asset from the user's continue watching list.
|
|
837
|
+
* @param {string} USER_ID - The unique identifier of the user
|
|
838
|
+
* @param {string} [ASSET_ID] - The asset ID to remove from continue watching
|
|
839
|
+
* @returns {Promise<Object>} Response confirming the clearing
|
|
840
|
+
* @throws {Error} 401 - The caller is not authenticated
|
|
841
|
+
* @example
|
|
842
|
+
* await nomadSDK.clearContinueWatching("user-123", "asset-456");
|
|
843
|
+
*
|
|
844
|
+
* @see {@link https://docs.nomad-cms.com/docs/api-guide/media-api/clear-continue-watching Portal API} POST /media/clear-watching
|
|
490
845
|
*/
|
|
491
846
|
async clearContinueWatching(USER_ID, ASSET_ID)
|
|
492
847
|
{
|
|
@@ -495,7 +850,7 @@ class NomadMediaSDK {
|
|
|
495
850
|
await this._init();
|
|
496
851
|
}
|
|
497
852
|
|
|
498
|
-
if (this.config.apiType !== "portal")
|
|
853
|
+
if (this.config.apiType !== "portal")
|
|
499
854
|
{
|
|
500
855
|
throw new Error("This function is only available for portal API type.");
|
|
501
856
|
}
|
|
@@ -504,8 +859,9 @@ class NomadMediaSDK {
|
|
|
504
859
|
|
|
505
860
|
try
|
|
506
861
|
{
|
|
507
|
-
await _clearContinueWatching(this, USER_ID, ASSET_ID);
|
|
862
|
+
const RESULT = await _clearContinueWatching(this, USER_ID, ASSET_ID);
|
|
508
863
|
_printDatetime(`Continue watching cleared`);
|
|
864
|
+
return RESULT;
|
|
509
865
|
}
|
|
510
866
|
catch (error)
|
|
511
867
|
{
|
|
@@ -517,12 +873,15 @@ class NomadMediaSDK {
|
|
|
517
873
|
/**
|
|
518
874
|
* @function clearWatchlist
|
|
519
875
|
* @async
|
|
520
|
-
* @description
|
|
521
|
-
*
|
|
522
|
-
*
|
|
523
|
-
* @returns {Promise<
|
|
524
|
-
* @throws {Error} -
|
|
525
|
-
* @
|
|
876
|
+
* @description Clear the user's entire watchlist.
|
|
877
|
+
* Removes all items from the specified user's watchlist.
|
|
878
|
+
* @param {string} USER_ID - The unique identifier of the user whose watchlist to clear
|
|
879
|
+
* @returns {Promise<Object>} Response confirming the watchlist was cleared
|
|
880
|
+
* @throws {Error} 401 - The caller is not authenticated
|
|
881
|
+
* @example
|
|
882
|
+
* await nomadSDK.clearWatchlist("user-123");
|
|
883
|
+
*
|
|
884
|
+
* @see {@link https://docs.nomad-cms.com/docs/api-guide/media-api/clear-watchlist Portal API} POST /media/clear-watchlist
|
|
526
885
|
*/
|
|
527
886
|
async clearWatchlist(USER_ID)
|
|
528
887
|
{
|
|
@@ -532,7 +891,7 @@ class NomadMediaSDK {
|
|
|
532
891
|
}
|
|
533
892
|
|
|
534
893
|
if (this.config.apiType !== "portal")
|
|
535
|
-
{
|
|
894
|
+
{
|
|
536
895
|
throw new Error("This function is only available for portal API type.");
|
|
537
896
|
}
|
|
538
897
|
|
|
@@ -542,8 +901,9 @@ class NomadMediaSDK {
|
|
|
542
901
|
{
|
|
543
902
|
USER_ID = USER_ID || this.id;
|
|
544
903
|
|
|
545
|
-
await _clearWatchlist(this, USER_ID);
|
|
904
|
+
const RESULT = await _clearWatchlist(this, USER_ID);
|
|
546
905
|
_printDatetime(`Watchlist cleared`);
|
|
906
|
+
return RESULT;
|
|
547
907
|
}
|
|
548
908
|
catch (error)
|
|
549
909
|
{
|
|
@@ -555,16 +915,20 @@ class NomadMediaSDK {
|
|
|
555
915
|
/**
|
|
556
916
|
* @function createForm
|
|
557
917
|
* @async
|
|
558
|
-
* @description
|
|
559
|
-
*
|
|
560
|
-
*
|
|
561
|
-
* @param {
|
|
562
|
-
*
|
|
563
|
-
* @returns {Promise<
|
|
564
|
-
*
|
|
565
|
-
* @
|
|
566
|
-
*
|
|
567
|
-
|
|
918
|
+
* @description Create a form submission for a content definition.
|
|
919
|
+
* Submits form data to create a new entry for the specified content definition. Used for
|
|
920
|
+
* user-generated content submissions through portal forms.
|
|
921
|
+
* @param {string} CONTENT_DEFINITION_ID - The content definition ID for the form type
|
|
922
|
+
* @param {Object} FORM_INFO - The form data to submit
|
|
923
|
+
* @returns {Promise<Object>} The created content entry
|
|
924
|
+
* @throws {Error} 401 - The caller is not authenticated
|
|
925
|
+
* @example
|
|
926
|
+
* const entry = await nomadSDK.createForm("def-123", {
|
|
927
|
+
* title: "My Submission", description: "Description"
|
|
928
|
+
* });
|
|
929
|
+
*
|
|
930
|
+
* @see {@link https://docs.nomad-cms.com/docs/api-guide/media-api/create-form Portal API} POST /media/form/{contentDefinitionId}
|
|
931
|
+
*/
|
|
568
932
|
async createForm(CONTENT_DEFINITION_ID, FORM_INFO)
|
|
569
933
|
{
|
|
570
934
|
if (this.token === null && this.config.username)
|
|
@@ -595,14 +959,17 @@ class NomadMediaSDK {
|
|
|
595
959
|
/**
|
|
596
960
|
* @function getContentCookies
|
|
597
961
|
* @async
|
|
598
|
-
* @description
|
|
599
|
-
*
|
|
600
|
-
*
|
|
601
|
-
*
|
|
602
|
-
* @returns {Promise<
|
|
603
|
-
*
|
|
604
|
-
* @throws {Error} -
|
|
605
|
-
* @
|
|
962
|
+
* @description Retrieve secure streaming cookies for portal media content.
|
|
963
|
+
* Gets signed cookies required for secure streaming of media content through the portal API.
|
|
964
|
+
* The cookies authorize CDN access to the media files.
|
|
965
|
+
* @param {string} CONTENT_ID - The unique identifier of the media content
|
|
966
|
+
* @returns {Promise<Object>} Streaming cookies and manifest URL for the content
|
|
967
|
+
* @throws {Error} 401 - The caller is not authenticated
|
|
968
|
+
* @throws {Error} 404 - The content was not found
|
|
969
|
+
* @example
|
|
970
|
+
* const cookies = await nomadSDK.getContentCookies("media-123");
|
|
971
|
+
*
|
|
972
|
+
* @see {@link https://docs.nomad-cms.com/docs/api-guide/asset-api-1 Portal API} GET /media/set-cookies/{id}
|
|
606
973
|
*/
|
|
607
974
|
async getContentCookies(CONTENT_ID)
|
|
608
975
|
{
|
|
@@ -634,11 +1001,15 @@ class NomadMediaSDK {
|
|
|
634
1001
|
/**
|
|
635
1002
|
* @function getDefaultSiteConfig
|
|
636
1003
|
* @async
|
|
637
|
-
* @description
|
|
638
|
-
*
|
|
639
|
-
*
|
|
640
|
-
* @
|
|
641
|
-
* @throws {Error} -
|
|
1004
|
+
* @description Retrieve the default site configuration.
|
|
1005
|
+
* Gets the default portal site configuration used when no specific configuration ID is
|
|
1006
|
+
* provided.
|
|
1007
|
+
* @returns {Promise<Object>} The default site configuration object
|
|
1008
|
+
* @throws {Error} 401 - The caller is not authenticated
|
|
1009
|
+
* @example
|
|
1010
|
+
* const config = await nomadSDK.getDefaultSiteConfig();
|
|
1011
|
+
*
|
|
1012
|
+
* @see {@link https://docs.nomad-cms.com/docs/api-guide/media-api/get-default-site-config Portal API} GET /media/config
|
|
642
1013
|
*/
|
|
643
1014
|
async getDefaultSiteConfig()
|
|
644
1015
|
{
|
|
@@ -670,12 +1041,17 @@ class NomadMediaSDK {
|
|
|
670
1041
|
/**
|
|
671
1042
|
* @function getDynamicContent
|
|
672
1043
|
* @async
|
|
673
|
-
* @description
|
|
674
|
-
*
|
|
675
|
-
*
|
|
676
|
-
*
|
|
677
|
-
* @
|
|
678
|
-
* @throws {Error} -
|
|
1044
|
+
* @description Retrieve a dynamic content item by its unique identifier.
|
|
1045
|
+
* Gets a dynamically generated content item used for personalized or rule-based content display
|
|
1046
|
+
* on the portal.
|
|
1047
|
+
* @param {string} DYNAMIC_CONTENT_RECORD_ID - The unique identifier of the dynamic content
|
|
1048
|
+
* @returns {Promise<Object>} The dynamic content object with its configuration and items
|
|
1049
|
+
* @throws {Error} 401 - The caller is not authenticated
|
|
1050
|
+
* @throws {Error} 404 - The dynamic content was not found
|
|
1051
|
+
* @example
|
|
1052
|
+
* const content = await nomadSDK.getDynamicContent("dynamic-123");
|
|
1053
|
+
*
|
|
1054
|
+
* @see {@link https://docs.nomad-cms.com/docs/api-guide/media-api/get-dynamic-content Portal API} GET /media/content/{id}
|
|
679
1055
|
*/
|
|
680
1056
|
async getDynamicContent(DYNAMIC_CONTENT_RECORD_ID)
|
|
681
1057
|
{
|
|
@@ -702,11 +1078,15 @@ class NomadMediaSDK {
|
|
|
702
1078
|
/**
|
|
703
1079
|
* @function getDynamicContents
|
|
704
1080
|
* @async
|
|
705
|
-
* @description
|
|
706
|
-
*
|
|
707
|
-
*
|
|
708
|
-
* @throws {Error} -
|
|
709
|
-
* @
|
|
1081
|
+
* @description Retrieve all dynamic content items.
|
|
1082
|
+
* Gets the list of all dynamic content configurations available for the portal front-end.
|
|
1083
|
+
* @returns {Promise<Object>} Array of dynamic content objects
|
|
1084
|
+
* @throws {Error} 401 - The caller is not authenticated
|
|
1085
|
+
* @example
|
|
1086
|
+
* const contents = await nomadSDK.getDynamicContents();
|
|
1087
|
+
* contents.forEach(c => console.log(c.id, c.name));
|
|
1088
|
+
*
|
|
1089
|
+
* @see {@link https://docs.nomad-cms.com/docs/api-guide/media-api/get-dynamic-contents Portal API} GET /media/content
|
|
710
1090
|
*/
|
|
711
1091
|
async getDynamicContents()
|
|
712
1092
|
{
|
|
@@ -738,15 +1118,21 @@ class NomadMediaSDK {
|
|
|
738
1118
|
/**
|
|
739
1119
|
* @function getMediaGroup
|
|
740
1120
|
* @async
|
|
741
|
-
* @description
|
|
742
|
-
*
|
|
743
|
-
*
|
|
744
|
-
* @
|
|
745
|
-
*
|
|
746
|
-
* @
|
|
747
|
-
* @
|
|
1121
|
+
* @description Retrieve a media group by its unique identifier.
|
|
1122
|
+
* Gets a media group and its contents with optional content filtering. Media groups organize
|
|
1123
|
+
* content items for portal display.
|
|
1124
|
+
* @param {string} MEDIA_GROUP_ID - The unique identifier of the media group
|
|
1125
|
+
* @param {Array<string>} [FILTER_IDS] - Optional array of content IDs to filter results
|
|
1126
|
+
* @param {string} [LANGUAGE_ID] - Optional language ID to localize the response
|
|
1127
|
+
* @returns {Promise<Object>} The media group object with its contents
|
|
1128
|
+
* @throws {Error} 401 - The caller is not authenticated
|
|
1129
|
+
* @throws {Error} 404 - The media group was not found
|
|
1130
|
+
* @example
|
|
1131
|
+
* const group = await nomadSDK.getMediaGroup("group-123", ["item-1", "item-2"]);
|
|
1132
|
+
*
|
|
1133
|
+
* @see {@link https://docs.nomad-cms.com/docs/api-guide/media-api/get-media-group Portal API} GET /media/group/{id}
|
|
748
1134
|
*/
|
|
749
|
-
async getMediaGroup(MEDIA_GROUP_ID, FILTER_IDS)
|
|
1135
|
+
async getMediaGroup(MEDIA_GROUP_ID, FILTER_IDS, LANGUAGE_ID)
|
|
750
1136
|
{
|
|
751
1137
|
if (this.token === null && this.config.username)
|
|
752
1138
|
{
|
|
@@ -762,7 +1148,7 @@ class NomadMediaSDK {
|
|
|
762
1148
|
|
|
763
1149
|
try
|
|
764
1150
|
{
|
|
765
|
-
const GET_MEDIA_GROUP_INFO = await _getMediaGroup(this, MEDIA_GROUP_ID, FILTER_IDS);
|
|
1151
|
+
const GET_MEDIA_GROUP_INFO = await _getMediaGroup(this, MEDIA_GROUP_ID, FILTER_IDS, LANGUAGE_ID);
|
|
766
1152
|
if (GET_MEDIA_GROUP_INFO) _printDatetime(`Media group gotten: ${MEDIA_GROUP_ID}`);
|
|
767
1153
|
return GET_MEDIA_GROUP_INFO;
|
|
768
1154
|
}
|
|
@@ -776,12 +1162,18 @@ class NomadMediaSDK {
|
|
|
776
1162
|
/**
|
|
777
1163
|
* @function getMediaItem
|
|
778
1164
|
* @async
|
|
779
|
-
* @description
|
|
780
|
-
*
|
|
781
|
-
*
|
|
782
|
-
*
|
|
783
|
-
* @
|
|
784
|
-
* @throws {Error} -
|
|
1165
|
+
* @description Retrieve a media item by its unique identifier.
|
|
1166
|
+
* Gets the full media item details including metadata, content definitions, and playback
|
|
1167
|
+
* information for the portal front-end.
|
|
1168
|
+
* @param {string} MEDIA_ITEM_ID - The unique identifier of the media item
|
|
1169
|
+
* @returns {Promise<Object>} The media item object with metadata and content details
|
|
1170
|
+
* @throws {Error} 401 - The caller is not authenticated
|
|
1171
|
+
* @throws {Error} 404 - The media item was not found
|
|
1172
|
+
* @example
|
|
1173
|
+
* const item = await nomadSDK.getMediaItem("item-123");
|
|
1174
|
+
* console.log(item.title);
|
|
1175
|
+
*
|
|
1176
|
+
* @see {@link https://docs.nomad-cms.com/docs/api-guide/media-api/get-media-item Portal API} GET /media/item/{id}
|
|
785
1177
|
*/
|
|
786
1178
|
async getMediaItem(MEDIA_ITEM_ID)
|
|
787
1179
|
{
|
|
@@ -813,13 +1205,14 @@ class NomadMediaSDK {
|
|
|
813
1205
|
/**
|
|
814
1206
|
* @function getMyContent
|
|
815
1207
|
* @async
|
|
816
|
-
* @description
|
|
817
|
-
*
|
|
818
|
-
*
|
|
819
|
-
*
|
|
820
|
-
* @
|
|
821
|
-
*
|
|
822
|
-
*
|
|
1208
|
+
* @description Retrieve the authenticated user's personal content.
|
|
1209
|
+
* Gets all content items owned by or assigned to the currently authenticated user.
|
|
1210
|
+
* @returns {Promise<Object>} Array of content items belonging to the user
|
|
1211
|
+
* @throws {Error} 401 - The caller is not authenticated
|
|
1212
|
+
* @example
|
|
1213
|
+
* const myContent = await nomadSDK.getMyContent();
|
|
1214
|
+
*
|
|
1215
|
+
* @see {@link https://docs.nomad-cms.com/docs/api-guide/media-api/get-my-content Portal API} GET /media/my-content
|
|
823
1216
|
*/
|
|
824
1217
|
async getMyContent()
|
|
825
1218
|
{
|
|
@@ -830,7 +1223,7 @@ class NomadMediaSDK {
|
|
|
830
1223
|
|
|
831
1224
|
if (this.config.apiType !== "portal")
|
|
832
1225
|
{
|
|
833
|
-
throw new Error("This function is
|
|
1226
|
+
throw new Error("This function is only available for portal API type.");
|
|
834
1227
|
}
|
|
835
1228
|
|
|
836
1229
|
_printDatetime(`Getting my content`);
|
|
@@ -851,25 +1244,36 @@ class NomadMediaSDK {
|
|
|
851
1244
|
/**
|
|
852
1245
|
* @function getMyGroup
|
|
853
1246
|
* @async
|
|
854
|
-
* @description
|
|
855
|
-
*
|
|
856
|
-
*
|
|
857
|
-
*
|
|
858
|
-
* @
|
|
859
|
-
* @
|
|
1247
|
+
* @description Retrieve a personal content group by its unique identifier.
|
|
1248
|
+
* Gets a content group from the user's personal groups, including its contents and
|
|
1249
|
+
* configuration.
|
|
1250
|
+
* @param {string} GROUP_ID - The unique identifier of the personal group
|
|
1251
|
+
* @param {Array<string>} [FILTER_IDS] - Optional array of content IDs to filter results
|
|
1252
|
+
* @returns {Promise<Object>} The personal group object with its contents
|
|
1253
|
+
* @throws {Error} 401 - The caller is not authenticated
|
|
1254
|
+
* @throws {Error} 404 - The group was not found
|
|
1255
|
+
* @example
|
|
1256
|
+
* const group = await nomadSDK.getMyGroup("group-123");
|
|
1257
|
+
*
|
|
1258
|
+
* @see {@link https://docs.nomad-cms.com/docs/api-guide/media-api/get-my-group Portal API} GET /media/my-group/{id}
|
|
860
1259
|
*/
|
|
861
|
-
async getMyGroup(GROUP_ID)
|
|
1260
|
+
async getMyGroup(GROUP_ID, FILTER_IDS)
|
|
862
1261
|
{
|
|
863
1262
|
if (this.token === null && this.config.username)
|
|
864
1263
|
{
|
|
865
1264
|
await this._init();
|
|
866
1265
|
}
|
|
867
1266
|
|
|
1267
|
+
if (this.config.apiType !== "portal")
|
|
1268
|
+
{
|
|
1269
|
+
throw new Error("This function is only available for portal API type.");
|
|
1270
|
+
}
|
|
1271
|
+
|
|
868
1272
|
_printDatetime(`Getting my group: ${GROUP_ID}`);
|
|
869
1273
|
|
|
870
1274
|
try
|
|
871
1275
|
{
|
|
872
|
-
const GET_MY_GROUP_INFO = await _getMyGroup(this, GROUP_ID);
|
|
1276
|
+
const GET_MY_GROUP_INFO = await _getMyGroup(this, GROUP_ID, FILTER_IDS);
|
|
873
1277
|
if (GET_MY_GROUP_INFO) _printDatetime(`My group gotten: ${GROUP_ID}`);
|
|
874
1278
|
return GET_MY_GROUP_INFO;
|
|
875
1279
|
}
|
|
@@ -883,12 +1287,17 @@ class NomadMediaSDK {
|
|
|
883
1287
|
/**
|
|
884
1288
|
* @function getSiteConfig
|
|
885
1289
|
* @async
|
|
886
|
-
* @description
|
|
887
|
-
*
|
|
888
|
-
*
|
|
889
|
-
*
|
|
890
|
-
* @
|
|
891
|
-
* @throws {Error} -
|
|
1290
|
+
* @description Retrieve a site configuration by its unique identifier.
|
|
1291
|
+
* Gets the portal site configuration including theme, layout, navigation, and content display
|
|
1292
|
+
* settings.
|
|
1293
|
+
* @param {string} SITE_CONFIG_RECORD_ID - The unique identifier of the site configuration
|
|
1294
|
+
* @returns {Promise<Object>} The site configuration object
|
|
1295
|
+
* @throws {Error} 401 - The caller is not authenticated
|
|
1296
|
+
* @throws {Error} 404 - The site configuration was not found
|
|
1297
|
+
* @example
|
|
1298
|
+
* const config = await nomadSDK.getSiteConfig("config-123");
|
|
1299
|
+
*
|
|
1300
|
+
* @see {@link https://docs.nomad-cms.com/docs/api-guide/media-api/get-site-config Portal API} GET /media/config/{id}
|
|
892
1301
|
*/
|
|
893
1302
|
async getSiteConfig(SITE_CONFIG_RECORD_ID)
|
|
894
1303
|
{
|
|
@@ -897,6 +1306,11 @@ class NomadMediaSDK {
|
|
|
897
1306
|
await this._init();
|
|
898
1307
|
}
|
|
899
1308
|
|
|
1309
|
+
if (this.config.apiType !== "portal")
|
|
1310
|
+
{
|
|
1311
|
+
throw new Error("This function is only available for portal API type.");
|
|
1312
|
+
}
|
|
1313
|
+
|
|
900
1314
|
_printDatetime(`Getting site config: ${SITE_CONFIG_RECORD_ID}`);
|
|
901
1315
|
|
|
902
1316
|
try
|
|
@@ -915,18 +1329,23 @@ class NomadMediaSDK {
|
|
|
915
1329
|
/**
|
|
916
1330
|
* @function mediaSearch
|
|
917
1331
|
* @async
|
|
918
|
-
* @description
|
|
919
|
-
*
|
|
920
|
-
*
|
|
921
|
-
* @param {
|
|
922
|
-
*
|
|
923
|
-
* @param {
|
|
924
|
-
* @param {number
|
|
925
|
-
* @
|
|
926
|
-
*
|
|
927
|
-
* @throws {Error} -
|
|
928
|
-
* @
|
|
929
|
-
|
|
1332
|
+
* @description Search for media content using a query string and optional filters.
|
|
1333
|
+
* Performs a search against the media index with support for pagination, sorting, and ID
|
|
1334
|
+
* filtering. Defaults to page offset 0 and page size 100.
|
|
1335
|
+
* @param {Object} QUERY - The search query object containing search criteria
|
|
1336
|
+
* @param {Array<string>} [IDS] - Optional array of specific content IDs to filter by
|
|
1337
|
+
* @param {Array<Object>} [SORT_FIELDS] - Optional array of sort field objects
|
|
1338
|
+
* @param {number} [OFFSET] - The page offset for pagination (defaults to 0)
|
|
1339
|
+
* @param {number} [SIZE] - The number of results per page (defaults to 100)
|
|
1340
|
+
* @returns {Promise<Object>} Search results containing items array and pagination info
|
|
1341
|
+
* @throws {Error} 401 - The caller is not authenticated
|
|
1342
|
+
* @example
|
|
1343
|
+
* const results = await nomadSDK.mediaSearch(
|
|
1344
|
+
* { query: "news" }, null, [{ fieldName: "date", order: "desc" }], 0, 25
|
|
1345
|
+
* );
|
|
1346
|
+
*
|
|
1347
|
+
* @see {@link https://docs.nomad-cms.com/docs/api-guide/media-api/media-search Portal API} POST /media/search
|
|
1348
|
+
*/
|
|
930
1349
|
async mediaSearch(QUERY, IDS, SORT_FIELDS, OFFSET, SIZE)
|
|
931
1350
|
{
|
|
932
1351
|
if (this.token === null && this.config.username)
|
|
@@ -1039,12 +1458,33 @@ export default NomadMediaSDK;
|
|
|
1039
1458
|
|
|
1040
1459
|
|
|
1041
1460
|
|
|
1042
|
-
|
|
1461
|
+
/**
|
|
1462
|
+
* Trigger a password reset code to be sent to the user's email address.
|
|
1463
|
+
*
|
|
1464
|
+
* Initiates the password reset flow for the specified user account. If the account is active,
|
|
1465
|
+
* a 6-digit verification code is sent to the user's email address. The code should then be
|
|
1466
|
+
* submitted to the reset-password endpoint along with the new password.
|
|
1467
|
+
*
|
|
1468
|
+
* **Authorization:** Public endpoint — no authentication required.
|
|
1469
|
+
*
|
|
1470
|
+
* **Side Effects:** Sends a password reset email with a 6-digit verification code to the user.
|
|
1471
|
+
*
|
|
1472
|
+
* @param {Object} instance - The SDK instance containing configuration (username)
|
|
1473
|
+
* @returns {Promise<Object>} Response indicating the reset code was sent, or a pending account state
|
|
1474
|
+
* @throws {Error} 400 - The request is null or UserName is missing
|
|
1475
|
+
*
|
|
1476
|
+
* @example
|
|
1477
|
+
* const result = await nomadSDK.forgotPassword();
|
|
1478
|
+
* console.log('Password reset code sent to email');
|
|
1479
|
+
*
|
|
1480
|
+
* @see {@link https://docs.nomad-cms.com/api/account/forgot-password Admin API} POST /account/forgot-password
|
|
1481
|
+
*/
|
|
1482
|
+
async function _forgotPassword(instance)
|
|
1043
1483
|
{
|
|
1044
1484
|
const API_URL = `${instance.config.serviceApiUrl}/api/account/forgot-password`;
|
|
1045
1485
|
|
|
1046
1486
|
const BODY = {
|
|
1047
|
-
|
|
1487
|
+
userName: instance.config.username
|
|
1048
1488
|
};
|
|
1049
1489
|
|
|
1050
1490
|
return await _sendRequest(instance, "Forgot Password", API_URL, "POST", undefined, BODY);
|
|
@@ -1053,6 +1493,41 @@ async function _forgotPassword(instance)
|
|
|
1053
1493
|
|
|
1054
1494
|
|
|
1055
1495
|
|
|
1496
|
+
/**
|
|
1497
|
+
* Authenticate a user and obtain a JWT access token.
|
|
1498
|
+
*
|
|
1499
|
+
* Validates the user's credentials and, if successful, creates a new session and returns
|
|
1500
|
+
* a JWT access token with a refresh token. Accounts that are disabled, expired, pending invitation, or pending
|
|
1501
|
+
* approval will be rejected. Guest users must have a valid profile and permitted application access.
|
|
1502
|
+
*
|
|
1503
|
+
* **Authorization:** Public endpoint — no authentication required.
|
|
1504
|
+
*
|
|
1505
|
+
* **Side Effects:** Creates a new user session. The login event is recorded for auditing purposes.
|
|
1506
|
+
*
|
|
1507
|
+
* **Error Scenarios:**
|
|
1508
|
+
* - Returns 400 if the request body is null or missing required fields
|
|
1509
|
+
* - Returns 404 if authentication fails or the user cannot be found
|
|
1510
|
+
* - Returns 403 if the user account is Disabled, Expired, PendingInvitation, or PendingApproval
|
|
1511
|
+
* - LoginStatus on the response indicates the specific failure reason (e.g., IsDisabled, IsPendingEmailConfirmation, RequiresNewPassword)
|
|
1512
|
+
*
|
|
1513
|
+
* @param {Object} instance - The SDK instance containing configuration (username, password, serviceApiUrl, and optionally applicationId)
|
|
1514
|
+
* @returns {Promise<Object>} Authentication response. User-identity fields (from the backend
|
|
1515
|
+
* `AccountLoginResponseModel`): `id`, `userName`, `firstName`, `lastName`, `loginStatus`,
|
|
1516
|
+
* `cookies`. Session/token fields consumed by the SDK: `token`, `refreshToken`,
|
|
1517
|
+
* `expirationSeconds`, `userSessionId`. Note: there is no `userId` or `expiresIn` field;
|
|
1518
|
+
* use `id` and `expirationSeconds` respectively.
|
|
1519
|
+
* @throws {Error} 400 - The request body is null or missing required fields (UserName, Password)
|
|
1520
|
+
* @throws {Error} 404 - Authentication failed — invalid credentials or user not found
|
|
1521
|
+
* @throws {Error} 403 - User account is Disabled, Expired, PendingInvitation, or PendingApproval
|
|
1522
|
+
*
|
|
1523
|
+
* @example
|
|
1524
|
+
* const response = await nomadSDK.login();
|
|
1525
|
+
* console.log('Access Token:', response.token);
|
|
1526
|
+
* console.log('User ID:', response.id);
|
|
1527
|
+
* console.log('User Name:', response.userName);
|
|
1528
|
+
*
|
|
1529
|
+
* @see {@link https://docs.nomad-cms.com/api/account/login Admin API} POST /account/login
|
|
1530
|
+
*/
|
|
1056
1531
|
async function _login(instance)
|
|
1057
1532
|
{
|
|
1058
1533
|
const API_URL = `${instance.config.serviceApiUrl}/api/account/login`;
|
|
@@ -1062,6 +1537,9 @@ async function _login(instance)
|
|
|
1062
1537
|
"password": instance.config.password
|
|
1063
1538
|
};
|
|
1064
1539
|
|
|
1540
|
+
// Optional: ties the session to an application (config.applicationId), as ping and logout do.
|
|
1541
|
+
if (instance.config.applicationId) BODY.applicationId = instance.config.applicationId;
|
|
1542
|
+
|
|
1065
1543
|
try
|
|
1066
1544
|
{
|
|
1067
1545
|
return await _sendRequest(instance, "Login", API_URL, "POST", undefined, BODY);
|
|
@@ -1083,11 +1561,38 @@ async function _login(instance)
|
|
|
1083
1561
|
|
|
1084
1562
|
|
|
1085
1563
|
|
|
1564
|
+
/**
|
|
1565
|
+
* Log out the currently authenticated user and end their session.
|
|
1566
|
+
*
|
|
1567
|
+
* Ends the user's active session and invalidates the authentication token.
|
|
1568
|
+
* If a UserSessionId is provided, that specific session is terminated.
|
|
1569
|
+
*
|
|
1570
|
+
* **Authorization:** Requires a valid Bearer token.
|
|
1571
|
+
*
|
|
1572
|
+
* **Side Effects:** Terminates the user session. The logout event is recorded for auditing purposes.
|
|
1573
|
+
*
|
|
1574
|
+
* @param {Object} instance - The SDK instance containing configuration and session information (the session ID and optional applicationId are sent in the request body)
|
|
1575
|
+
* @returns {Promise<Object>} Confirmation that the user was successfully logged out and the session was deleted
|
|
1576
|
+
* @throws {Error} 401 - The caller is not authenticated
|
|
1577
|
+
*
|
|
1578
|
+
* @example
|
|
1579
|
+
* await nomadSDK.logout();
|
|
1580
|
+
* console.log('User logged out successfully');
|
|
1581
|
+
*
|
|
1582
|
+
* @see {@link https://docs.nomad-cms.com/api/account/logout Admin API} POST /account/logout
|
|
1583
|
+
*/
|
|
1086
1584
|
async function _logout(instance)
|
|
1087
1585
|
{
|
|
1088
1586
|
const API_URL = `${instance.config.serviceApiUrl}/api/account/logout`;
|
|
1089
1587
|
|
|
1090
|
-
|
|
1588
|
+
// The API requires a request body; an empty body is rejected with 400.
|
|
1589
|
+
// Undefined fields are dropped by JSON serialisation, so this is at worst "{}".
|
|
1590
|
+
const BODY = {
|
|
1591
|
+
userSessionId: instance.userSessionId ?? undefined,
|
|
1592
|
+
applicationId: instance.config.applicationId ?? undefined
|
|
1593
|
+
};
|
|
1594
|
+
|
|
1595
|
+
return await _sendRequest(instance, "Logout", API_URL, "POST", undefined, BODY);
|
|
1091
1596
|
}
|
|
1092
1597
|
|
|
1093
1598
|
|
|
@@ -1096,7 +1601,30 @@ async function _logout(instance)
|
|
|
1096
1601
|
|
|
1097
1602
|
|
|
1098
1603
|
|
|
1099
|
-
|
|
1604
|
+
/**
|
|
1605
|
+
* Retrieve a new JWT access token using a refresh token.
|
|
1606
|
+
*
|
|
1607
|
+
* Exchanges a valid refresh token for a new JWT access token. Automatically updates
|
|
1608
|
+
* the SDK instance's token property on success.
|
|
1609
|
+
*
|
|
1610
|
+
* **Authorization:** Public endpoint — no authentication required. The refresh token itself serves as the credential.
|
|
1611
|
+
*
|
|
1612
|
+
* **Side Effects:** Updates the SDK instance's token property with the new access token.
|
|
1613
|
+
*
|
|
1614
|
+
* @param {Object} instance - The SDK instance containing configuration and refreshTokenVal
|
|
1615
|
+
* @returns {Promise<boolean>} True if the token was successfully refreshed, false otherwise
|
|
1616
|
+
* @throws {Error} 400 - The request body is null or RefreshToken is missing
|
|
1617
|
+
* @throws {Error} 404 - The refresh token is invalid or has expired
|
|
1618
|
+
*
|
|
1619
|
+
* @example
|
|
1620
|
+
* const success = await nomadSDK.refreshToken();
|
|
1621
|
+
* if (success) {
|
|
1622
|
+
* console.log('Token refreshed successfully');
|
|
1623
|
+
* }
|
|
1624
|
+
*
|
|
1625
|
+
* @see {@link https://docs.nomad-cms.com/api/account/refresh-token Admin API} POST /account/refresh-token
|
|
1626
|
+
*/
|
|
1627
|
+
async function _refreshToken(instance)
|
|
1100
1628
|
{
|
|
1101
1629
|
const API_URL = `${instance.config.serviceApiUrl}/api/account/refresh-token`;
|
|
1102
1630
|
|
|
@@ -1104,22 +1632,69 @@ async function _refreshToken(instance)
|
|
|
1104
1632
|
refreshToken: instance.refreshTokenVal
|
|
1105
1633
|
};
|
|
1106
1634
|
|
|
1107
|
-
|
|
1108
|
-
|
|
1635
|
+
// _sendRequest() now throws on failure (api-exception-handler.js) rather
|
|
1636
|
+
// than resolving to a falsy value — but this function's own contract
|
|
1637
|
+
// ("returns false, never throws" — see its own doc comment/callers)
|
|
1638
|
+
// predates that and must be preserved regardless: _sendRequest()'s own
|
|
1639
|
+
// 403-retry logic calls this and explicitly checks the boolean result to
|
|
1640
|
+
// decide whether to fall back to a full re-login. Letting a failed
|
|
1641
|
+
// refresh (e.g. an expired/already-used refresh token, a 409) throw
|
|
1642
|
+
// uncaught here would crash that entire retry path instead of correctly
|
|
1643
|
+
// falling through to login().
|
|
1644
|
+
let TOKEN_INFO;
|
|
1645
|
+
try
|
|
1646
|
+
{
|
|
1647
|
+
TOKEN_INFO = await _sendRequest(instance, "Refreshing Token", API_URL, "POST", undefined, BODY);
|
|
1648
|
+
}
|
|
1649
|
+
catch (error)
|
|
1650
|
+
{
|
|
1651
|
+
return false;
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
if (TOKEN_INFO && TOKEN_INFO.token)
|
|
1655
|
+
{
|
|
1656
|
+
instance.token = TOKEN_INFO.token;
|
|
1657
|
+
return true;
|
|
1658
|
+
}
|
|
1109
1659
|
|
|
1110
|
-
return
|
|
1660
|
+
return false;
|
|
1111
1661
|
}
|
|
1112
1662
|
|
|
1113
1663
|
|
|
1114
1664
|
|
|
1115
1665
|
|
|
1116
1666
|
|
|
1117
|
-
|
|
1667
|
+
/**
|
|
1668
|
+
* Reset the user's password using a verification code from the forgot-password email.
|
|
1669
|
+
*
|
|
1670
|
+
* Completes the password reset flow by validating the verification code from the forgot-password
|
|
1671
|
+
* email and setting the new password. For accounts pending approval, this also completes the
|
|
1672
|
+
* email verification step.
|
|
1673
|
+
*
|
|
1674
|
+
* **Authorization:** Public endpoint — no authentication required. The reset token serves as the credential.
|
|
1675
|
+
*
|
|
1676
|
+
* **Side Effects:**
|
|
1677
|
+
* - Updates the user's password
|
|
1678
|
+
* - For pending-approval accounts, also verifies the email address
|
|
1679
|
+
*
|
|
1680
|
+
* @param {Object} instance - The SDK instance containing configuration (username)
|
|
1681
|
+
* @param {string} CODE - The 6-digit verification code from the forgot-password email
|
|
1682
|
+
* @param {string} NEW_PASSWORD - The new password to set for the user
|
|
1683
|
+
* @returns {Promise<Object>} Response confirming the password was successfully reset
|
|
1684
|
+
* @throws {Error} 400 - The request is null, UserName is missing, reset token is invalid/expired, or user is disabled
|
|
1685
|
+
*
|
|
1686
|
+
* @example
|
|
1687
|
+
* const result = await nomadSDK.resetPassword('123456', 'newSecurePassword!');
|
|
1688
|
+
* console.log('Password reset successfully');
|
|
1689
|
+
*
|
|
1690
|
+
* @see {@link https://docs.nomad-cms.com/api/account/reset-password Admin API} POST /account/reset-password
|
|
1691
|
+
*/
|
|
1692
|
+
async function _resetPassword(instance, CODE, NEW_PASSWORD)
|
|
1118
1693
|
{
|
|
1119
1694
|
const API_URL = `${instance.config.serviceApiUrl}/api/account/reset-password`;
|
|
1120
1695
|
|
|
1121
1696
|
const BODY = {
|
|
1122
|
-
userName: instance.username,
|
|
1697
|
+
userName: instance.config.username,
|
|
1123
1698
|
token: CODE,
|
|
1124
1699
|
newPassword: NEW_PASSWORD
|
|
1125
1700
|
};
|
|
@@ -1128,52 +1703,113 @@ async function _resetPassword(instance, CODE, NEW_PASSWORD)
|
|
|
1128
1703
|
}
|
|
1129
1704
|
|
|
1130
1705
|
|
|
1131
|
-
|
|
1706
|
+
/**
|
|
1707
|
+
* Builds and throws a real Error for a failed API call, instead of logging
|
|
1708
|
+
* and swallowing it.
|
|
1709
|
+
*
|
|
1710
|
+
* This used to log via console.error and return null on every failure — a
|
|
1711
|
+
* design that made a genuinely failed request indistinguishable, to any
|
|
1712
|
+
* caller, from a real (if empty/falsy) success. Confirmed live to be the
|
|
1713
|
+
* root cause of several serious bugs: createContent() reporting "Content
|
|
1714
|
+
* created" for a record whose properties write had actually failed
|
|
1715
|
+
* (a rejected Lookup Multi-Select shape), updateAsset() silently no-opping
|
|
1716
|
+
* while an asset was still mid-upload, and getContent() returning `null`
|
|
1717
|
+
* for a genuine 404 rather than letting a caller distinguish "not found"
|
|
1718
|
+
* from "found, something else is wrong." Every one of those bugs required
|
|
1719
|
+
* tracing through live server logs to find, specifically because nothing
|
|
1720
|
+
* in this SDK's own return value or a thrown exception ever said so.
|
|
1721
|
+
*
|
|
1722
|
+
* @param {Error|Object|string} error - The underlying error (an Axios error,
|
|
1723
|
+
* a parsed error-response body, or a plain string message)
|
|
1724
|
+
* @param {string} message - A human-readable prefix describing what failed
|
|
1725
|
+
* (e.g. "Update Content failed")
|
|
1726
|
+
* @param {number} [status] - The HTTP status code, when known, attached to
|
|
1727
|
+
* the thrown error as `.status` so callers can distinguish e.g. a 404
|
|
1728
|
+
* from a 403 without parsing the message text.
|
|
1729
|
+
* @throws {Error} Always — with `.message` built from `message` + whatever
|
|
1730
|
+
* detail could be extracted from `error`, `.cause` set to the original
|
|
1731
|
+
* `error` value, and `.status` set when provided.
|
|
1732
|
+
*/
|
|
1733
|
+
function _apiExceptionHandler(error, message, status)
|
|
1132
1734
|
{
|
|
1133
|
-
|
|
1134
|
-
|
|
1735
|
+
let detail;
|
|
1736
|
+
|
|
1737
|
+
if (!error)
|
|
1135
1738
|
{
|
|
1136
|
-
|
|
1739
|
+
detail = undefined;
|
|
1137
1740
|
}
|
|
1138
|
-
|
|
1139
|
-
// Check if error is a string
|
|
1140
|
-
if (typeof error === "string")
|
|
1741
|
+
else if (typeof error === "string")
|
|
1141
1742
|
{
|
|
1142
|
-
|
|
1743
|
+
detail = error;
|
|
1143
1744
|
}
|
|
1144
1745
|
else if (error.errors)
|
|
1145
1746
|
{
|
|
1146
|
-
if (Array.isArray(error.errors))
|
|
1747
|
+
if (Array.isArray(error.errors))
|
|
1147
1748
|
{
|
|
1148
1749
|
let errorString = "";
|
|
1149
|
-
for (let errorIdx = 0; errorIdx < error.errors.length; errorIdx++)
|
|
1750
|
+
for (let errorIdx = 0; errorIdx < error.errors.length; errorIdx++)
|
|
1150
1751
|
{
|
|
1151
|
-
errorString += `${error.errors[errorIdx].code}: ${error.errors[errorIdx].field}: ${error.errors[errorIdx].message}\n`;
|
|
1752
|
+
errorString += `${error.errors[errorIdx].code}: ${error.errors[errorIdx].field}: ${error.errors[errorIdx].message}\n`;
|
|
1152
1753
|
}
|
|
1153
|
-
|
|
1154
|
-
}
|
|
1155
|
-
else if (typeof error.errors === "object")
|
|
1754
|
+
detail = errorString;
|
|
1755
|
+
}
|
|
1756
|
+
else if (typeof error.errors === "object")
|
|
1156
1757
|
{
|
|
1157
1758
|
let errorString = "";
|
|
1158
|
-
for (let key in error.errors)
|
|
1759
|
+
for (let key in error.errors)
|
|
1159
1760
|
{
|
|
1160
|
-
if (Object.prototype.hasOwnProperty.call(error.errors, key))
|
|
1761
|
+
if (Object.prototype.hasOwnProperty.call(error.errors, key))
|
|
1161
1762
|
{
|
|
1162
1763
|
errorString += `${key}: ${error.errors[key]}\n`;
|
|
1163
1764
|
}
|
|
1164
1765
|
}
|
|
1165
|
-
|
|
1766
|
+
detail = errorString;
|
|
1166
1767
|
}
|
|
1167
1768
|
}
|
|
1168
1769
|
else
|
|
1169
1770
|
{
|
|
1170
|
-
|
|
1771
|
+
detail = error.message;
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
const thrown = new Error(detail ? `${message}: ${detail}` : message);
|
|
1775
|
+
thrown.cause = error;
|
|
1776
|
+
if (status !== undefined && status !== null) thrown.status = status;
|
|
1777
|
+
throw thrown;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
|
|
1781
|
+
class InvalidAPITypeException extends Error {
|
|
1782
|
+
constructor(message) {
|
|
1783
|
+
super(message);
|
|
1784
|
+
this.name = "InvalidAPITypeException";
|
|
1171
1785
|
}
|
|
1786
|
+
}
|
|
1172
1787
|
|
|
1173
|
-
|
|
1788
|
+
class MissingCredentialsException extends Error {
|
|
1789
|
+
constructor(credentials = null, message = "Missing login credentials") {
|
|
1790
|
+
if (credentials) {
|
|
1791
|
+
message = `${message}: ${Array.isArray(credentials) ? credentials.join(", ") : credentials}`;
|
|
1792
|
+
}
|
|
1793
|
+
super(message);
|
|
1794
|
+
this.name = "MissingCredentialsException";
|
|
1795
|
+
this.credentials = credentials;
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
class LoginException extends Error {
|
|
1800
|
+
constructor(message = "Login info incorrect", cause = undefined) {
|
|
1801
|
+
super(message);
|
|
1802
|
+
this.name = "LoginException";
|
|
1803
|
+
if (cause !== undefined)
|
|
1804
|
+
{
|
|
1805
|
+
this.cause = cause;
|
|
1806
|
+
if (cause && cause.status !== undefined) this.status = cause.status;
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1174
1809
|
}
|
|
1175
1810
|
|
|
1176
1811
|
|
|
1812
|
+
|
|
1177
1813
|
import { getRandomValues } from 'crypto';
|
|
1178
1814
|
|
|
1179
1815
|
function _newGuid()
|
|
@@ -1196,51 +1832,116 @@ function _printDatetime(msg)
|
|
|
1196
1832
|
import { promises as fs } from 'fs';
|
|
1197
1833
|
import path from 'path';
|
|
1198
1834
|
|
|
1835
|
+
/**
|
|
1836
|
+
* Normalizes the file argument of an upload into the properties the upload
|
|
1837
|
+
* needs, without reading the file's contents into memory.
|
|
1838
|
+
*
|
|
1839
|
+
* Accepts:
|
|
1840
|
+
* - a file path (Node.js only), read part by part from disk
|
|
1841
|
+
* - a browser `File`/`Blob`, read part by part with `slice()`
|
|
1842
|
+
* - an object that already carries its bytes in `buffer` (for example a
|
|
1843
|
+
* multer file), plus `size` and a name
|
|
1844
|
+
*
|
|
1845
|
+
* @param {string|File|Blob|Object} FILE - The file to upload
|
|
1846
|
+
* @returns {Promise<{originalName: string, size: number, path?: string, blob?: Blob, buffer?: Buffer|ArrayBuffer|Uint8Array}>}
|
|
1847
|
+
* The file's name and size, and the single source its parts are read from
|
|
1848
|
+
* @throws {Error} When the path does not exist, or a path is given in a browser
|
|
1849
|
+
*/
|
|
1199
1850
|
async function _processFile(FILE)
|
|
1200
1851
|
{
|
|
1201
|
-
let FILE_PATH = null;
|
|
1202
1852
|
if (typeof FILE === "string")
|
|
1203
1853
|
{
|
|
1204
|
-
|
|
1205
|
-
if (typeof window === "undefined")
|
|
1854
|
+
if (typeof window !== "undefined")
|
|
1206
1855
|
{
|
|
1207
|
-
|
|
1208
|
-
FILE = await fs.stat(FILE);
|
|
1209
|
-
FILE.originalName = path.basename(FILE_PATH);
|
|
1210
|
-
if (!FILE.buffer)
|
|
1211
|
-
{
|
|
1212
|
-
FILE.buffer = await fs.readFile(FILE_PATH);
|
|
1213
|
-
}
|
|
1856
|
+
throw new Error("File path is not supported in browser");
|
|
1214
1857
|
}
|
|
1215
|
-
|
|
1858
|
+
|
|
1859
|
+
let stats;
|
|
1860
|
+
try
|
|
1216
1861
|
{
|
|
1217
|
-
|
|
1862
|
+
stats = await fs.stat(FILE);
|
|
1218
1863
|
}
|
|
1219
|
-
|
|
1220
|
-
else
|
|
1221
|
-
{
|
|
1222
|
-
FILE.originalName = FILE.name;
|
|
1223
|
-
// if running in browser
|
|
1224
|
-
if (!FILE.buffer)
|
|
1864
|
+
catch (error)
|
|
1225
1865
|
{
|
|
1226
|
-
|
|
1866
|
+
const notFound = new Error(`File not found: ${FILE}`);
|
|
1867
|
+
notFound.code = error.code;
|
|
1868
|
+
notFound.cause = error;
|
|
1869
|
+
throw notFound;
|
|
1227
1870
|
}
|
|
1871
|
+
|
|
1872
|
+
if (!stats.isFile())
|
|
1873
|
+
{
|
|
1874
|
+
throw new Error(`Not a file: ${FILE}`);
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
return { originalName: path.basename(FILE), size: stats.size, path: FILE };
|
|
1228
1878
|
}
|
|
1229
1879
|
|
|
1230
|
-
|
|
1880
|
+
const originalName = FILE.originalName || FILE.name || FILE.originalname;
|
|
1881
|
+
|
|
1882
|
+
if (FILE.buffer)
|
|
1883
|
+
{
|
|
1884
|
+
const size = FILE.size ?? FILE.buffer.byteLength;
|
|
1885
|
+
return { originalName, size, buffer: FILE.buffer };
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
if (typeof FILE.slice === "function")
|
|
1889
|
+
{
|
|
1890
|
+
return { originalName, size: FILE.size, blob: FILE };
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
throw new Error("Unsupported file: pass a file path, a File/Blob, or an object with a buffer");
|
|
1231
1894
|
}
|
|
1232
1895
|
|
|
1233
|
-
|
|
1896
|
+
/**
|
|
1897
|
+
* Opens a reader that returns the bytes of one part at a time.
|
|
1898
|
+
*
|
|
1899
|
+
* In Node.js a path is read with positional reads on a single file handle, so
|
|
1900
|
+
* concurrent parts never share or move a file position. Blobs are sliced
|
|
1901
|
+
* lazily and buffers are viewed without copying.
|
|
1902
|
+
*
|
|
1903
|
+
* @param {Object} FILE_PROPS - The result of `_processFile`
|
|
1904
|
+
* @returns {Promise<{read: function(number, number): Promise<Buffer|Uint8Array|Blob>, close: function(): Promise<void>}>}
|
|
1905
|
+
* `read(start, end)` returns the bytes from `start` to `end` inclusive
|
|
1906
|
+
*/
|
|
1907
|
+
async function _openPartReader(FILE_PROPS)
|
|
1234
1908
|
{
|
|
1235
|
-
|
|
1909
|
+
if (FILE_PROPS.path)
|
|
1236
1910
|
{
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1911
|
+
const HANDLE = await fs.open(FILE_PROPS.path, "r");
|
|
1912
|
+
return {
|
|
1913
|
+
async read(START, END)
|
|
1914
|
+
{
|
|
1915
|
+
const LENGTH = END + 1 - START;
|
|
1916
|
+
const PART = Buffer.alloc(LENGTH);
|
|
1917
|
+
let offset = 0;
|
|
1918
|
+
while (offset < LENGTH)
|
|
1919
|
+
{
|
|
1920
|
+
const { bytesRead } = await HANDLE.read(PART, offset, LENGTH - offset, START + offset);
|
|
1921
|
+
if (bytesRead === 0) throw new Error(`Unexpected end of file reading bytes ${START}-${END}`);
|
|
1922
|
+
offset += bytesRead;
|
|
1923
|
+
}
|
|
1924
|
+
return PART;
|
|
1925
|
+
},
|
|
1926
|
+
close: () => HANDLE.close()
|
|
1927
|
+
};
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
if (FILE_PROPS.blob)
|
|
1931
|
+
{
|
|
1932
|
+
return {
|
|
1933
|
+
read: async (START, END) => FILE_PROPS.blob.slice(START, END + 1),
|
|
1934
|
+
close: async () => {}
|
|
1935
|
+
};
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
const BYTES = FILE_PROPS.buffer instanceof ArrayBuffer
|
|
1939
|
+
? new Uint8Array(FILE_PROPS.buffer)
|
|
1940
|
+
: FILE_PROPS.buffer;
|
|
1941
|
+
return {
|
|
1942
|
+
read: async (START, END) => BYTES.subarray(START, END + 1),
|
|
1943
|
+
close: async () => {}
|
|
1944
|
+
};
|
|
1244
1945
|
}
|
|
1245
1946
|
|
|
1246
1947
|
|
|
@@ -1248,7 +1949,10 @@ async function _readFileAsArrayBuffer(file)
|
|
|
1248
1949
|
|
|
1249
1950
|
|
|
1250
1951
|
|
|
1251
|
-
|
|
1952
|
+
|
|
1953
|
+
|
|
1954
|
+
|
|
1955
|
+
|
|
1252
1956
|
|
|
1253
1957
|
const MAX_RETRIES = 3;
|
|
1254
1958
|
const TIMEOUT = 10000;
|
|
@@ -1263,8 +1967,22 @@ async function _sendRequest(instance, FUNCTION_NAME, URL, METHOD_TYPE, PARAMS, B
|
|
|
1263
1967
|
if (instance.token) HEADERS["Authorization"] = `Bearer ${instance.token}`;
|
|
1264
1968
|
else if (instance.config.apiKey) HEADERS["Authorization"] = `ApiKey ${instance.config.apiKey}`;
|
|
1265
1969
|
|
|
1266
|
-
|
|
1970
|
+
// A missing required id argument would otherwise be sent to the server as ".../undefined".
|
|
1971
|
+
// String(URL) so a caller that built a URL object does not crash the check.
|
|
1972
|
+
const URL_PATH = String(URL).split("?")[0];
|
|
1973
|
+
if (/\/(undefined|null)(\/|\?|$)/.test(URL_PATH))
|
|
1974
|
+
{
|
|
1975
|
+
_apiExceptionHandler(`a required id argument was not provided (${URL_PATH})`, `${FUNCTION_NAME} failed`);
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
const HAS_PARAMS = PARAMS && Object.keys(PARAMS).length > 0;
|
|
1979
|
+
if (instance.debug && FUNCTION_NAME !== "Login") console.log(`URL: ${URL}\nMETHOD: ${METHOD_TYPE}` + (HAS_PARAMS ? `\nPARAMS: ${JSON.stringify(PARAMS, null, 4)}` : "") + (BODY ? `\nBODY: ${JSON.stringify(BODY, null, 4)}` : ""));
|
|
1980
|
+
|
|
1981
|
+
// Only requests that are safe to repeat are retried after a timeout. A timed-out
|
|
1982
|
+
// POST/PUT/DELETE may already have been applied by the server.
|
|
1983
|
+
const IS_IDEMPOTENT = ["GET", "HEAD", "OPTIONS"].includes(String(METHOD_TYPE).toUpperCase());
|
|
1267
1984
|
|
|
1985
|
+
let lastError = null;
|
|
1268
1986
|
let retries = 0;
|
|
1269
1987
|
let RESPONSE = null;
|
|
1270
1988
|
let refreshedToken = false;
|
|
@@ -1288,32 +2006,58 @@ async function _sendRequest(instance, FUNCTION_NAME, URL, METHOD_TYPE, PARAMS, B
|
|
|
1288
2006
|
|
|
1289
2007
|
throw new Error();
|
|
1290
2008
|
}
|
|
1291
|
-
catch (error)
|
|
2009
|
+
catch (error)
|
|
1292
2010
|
{
|
|
2011
|
+
lastError = error;
|
|
2012
|
+
const ERROR_MESSAGE = error.response?.data?.message ? error.response.data.message : error.message;
|
|
2013
|
+
|
|
1293
2014
|
if (retries >= MAX_RETRIES)
|
|
1294
2015
|
{
|
|
1295
|
-
|
|
2016
|
+
_apiExceptionHandler(ERROR_MESSAGE, `${FUNCTION_NAME} failed`, error.response?.status);
|
|
1296
2017
|
}
|
|
1297
2018
|
|
|
1298
2019
|
if (error?.response?.status === 403 && FUNCTION_NAME !== "Refresh Token" && !refreshedToken)
|
|
1299
2020
|
{
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
2021
|
+
if (!instance.refreshTokenVal)
|
|
2022
|
+
{
|
|
2023
|
+
throw new LoginException();
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
const refreshSuccess = await instance.refreshToken();
|
|
2027
|
+
if (refreshSuccess)
|
|
2028
|
+
{
|
|
2029
|
+
HEADERS["Authorization"] = `Bearer ${instance.token}`;
|
|
2030
|
+
refreshedToken = true;
|
|
2031
|
+
}
|
|
2032
|
+
else
|
|
2033
|
+
{
|
|
2034
|
+
// If refresh token is invalid, fall back to login
|
|
2035
|
+
await instance.login();
|
|
2036
|
+
HEADERS["Authorization"] = `Bearer ${instance.token}`;
|
|
2037
|
+
refreshedToken = false;
|
|
2038
|
+
}
|
|
1303
2039
|
}
|
|
1304
|
-
|
|
2040
|
+
|
|
2041
|
+
else if (error.code === 'ECONNABORTED' && IS_IDEMPOTENT)
|
|
1305
2042
|
{
|
|
1306
2043
|
await _sleep(SLEEP_TIME * 2 ** (retries + 1));
|
|
1307
2044
|
}
|
|
1308
|
-
else
|
|
2045
|
+
else
|
|
1309
2046
|
{
|
|
1310
|
-
|
|
2047
|
+
_apiExceptionHandler(ERROR_MESSAGE, `${FUNCTION_NAME} failed`, error.response?.status);
|
|
1311
2048
|
}
|
|
1312
2049
|
++retries;
|
|
1313
2050
|
}
|
|
1314
2051
|
} while ((!RESPONSE || RESPONSE.status < 200 || RESPONSE.status >= 300) && retries < MAX_RETRIES);
|
|
1315
|
-
|
|
1316
|
-
|
|
2052
|
+
|
|
2053
|
+
// Retries ran out without a successful response: fail loudly instead of returning undefined.
|
|
2054
|
+
if (!RESPONSE || RESPONSE.status < 200 || RESPONSE.status >= 300)
|
|
2055
|
+
{
|
|
2056
|
+
_apiExceptionHandler(lastError?.response?.data?.message ?? lastError?.message ?? "no response received",
|
|
2057
|
+
`${FUNCTION_NAME} failed`, lastError?.response?.status);
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
return RESPONSE.data;
|
|
1317
2061
|
}
|
|
1318
2062
|
|
|
1319
2063
|
|
|
@@ -1352,9 +2096,91 @@ function _slugify(text)
|
|
|
1352
2096
|
|
|
1353
2097
|
|
|
1354
2098
|
|
|
2099
|
+
/**
|
|
2100
|
+
* Strips read-only metadata the server adds when *reading back* a
|
|
2101
|
+
* reference-type field, so a value read from `getContent()`/`getAsset()`/
|
|
2102
|
+
* similar and resubmitted unchanged on a later update keeps the same shape
|
|
2103
|
+
* it was originally written with.
|
|
2104
|
+
*
|
|
2105
|
+
* Confirmed live (this project's own Media Contributor app, dev-05): a
|
|
2106
|
+
* Content Reference or Asset Reference field's read-back shape includes a
|
|
2107
|
+
* `type` key (disambiguating what kind of thing is referenced — e.g. an
|
|
2108
|
+
* Asset vs. another Content record) that no caller ever supplies on write.
|
|
2109
|
+
* A read-modify-write update that resubmits a field's read-back value
|
|
2110
|
+
* verbatim (because the caller didn't intend to change it) sends that
|
|
2111
|
+
* `type` key back too — and on `Upload Batch.FolderId`, this triggered an
|
|
2112
|
+
* OpenSearch mapping-type rejection ("failed to parse field
|
|
2113
|
+
* [identifiers.folderId] of type [text]") that destroyed the *entire*
|
|
2114
|
+
* document's searchability, not just that one field, with no error ever
|
|
2115
|
+
* surfaced to the caller (see api-exception-handler.js's own history).
|
|
2116
|
+
*
|
|
2117
|
+
* `id` and `description`/`title` are left untouched — both appear in real,
|
|
2118
|
+
* confirmed-working write shapes (e.g. a Lookup Multi-Select's
|
|
2119
|
+
* `{id, description}`) — only `type` is confirmed read-only/write-unsafe.
|
|
2120
|
+
*
|
|
2121
|
+
* @param {*} value - A property value that may be a reference object
|
|
2122
|
+
* (`{id, type, ...}`), an array of them, or anything else (passed through
|
|
2123
|
+
* unchanged)
|
|
2124
|
+
* @returns {*} The same shape with `type` removed from any object that has
|
|
2125
|
+
* both `id` and `type`
|
|
2126
|
+
*/
|
|
2127
|
+
function _stripReadOnlyReferenceMetadata(value)
|
|
2128
|
+
{
|
|
2129
|
+
if (Array.isArray(value)) return value.map(_stripReadOnlyReferenceMetadata);
|
|
2130
|
+
if (value && typeof value === 'object' && 'id' in value && 'type' in value)
|
|
2131
|
+
{
|
|
2132
|
+
const { type, ...rest } = value;
|
|
2133
|
+
return rest;
|
|
2134
|
+
}
|
|
2135
|
+
return value;
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
|
|
2139
|
+
|
|
2140
|
+
/**
|
|
2141
|
+
* Convert a list of IDs into the lookup objects the Nomad API expects.
|
|
2142
|
+
*
|
|
2143
|
+
* Fields such as tags, collections and related content are bound by the API as
|
|
2144
|
+
* lookup objects and read each item's `id`; a bare string cannot be bound. Items
|
|
2145
|
+
* that are already objects are passed through unchanged.
|
|
2146
|
+
*
|
|
2147
|
+
* @param {Array<string|Object>|null|undefined} VALUES - IDs, or lookup objects
|
|
2148
|
+
* @returns {Array<Object>|null|undefined} `VALUES` unchanged when it is null or
|
|
2149
|
+
* undefined (the field is left as it is); otherwise one lookup object per item
|
|
2150
|
+
*/
|
|
2151
|
+
function _toLookupModels(VALUES)
|
|
2152
|
+
{
|
|
2153
|
+
if (VALUES === null || VALUES === undefined) return VALUES;
|
|
2154
|
+
|
|
2155
|
+
return VALUES.map(VALUE => typeof VALUE === "string" ? { id: VALUE } : VALUE);
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
|
|
1355
2159
|
|
|
1356
2160
|
|
|
1357
|
-
|
|
2161
|
+
|
|
2162
|
+
/**
|
|
2163
|
+
* Clear a "continue watching" entry for a user.
|
|
2164
|
+
*
|
|
2165
|
+
* Removes a specific asset from the user's continue watching list.
|
|
2166
|
+
*
|
|
2167
|
+
* **Authorization:** Requires a valid Bearer token.
|
|
2168
|
+
*
|
|
2169
|
+
* **Side Effects:**
|
|
2170
|
+
* - Removes the continue watching tracking data for the user/asset
|
|
2171
|
+
*
|
|
2172
|
+
* @param {Object} instance - The SDK instance containing configuration and auth token
|
|
2173
|
+
* @param {string} USER_ID - The unique identifier of the user
|
|
2174
|
+
* @param {string} [ASSET_ID] - The asset ID to remove from continue watching
|
|
2175
|
+
* @returns {Promise<Object>} Response confirming the clearing
|
|
2176
|
+
* @throws {Error} 401 - The caller is not authenticated
|
|
2177
|
+
*
|
|
2178
|
+
* @example
|
|
2179
|
+
* await nomadSDK.clearContinueWatching("user-123", "asset-456");
|
|
2180
|
+
*
|
|
2181
|
+
* @see {@link https://docs.nomad-cms.com/docs/api-guide/media-api/clear-continue-watching Portal API} POST /media/clear-watching
|
|
2182
|
+
*/
|
|
2183
|
+
async function _clearContinueWatching(instance, USER_ID, ASSET_ID)
|
|
1358
2184
|
{
|
|
1359
2185
|
const API_URL = `${instance.config.serviceApiUrl}/api/media/clear-watching`;
|
|
1360
2186
|
|
|
@@ -1369,7 +2195,27 @@ async function _clearContinueWatching(instance, USER_ID, ASSET_ID)
|
|
|
1369
2195
|
|
|
1370
2196
|
|
|
1371
2197
|
|
|
1372
|
-
|
|
2198
|
+
/**
|
|
2199
|
+
* Clear the user's entire watchlist.
|
|
2200
|
+
*
|
|
2201
|
+
* Removes all items from the specified user's watchlist.
|
|
2202
|
+
*
|
|
2203
|
+
* **Authorization:** Requires a valid Bearer token.
|
|
2204
|
+
*
|
|
2205
|
+
* **Side Effects:**
|
|
2206
|
+
* - Removes all watchlist entries for the user
|
|
2207
|
+
*
|
|
2208
|
+
* @param {Object} instance - The SDK instance containing configuration and auth token
|
|
2209
|
+
* @param {string} USER_ID - The unique identifier of the user whose watchlist to clear
|
|
2210
|
+
* @returns {Promise<Object>} Response confirming the watchlist was cleared
|
|
2211
|
+
* @throws {Error} 401 - The caller is not authenticated
|
|
2212
|
+
*
|
|
2213
|
+
* @example
|
|
2214
|
+
* await nomadSDK.clearWatchlist("user-123");
|
|
2215
|
+
*
|
|
2216
|
+
* @see {@link https://docs.nomad-cms.com/docs/api-guide/media-api/clear-watchlist Portal API} POST /media/clear-watchlist
|
|
2217
|
+
*/
|
|
2218
|
+
async function _clearWatchlist(instance, USER_ID)
|
|
1373
2219
|
{
|
|
1374
2220
|
const API_URL = `${instance.config.serviceApiUrl}/api/media/clear-watchlist?userId=${USER_ID}`;
|
|
1375
2221
|
|
|
@@ -1379,7 +2225,32 @@ async function _clearWatchlist(instance, USER_ID)
|
|
|
1379
2225
|
|
|
1380
2226
|
|
|
1381
2227
|
|
|
1382
|
-
|
|
2228
|
+
/**
|
|
2229
|
+
* Create a form submission for a content definition.
|
|
2230
|
+
*
|
|
2231
|
+
* Submits form data to create a new entry for the specified content
|
|
2232
|
+
* definition. Used for user-generated content submissions through
|
|
2233
|
+
* portal forms.
|
|
2234
|
+
*
|
|
2235
|
+
* **Authorization:** Requires a valid Bearer token.
|
|
2236
|
+
*
|
|
2237
|
+
* **Side Effects:**
|
|
2238
|
+
* - Creates a new content entry from the form submission
|
|
2239
|
+
*
|
|
2240
|
+
* @param {Object} instance - The SDK instance containing configuration and auth token
|
|
2241
|
+
* @param {string} CONTENT_DEFINITION_ID - The content definition ID for the form type
|
|
2242
|
+
* @param {Object} FORM_INFO - The form data to submit
|
|
2243
|
+
* @returns {Promise<Object>} The created content entry
|
|
2244
|
+
* @throws {Error} 401 - The caller is not authenticated
|
|
2245
|
+
*
|
|
2246
|
+
* @example
|
|
2247
|
+
* const entry = await nomadSDK.createForm("def-123", {
|
|
2248
|
+
* title: "My Submission", description: "Description"
|
|
2249
|
+
* });
|
|
2250
|
+
*
|
|
2251
|
+
* @see {@link https://docs.nomad-cms.com/docs/api-guide/media-api/create-form Portal API} POST /media/form/{contentDefinitionId}
|
|
2252
|
+
*/
|
|
2253
|
+
async function _createForm(instance, CONTENT_DEFINITION_ID, FORM_INFO)
|
|
1383
2254
|
{
|
|
1384
2255
|
const API_URL = `${instance.config.serviceApiUrl}/api/media/form/${CONTENT_DEFINITION_ID}`;
|
|
1385
2256
|
|
|
@@ -1391,9 +2262,28 @@ async function _createForm(instance, CONTENT_DEFINITION_ID, FORM_INFO)
|
|
|
1391
2262
|
|
|
1392
2263
|
|
|
1393
2264
|
|
|
1394
|
-
|
|
2265
|
+
/**
|
|
2266
|
+
* Retrieve secure streaming cookies for portal media content.
|
|
2267
|
+
*
|
|
2268
|
+
* Gets signed cookies required for secure streaming of media content through the
|
|
2269
|
+
* portal API. The cookies authorize CDN access to the media files.
|
|
2270
|
+
*
|
|
2271
|
+
* **Authorization:** Requires a valid Bearer token.
|
|
2272
|
+
*
|
|
2273
|
+
* @param {Object} instance - The SDK instance containing configuration and auth token
|
|
2274
|
+
* @param {string} CONTENT_ID - The unique identifier of the media content
|
|
2275
|
+
* @returns {Promise<Object>} Streaming cookies and manifest URL for the content
|
|
2276
|
+
* @throws {Error} 401 - The caller is not authenticated
|
|
2277
|
+
* @throws {Error} 404 - The content was not found
|
|
2278
|
+
*
|
|
2279
|
+
* @example
|
|
2280
|
+
* const cookies = await nomadSDK.getContentCookies("media-123");
|
|
2281
|
+
*
|
|
2282
|
+
* @see {@link https://docs.nomad-cms.com/docs/api-guide/asset-api-1 Portal API} GET /media/set-cookies/{id}
|
|
2283
|
+
*/
|
|
2284
|
+
async function _getContentCookies(instance, CONTENT_ID)
|
|
1395
2285
|
{
|
|
1396
|
-
const API_URL = `${instance.config.serviceApiUrl}/api/media/set-cookies/${
|
|
2286
|
+
const API_URL = `${instance.config.serviceApiUrl}/api/media/set-cookies/${CONTENT_ID}`;
|
|
1397
2287
|
|
|
1398
2288
|
return await _sendRequest(instance, "Get Content Cookies", API_URL, "GET", undefined, undefined);
|
|
1399
2289
|
}
|
|
@@ -1401,7 +2291,24 @@ async function _getContentCookies(instance, ID)
|
|
|
1401
2291
|
|
|
1402
2292
|
|
|
1403
2293
|
|
|
1404
|
-
|
|
2294
|
+
/**
|
|
2295
|
+
* Retrieve the default site configuration.
|
|
2296
|
+
*
|
|
2297
|
+
* Gets the default portal site configuration used when no specific
|
|
2298
|
+
* configuration ID is provided.
|
|
2299
|
+
*
|
|
2300
|
+
* **Authorization:** Requires a valid Bearer token.
|
|
2301
|
+
*
|
|
2302
|
+
* @param {Object} instance - The SDK instance containing configuration and auth token
|
|
2303
|
+
* @returns {Promise<Object>} The default site configuration object
|
|
2304
|
+
* @throws {Error} 401 - The caller is not authenticated
|
|
2305
|
+
*
|
|
2306
|
+
* @example
|
|
2307
|
+
* const config = await nomadSDK.getDefaultSiteConfig();
|
|
2308
|
+
*
|
|
2309
|
+
* @see {@link https://docs.nomad-cms.com/docs/api-guide/media-api/get-default-site-config Portal API} GET /media/config
|
|
2310
|
+
*/
|
|
2311
|
+
async function _getDefaultSiteConfig(instance)
|
|
1405
2312
|
{
|
|
1406
2313
|
const API_URL = `${instance.config.serviceApiUrl}/api/media/config`;
|
|
1407
2314
|
|
|
@@ -1411,9 +2318,28 @@ async function _getDefaultSiteConfig(instance)
|
|
|
1411
2318
|
|
|
1412
2319
|
|
|
1413
2320
|
|
|
1414
|
-
|
|
2321
|
+
/**
|
|
2322
|
+
* Retrieve a dynamic content item by its unique identifier.
|
|
2323
|
+
*
|
|
2324
|
+
* Gets a dynamically generated content item used for personalized or
|
|
2325
|
+
* rule-based content display on the portal.
|
|
2326
|
+
*
|
|
2327
|
+
* **Authorization:** Requires a valid Bearer token.
|
|
2328
|
+
*
|
|
2329
|
+
* @param {Object} instance - The SDK instance containing configuration and auth token
|
|
2330
|
+
* @param {string} DYNAMIC_CONTENT_RECORD_ID - The unique identifier of the dynamic content
|
|
2331
|
+
* @returns {Promise<Object>} The dynamic content object with its configuration and items
|
|
2332
|
+
* @throws {Error} 401 - The caller is not authenticated
|
|
2333
|
+
* @throws {Error} 404 - The dynamic content was not found
|
|
2334
|
+
*
|
|
2335
|
+
* @example
|
|
2336
|
+
* const content = await nomadSDK.getDynamicContent("dynamic-123");
|
|
2337
|
+
*
|
|
2338
|
+
* @see {@link https://docs.nomad-cms.com/docs/api-guide/media-api/get-dynamic-content Portal API} GET /media/content/{id}
|
|
2339
|
+
*/
|
|
2340
|
+
async function _getDynamicContent(instance, DYNAMIC_CONTENT_RECORD_ID)
|
|
1415
2341
|
{
|
|
1416
|
-
const API_URL = `${instance.config.serviceApiUrl}/api/media/content/${
|
|
2342
|
+
const API_URL = `${instance.config.serviceApiUrl}/api/media/content/${DYNAMIC_CONTENT_RECORD_ID}`;
|
|
1417
2343
|
|
|
1418
2344
|
return await _sendRequest(instance, "Get Dynamic Content", API_URL, "GET", undefined, undefined);
|
|
1419
2345
|
}
|
|
@@ -1421,7 +2347,25 @@ async function _getDynamicContent(instance, ID)
|
|
|
1421
2347
|
|
|
1422
2348
|
|
|
1423
2349
|
|
|
1424
|
-
|
|
2350
|
+
/**
|
|
2351
|
+
* Retrieve all dynamic content items.
|
|
2352
|
+
*
|
|
2353
|
+
* Gets the list of all dynamic content configurations available for
|
|
2354
|
+
* the portal front-end.
|
|
2355
|
+
*
|
|
2356
|
+
* **Authorization:** Requires a valid Bearer token.
|
|
2357
|
+
*
|
|
2358
|
+
* @param {Object} instance - The SDK instance containing configuration and auth token
|
|
2359
|
+
* @returns {Promise<Object>} Array of dynamic content objects
|
|
2360
|
+
* @throws {Error} 401 - The caller is not authenticated
|
|
2361
|
+
*
|
|
2362
|
+
* @example
|
|
2363
|
+
* const contents = await nomadSDK.getDynamicContents();
|
|
2364
|
+
* contents.forEach(c => console.log(c.id, c.name));
|
|
2365
|
+
*
|
|
2366
|
+
* @see {@link https://docs.nomad-cms.com/docs/api-guide/media-api/get-dynamic-contents Portal API} GET /media/content
|
|
2367
|
+
*/
|
|
2368
|
+
async function _getDynamicContents(instance)
|
|
1425
2369
|
{
|
|
1426
2370
|
const API_URL = `${instance.config.serviceApiUrl}/api/media/content`;
|
|
1427
2371
|
|
|
@@ -1431,12 +2375,38 @@ async function _getDynamicContents(instance)
|
|
|
1431
2375
|
|
|
1432
2376
|
|
|
1433
2377
|
|
|
1434
|
-
|
|
2378
|
+
/**
|
|
2379
|
+
* Retrieve a media group by its unique identifier.
|
|
2380
|
+
*
|
|
2381
|
+
* Gets a media group and its contents with optional content filtering.
|
|
2382
|
+
* Media groups organize content items for portal display.
|
|
2383
|
+
*
|
|
2384
|
+
* **Authorization:** Requires a valid Bearer token.
|
|
2385
|
+
*
|
|
2386
|
+
* @param {Object} instance - The SDK instance containing configuration and auth token
|
|
2387
|
+
* @param {string} MEDIA_GROUP_ID - The unique identifier of the media group
|
|
2388
|
+
* @param {Array<string>} [FILTER_IDS] - Optional array of content IDs to filter results
|
|
2389
|
+
* @param {string} [LANGUAGE_ID] - Optional language ID to localize the response
|
|
2390
|
+
* @returns {Promise<Object>} The media group object with its contents
|
|
2391
|
+
* @throws {Error} 401 - The caller is not authenticated
|
|
2392
|
+
* @throws {Error} 404 - The media group was not found
|
|
2393
|
+
*
|
|
2394
|
+
* @example
|
|
2395
|
+
* const group = await nomadSDK.getMediaGroup("group-123", ["item-1", "item-2"]);
|
|
2396
|
+
*
|
|
2397
|
+
* @see {@link https://docs.nomad-cms.com/docs/api-guide/media-api/get-media-group Portal API} GET /media/group/{id}
|
|
2398
|
+
*/
|
|
2399
|
+
async function _getMediaGroup(instance, MEDIA_GROUP_ID, FILTER_IDS, LANGUAGE_ID)
|
|
1435
2400
|
{
|
|
1436
|
-
let apiUrl = `${instance.config.serviceApiUrl}/api/media/group/${
|
|
2401
|
+
let apiUrl = `${instance.config.serviceApiUrl}/api/media/group/${MEDIA_GROUP_ID}`;
|
|
2402
|
+
|
|
2403
|
+
// filterIds is sent as repeated query keys (filterIds=a&filterIds=b)
|
|
2404
|
+
const QUERY = [];
|
|
1437
2405
|
if (FILTER_IDS && FILTER_IDS.length > 0) {
|
|
1438
|
-
|
|
2406
|
+
QUERY.push(...FILTER_IDS.map(id => `filterIds=${encodeURIComponent(id)}`));
|
|
1439
2407
|
}
|
|
2408
|
+
if (LANGUAGE_ID) QUERY.push(`languageId=${encodeURIComponent(LANGUAGE_ID)}`);
|
|
2409
|
+
if (QUERY.length > 0) apiUrl += '?' + QUERY.join('&');
|
|
1440
2410
|
|
|
1441
2411
|
return await _sendRequest(instance, "Get Media Group", apiUrl, "GET", undefined, undefined);
|
|
1442
2412
|
}
|
|
@@ -1444,9 +2414,29 @@ async function _getMediaGroup(instance, ID, FILTER_IDS)
|
|
|
1444
2414
|
|
|
1445
2415
|
|
|
1446
2416
|
|
|
1447
|
-
|
|
2417
|
+
/**
|
|
2418
|
+
* Retrieve a media item by its unique identifier.
|
|
2419
|
+
*
|
|
2420
|
+
* Gets the full media item details including metadata, content definitions,
|
|
2421
|
+
* and playback information for the portal front-end.
|
|
2422
|
+
*
|
|
2423
|
+
* **Authorization:** Requires a valid Bearer token.
|
|
2424
|
+
*
|
|
2425
|
+
* @param {Object} instance - The SDK instance containing configuration and auth token
|
|
2426
|
+
* @param {string} MEDIA_ITEM_ID - The unique identifier of the media item
|
|
2427
|
+
* @returns {Promise<Object>} The media item object with metadata and content details
|
|
2428
|
+
* @throws {Error} 401 - The caller is not authenticated
|
|
2429
|
+
* @throws {Error} 404 - The media item was not found
|
|
2430
|
+
*
|
|
2431
|
+
* @example
|
|
2432
|
+
* const item = await nomadSDK.getMediaItem("item-123");
|
|
2433
|
+
* console.log(item.title);
|
|
2434
|
+
*
|
|
2435
|
+
* @see {@link https://docs.nomad-cms.com/docs/api-guide/media-api/get-media-item Portal API} GET /media/item/{id}
|
|
2436
|
+
*/
|
|
2437
|
+
async function _getMediaItem(instance, MEDIA_ITEM_ID)
|
|
1448
2438
|
{
|
|
1449
|
-
const API_URL = `${instance.config.serviceApiUrl}/api/media/item/${
|
|
2439
|
+
const API_URL = `${instance.config.serviceApiUrl}/api/media/item/${MEDIA_ITEM_ID}`;
|
|
1450
2440
|
|
|
1451
2441
|
return await _sendRequest(instance, "Get Media Item", API_URL, "GET", undefined, undefined);
|
|
1452
2442
|
}
|
|
@@ -1454,7 +2444,24 @@ async function _getMediaItem(instance, ID)
|
|
|
1454
2444
|
|
|
1455
2445
|
|
|
1456
2446
|
|
|
1457
|
-
|
|
2447
|
+
/**
|
|
2448
|
+
* Retrieve the authenticated user's personal content.
|
|
2449
|
+
*
|
|
2450
|
+
* Gets all content items owned by or assigned to the currently
|
|
2451
|
+
* authenticated user.
|
|
2452
|
+
*
|
|
2453
|
+
* **Authorization:** Requires a valid Bearer token.
|
|
2454
|
+
*
|
|
2455
|
+
* @param {Object} instance - The SDK instance containing configuration and auth token
|
|
2456
|
+
* @returns {Promise<Object>} Array of content items belonging to the user
|
|
2457
|
+
* @throws {Error} 401 - The caller is not authenticated
|
|
2458
|
+
*
|
|
2459
|
+
* @example
|
|
2460
|
+
* const myContent = await nomadSDK.getMyContent();
|
|
2461
|
+
*
|
|
2462
|
+
* @see {@link https://docs.nomad-cms.com/docs/api-guide/media-api/get-my-content Portal API} GET /media/my-content
|
|
2463
|
+
*/
|
|
2464
|
+
async function _getMyContent(instance)
|
|
1458
2465
|
{
|
|
1459
2466
|
const API_URL = `${instance.config.serviceApiUrl}/api/media/my-content`;
|
|
1460
2467
|
|
|
@@ -1464,9 +2471,34 @@ async function _getMyContent(instance)
|
|
|
1464
2471
|
|
|
1465
2472
|
|
|
1466
2473
|
|
|
1467
|
-
|
|
2474
|
+
/**
|
|
2475
|
+
* Retrieve a personal content group by its unique identifier.
|
|
2476
|
+
*
|
|
2477
|
+
* Gets a content group from the user's personal groups, including its
|
|
2478
|
+
* contents and configuration.
|
|
2479
|
+
*
|
|
2480
|
+
* **Authorization:** Requires a valid Bearer token.
|
|
2481
|
+
*
|
|
2482
|
+
* @param {Object} instance - The SDK instance containing configuration and auth token
|
|
2483
|
+
* @param {string} GROUP_ID - The unique identifier of the personal group
|
|
2484
|
+
* @param {Array<string>} [FILTER_IDS] - Optional array of content IDs to filter results
|
|
2485
|
+
* @returns {Promise<Object>} The personal group object with its contents
|
|
2486
|
+
* @throws {Error} 401 - The caller is not authenticated
|
|
2487
|
+
* @throws {Error} 404 - The group was not found
|
|
2488
|
+
*
|
|
2489
|
+
* @example
|
|
2490
|
+
* const group = await nomadSDK.getMyGroup("group-123");
|
|
2491
|
+
*
|
|
2492
|
+
* @see {@link https://docs.nomad-cms.com/docs/api-guide/media-api/get-my-group Portal API} GET /media/my-group/{id}
|
|
2493
|
+
*/
|
|
2494
|
+
async function _getMyGroup(instance, GROUP_ID, FILTER_IDS)
|
|
1468
2495
|
{
|
|
1469
|
-
|
|
2496
|
+
let API_URL = `${instance.config.serviceApiUrl}/api/media/my-group/${GROUP_ID}`;
|
|
2497
|
+
|
|
2498
|
+
// filterIds is sent as repeated query keys (filterIds=a&filterIds=b)
|
|
2499
|
+
if (FILTER_IDS && FILTER_IDS.length > 0) {
|
|
2500
|
+
API_URL += '?' + FILTER_IDS.map(id => `filterIds=${encodeURIComponent(id)}`).join('&');
|
|
2501
|
+
}
|
|
1470
2502
|
|
|
1471
2503
|
return await _sendRequest(instance, "Get My Group", API_URL, "GET", undefined, undefined);
|
|
1472
2504
|
}
|
|
@@ -1474,9 +2506,28 @@ async function _getMyGroup(instance, ID)
|
|
|
1474
2506
|
|
|
1475
2507
|
|
|
1476
2508
|
|
|
1477
|
-
|
|
2509
|
+
/**
|
|
2510
|
+
* Retrieve a site configuration by its unique identifier.
|
|
2511
|
+
*
|
|
2512
|
+
* Gets the portal site configuration including theme, layout, navigation,
|
|
2513
|
+
* and content display settings.
|
|
2514
|
+
*
|
|
2515
|
+
* **Authorization:** Requires a valid Bearer token.
|
|
2516
|
+
*
|
|
2517
|
+
* @param {Object} instance - The SDK instance containing configuration and auth token
|
|
2518
|
+
* @param {string} SITE_CONFIG_RECORD_ID - The unique identifier of the site configuration
|
|
2519
|
+
* @returns {Promise<Object>} The site configuration object
|
|
2520
|
+
* @throws {Error} 401 - The caller is not authenticated
|
|
2521
|
+
* @throws {Error} 404 - The site configuration was not found
|
|
2522
|
+
*
|
|
2523
|
+
* @example
|
|
2524
|
+
* const config = await nomadSDK.getSiteConfig("config-123");
|
|
2525
|
+
*
|
|
2526
|
+
* @see {@link https://docs.nomad-cms.com/docs/api-guide/media-api/get-site-config Portal API} GET /media/config/{id}
|
|
2527
|
+
*/
|
|
2528
|
+
async function _getSiteConfig(instance, SITE_CONFIG_RECORD_ID)
|
|
1478
2529
|
{
|
|
1479
|
-
const API_URL = `${instance.config.serviceApiUrl}/api/media/config/${
|
|
2530
|
+
const API_URL = `${instance.config.serviceApiUrl}/api/media/config/${SITE_CONFIG_RECORD_ID}`;
|
|
1480
2531
|
|
|
1481
2532
|
return await _sendRequest(instance, "Get Site Config", API_URL, "GET", undefined, undefined);
|
|
1482
2533
|
}
|
|
@@ -1484,19 +2535,43 @@ async function _getSiteConfig(instance, ID)
|
|
|
1484
2535
|
|
|
1485
2536
|
|
|
1486
2537
|
|
|
1487
|
-
|
|
1488
|
-
|
|
2538
|
+
/**
|
|
2539
|
+
* Search for media content using a query string and optional filters.
|
|
2540
|
+
*
|
|
2541
|
+
* Performs a search against the media index with support for pagination,
|
|
2542
|
+
* sorting, and ID filtering. Defaults to page offset 0 and page size 100.
|
|
2543
|
+
*
|
|
2544
|
+
* **Authorization:** Requires a valid Bearer token.
|
|
2545
|
+
*
|
|
2546
|
+
* @param {Object} instance - The SDK instance containing configuration and auth token
|
|
2547
|
+
* @param {Object} QUERY - The search query object containing search criteria
|
|
2548
|
+
* @param {Array<string>} [IDS] - Optional array of specific content IDs to filter by
|
|
2549
|
+
* @param {Array<Object>} [SORT_FIELDS] - Optional array of sort field objects
|
|
2550
|
+
* @param {number} [OFFSET=0] - The page offset for pagination (defaults to 0)
|
|
2551
|
+
* @param {number} [SIZE=100] - The number of results per page (defaults to 100)
|
|
2552
|
+
* @returns {Promise<Object>} Search results containing items array and pagination info
|
|
2553
|
+
* @throws {Error} 401 - The caller is not authenticated
|
|
2554
|
+
*
|
|
2555
|
+
* @example
|
|
2556
|
+
* const results = await nomadSDK.mediaSearch(
|
|
2557
|
+
* { query: "news" }, null, [{ fieldName: "date", order: "desc" }], 0, 25
|
|
2558
|
+
* );
|
|
2559
|
+
*
|
|
2560
|
+
* @see {@link https://docs.nomad-cms.com/docs/api-guide/media-api/media-search Portal API} POST /media/search
|
|
2561
|
+
*/
|
|
2562
|
+
async function _mediaSearch(instance, QUERY, IDS, SORT_FIELDS,
|
|
2563
|
+
OFFSET, SIZE)
|
|
1489
2564
|
{
|
|
1490
2565
|
const API_URL = `${instance.config.serviceApiUrl}/api/media/search`;
|
|
1491
2566
|
|
|
1492
2567
|
const BODY = {
|
|
1493
|
-
searchQuery:
|
|
2568
|
+
searchQuery: QUERY,
|
|
1494
2569
|
ids: IDS,
|
|
1495
2570
|
sortFields: SORT_FIELDS
|
|
1496
2571
|
};
|
|
1497
2572
|
|
|
1498
|
-
|
|
1499
|
-
|
|
2573
|
+
OFFSET ? BODY["pageOffset"] = OFFSET : BODY["pageOffset"] = 0;
|
|
2574
|
+
SIZE ? BODY["pageSize"] = SIZE : BODY["pageSize"] = 100;
|
|
1500
2575
|
|
|
1501
2576
|
return await _sendRequest(instance, "Media Search", API_URL, "POST", undefined, BODY);
|
|
1502
2577
|
}
|