@adobe/acc-js-sdk 1.1.24 → 1.1.26

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.
@@ -2,9 +2,33 @@
2
2
  layout: page
3
3
  title: Change Log
4
4
  ---
5
- <section class="changelog"><h1>Version 1.1.24</h1>
5
+
6
+ <section class="changelog"><h1>Version 1.1.26</h1>
7
+ <h2>2023/04/17</h2>
8
+
9
+ <li>
10
+ Added support for IMS Bearer tokens without require any session and security tokens. See <a href="https://opensource.adobe.com/acc-js-sdk/connecting.html"> for more details.</a>
11
+ </li>
12
+ <li>
13
+ Fixed a bug causing incorrect JSON to XML transformation when the JSON object has a property named 'length'
14
+ </li>
15
+ </section>
16
+
17
+ <section class="changelog"><h1>Version 1.1.25</h1>
6
18
  <h2>2023/03/07</h2>
7
19
 
20
+ <li>
21
+ Added an (optional) parameter "options" to the file upload function. This parameter contains an "action" property whose value
22
+ can be "publishIfNeeded" or "none" and indicates which action the upload function should perform after it uploaded the file.
23
+ The default is "publishIfNeeded" which consists of creating a public resources with the file content and publishing it (making
24
+ it available publicly). The other action "none" means that no action is taken and that it is the responsibility of the caller
25
+ to post-process the uploaded file by calling the relevant APIs.
26
+ </li>
27
+ </section>
28
+
29
+ <section class="changelog"><h1>Version 1.1.24</h1>
30
+ <h2>2023/04/07</h2>
31
+
8
32
  <li>
9
33
  Added support for abortable requests. See <a href="https://opensource.adobe.com/acc-js-sdk/abortRequest.html"> for more details.</a>
10
34
  </li>
@@ -51,19 +51,58 @@ await client.logoff();
51
51
  <li>Informations about the connected user, and it's privileges</li>
52
52
  </ul>
53
53
 
54
- <p>T
55
- he most convenient way to access this information is by using the <a href="{{ site.baseurl }}/application.html">Application object</a>, but you can
54
+ <p>The most convenient way to access this information is by using the <a href="{{ site.baseurl }}/application.html">Application object</a>, but you can
56
55
  also use the <b>client.getSessionInfo().serverInfo</b> and <b>client.getSessionInfo().sessionInfo</b> calls.
57
56
  </p>
58
57
 
58
+ <p class="info">Note: depending on the type of credentials used, user and server info may not be returned.
59
+ </p>
59
60
 
61
+ <table>
62
+ <thead>
63
+ <tr>
64
+ <th>Credentials Type</th>
65
+ <th>Returns info</th>
66
+ </tr>
67
+ </thead>
68
+ <tbody>
69
+ <tr>
70
+ <td>UserPassword</td>
71
+ <td>Yes</td>
72
+ </tr>
73
+ <tr>
74
+ <td>ImsServiceToken</td>
75
+ <td>Yes</td>
76
+ </tr>
77
+ <tr>
78
+ <td>SessionToken</td>
79
+ <td>No</td>
80
+ </tr>
81
+ <tr>
82
+ <td>AnonymousUser</td>
83
+ <td>No</td>
84
+ </tr>
85
+ <tr>
86
+ <td>SecurityToken</td>
87
+ <td>Yes</td>
88
+ </tr>
89
+ <tr>
90
+ <td>BearerToken</td>
91
+ <td>Yes</td>
92
+ </tr>
93
+ <tr>
94
+ <td>ImsBearerToken</td>
95
+ <td>No</td>
96
+ </tr>
97
+ </tbody>
98
+ </table>
60
99
 
61
100
  <h1>Credentials</h1>
62
101
 
63
102
  <p>
64
103
  There are several methods of the <b>sdk.ConnectionParameters</b> depending on the type of authentication you want to use. They are
65
104
  described below.
66
- </p>>
105
+ </p>
67
106
 
68
107
  <h2>Login with user and password</h2>
69
108
  <p>This is the most common method to log in to Campaign. User the <b>ofUserAndPassword</b> function and pass it the user name and password</p>
@@ -75,7 +114,26 @@ const connectionParameters = sdk.ConnectionParameters.ofUserAndPassword(
75
114
 
76
115
 
77
116
  <h2>Login with IMS access token</h2>
78
- <p>The SDK supports IMS access token of an IMS user with the <b>ofBearerToken</b> function. Pass it a bearer token.</p>
117
+
118
+ <p>In Campaign 8.5 and above, native support for IMS bearer tokens is avaiable and can be used in the SDK as the preferred connection method.</p>
119
+ <pre class="code">
120
+ const connectionParameters = sdk.ConnectionParameters.ofImsBearerToken(
121
+ "https://myInstance.campaign.adobe.com",
122
+ "ims_bearer_token");
123
+ </pre>
124
+
125
+
126
+ <p class="info">Note: You still need to call Logon
127
+ </p>
128
+
129
+ <p>With this authentication method, an IMS Bearer token is obtained outside of Campaign, for example using IMS APIs and is passed directly to
130
+ all Campaign APIs. Campaigns itslef will verify the token and grant the corresponding access.
131
+ </p>
132
+
133
+
134
+ <p>For older versions of Campaign, you can still use IMS tokens with the <b>ofBearerToken</b> function. The difference between ofImsBearerToken and ofBearerToken
135
+ is that ofBearerToken will internally call "xtk:session#BearerTokenLogon" to exchange the IMS token for Campaign session and security tokens. Subsequent
136
+ API calls will use Campaign tokens. It's recommended to us <b>ofImsBearerToken</b> logon whenever possible.</p>
79
137
  <pre class="code">
80
138
  const connectionParameters = sdk.ConnectionParameters.ofBearerToken(
81
139
  "https://myInstance.campaign.adobe.com",
package/docs/samples.html CHANGED
@@ -3,7 +3,7 @@ layout: page
3
3
  title: Sample Code
4
4
  ---
5
5
 
6
- <p>The <b>samples</b> folder contains several samples illustrating how to use the various Campaing APIs.</p>
6
+ <p>The <b>samples</b> folder contains several samples illustrating how to use the various Campaign APIs.</p>
7
7
 
8
8
  <p>A sample file looks like this</p>
9
9
 
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@adobe/acc-js-sdk",
3
- "version": "1.1.24",
3
+ "version": "1.1.26",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@adobe/acc-js-sdk",
9
- "version": "1.1.24",
9
+ "version": "1.1.26",
10
10
  "license": "ISC",
11
11
  "dependencies": {
12
12
  "axios": "^1.2.1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/acc-js-sdk",
3
- "version": "1.1.24",
3
+ "version": "1.1.26",
4
4
  "description": "ACC Javascript SDK",
5
5
  "main": "src/index.js",
6
6
  "homepage": "https://github.com/adobe/acc-js-sdk#readme",
package/src/client.js CHANGED
@@ -11,7 +11,7 @@ governing permissions and limitations under the License.
11
11
  */
12
12
  (function() {
13
13
  "use strict";
14
-
14
+ /*jshint sub:true*/
15
15
 
16
16
  /**********************************************************************************
17
17
  *
@@ -244,12 +244,12 @@ class Credentials {
244
244
  */
245
245
  constructor(type, sessionToken, securityToken) {
246
246
  if (type != "UserPassword" && type != "ImsServiceToken" && type != "SessionToken" &&
247
- type != "AnonymousUser" && type != "SecurityToken" && type != "BearerToken")
247
+ type != "AnonymousUser" && type != "SecurityToken" && type != "BearerToken" && type != "ImsBearerToken")
248
248
  throw CampaignException.INVALID_CREDENTIALS_TYPE(type);
249
249
  this._type = type;
250
250
  this._sessionToken = sessionToken || "";
251
251
  this._securityToken = securityToken || "";
252
- if (type == "BearerToken") {
252
+ if (type == "BearerToken" || type === "ImsBearerToken") {
253
253
  this._bearerToken = sessionToken || "";
254
254
  this._sessionToken = "";
255
255
  }
@@ -397,8 +397,13 @@ class ConnectionParameters {
397
397
  }
398
398
 
399
399
  /**
400
- * Creates connection parameters for a Campaign instance from bearer token
401
- *
400
+ * Creates connection parameters for a Campaign instance from bearer token.
401
+ * This authentication method uses an IMS Bearer token and calls the xtk:session#BearerTokenLogin method
402
+ * which will exchange the IMS bearer token with Campaign session and security tokens.
403
+ * This is a legacy method. Campaign 8.5 will has an authentication method which allows to directly
404
+ * use the IMS bearer token and which is recommended.
405
+ * To avoid an extra call to "BearerTokenLogin", use the "ofImsBearerToken" method.
406
+ *
402
407
  * @param {string} endpoint The campaign endpoint (URL)
403
408
  * @param {string} bearerToken IMS bearer token
404
409
  * @param {*} options connection options
@@ -408,6 +413,23 @@ class ConnectionParameters {
408
413
  const credentials = new Credentials("BearerToken", bearerToken);
409
414
  return new ConnectionParameters(endpoint, credentials, options);
410
415
  }
416
+
417
+ /**
418
+ * Creates connection parameters for a Campaign instance from a IMS bearer token.
419
+ * This authentication method does not require exchange the IMS token with session and security tokens
420
+ * and only works in ACC 8.5 and above.
421
+ * For older version of ACC or to use session/seurity tokens, use the "ofBearerToken" method.
422
+ *
423
+ * @param {string} endpoint The campaign endpoint (URL)
424
+ * @param {string} bearerToken IMS bearer token
425
+ * @param {*} options connection options
426
+ * @returns {ConnectionParameters} a ConnectionParameters object which can be used to create a Client
427
+ */
428
+ static ofImsBearerToken(endpoint, bearerToken, options) {
429
+ const credentials = new Credentials("ImsBearerToken", bearerToken);
430
+ return new ConnectionParameters(endpoint, credentials, options);
431
+ }
432
+
411
433
  /**
412
434
  * Creates connection parameters for a Campaign instance, using an IMS service token and a user name (the user to impersonate)
413
435
  *
@@ -520,6 +542,13 @@ class ConnectionParameters {
520
542
  // File Uploader
521
543
  // ========================================================================================
522
544
 
545
+ /**
546
+ * @typedef {Object} FileUploadOptions
547
+ * @property {"publishIfNeeded"|"none"|undefined} the post-processing action to execute. Defaults to "publishIfNeeded"
548
+ * @memberOf Campaign
549
+ */
550
+
551
+
523
552
  /**
524
553
  * File Uploader API for JS SDK(Currently available only in browsers)
525
554
  * @private
@@ -535,26 +564,28 @@ const fileUploader = (client) => {
535
564
  * This is the exposed/public method for fileUploader instance which will do all the processing related to the upload process internally and returns the promise containing all the required data.
536
565
  * @ignore
537
566
  * @param file, where file is an instance of [File](https://developer.mozilla.org/en-US/docs/Web/API/File)
567
+ * @param {FileUploadOptions|undefined} options
538
568
  * @returns {Promise<{name: string, md5: string, type: string, size: string, url: string}>}
539
569
  */
540
- upload: (file) => {
570
+ upload: (file, options) => {
541
571
  console.log(`fileuploader.upload is an experimental feature and is not currently fully functional. It is work in progress and will change in the future.`);
542
572
  return new Promise((resolve, reject) => {
573
+ const action = (options && options.action) ? options.action : "publishIfNeeded";
574
+ if (action !== "publishIfNeeded" && action !== "none")
575
+ reject(CampaignException.BAD_PARAMETER("action", action, "The 'action' parameter of the upload API should be 'publishIfNeeded' or 'none'"));
543
576
  try {
544
577
  if (!Util.isBrowser()) {
545
578
  throw 'File uploading is only supported in browser based calls.';
546
579
  }
547
580
  const data = new FormData();
548
581
  data.append('file_noMd5', file);
582
+ const headers = client._getAuthHeaders(false);
549
583
  client._makeHttpCall({
550
584
  url: `${client._connectionParameters._endpoint}/nl/jsp/uploadFile.jsp`,
551
585
  processData: false,
552
586
  method: 'POST',
553
587
  data: data,
554
- headers: {
555
- 'X-Security-Token': client._securityToken,
556
- 'X-Session-Token': client._sessionToken,
557
- }
588
+ headers: headers
558
589
  }).then((okay) => {
559
590
  if (!okay.startsWith('Ok')) {
560
591
  throw okay;
@@ -569,28 +600,31 @@ const fileUploader = (client) => {
569
600
  // https://git.corp.adobe.com/Campaign/ac/blob/v6-master/wpp/xtk/web/dce/uploader.js
570
601
  return reject(CampaignException.FILE_UPLOAD_FAILED(file.name, 'Malformed data:' + data.toString()));
571
602
  }
572
- const counter = await client.NLWS.xtkCounter.increaseValue({name: 'xtkResource'});
573
- const fileRes= {
574
- internalName: 'RES' + counter,
575
- md5: data[0].md5,
576
- label: data[0].fileName,
577
- fileName: data[0].fileName,
578
- originalName: data[0].fileName,
579
- useMd5AsFilename: '1',
580
- storageType: 5,
581
- xtkschema: 'xtk:fileRes'
582
-
583
- };
584
- await client.NLWS.xtkSession.write(fileRes);
585
- await client.NLWS.xtkFileRes.create(fileRes).publishIfNeeded();
586
- const url = await client.NLWS.xtkFileRes.create(fileRes).getURL();
587
- resolve({
603
+ const result = {
588
604
  name: data[0].fileName,
589
605
  md5: data[0].md5,
590
606
  type: file.type,
591
607
  size: file.size,
592
- url: url
593
- });
608
+ };
609
+ if (action === "publishIfNeeded") {
610
+ const counter = await client.NLWS.xtkCounter.increaseValue({name: 'xtkResource'});
611
+ const fileRes= {
612
+ internalName: 'RES' + counter,
613
+ md5: data[0].md5,
614
+ label: data[0].fileName,
615
+ fileName: data[0].fileName,
616
+ originalName: data[0].fileName,
617
+ useMd5AsFilename: '1',
618
+ storageType: 5,
619
+ xtkschema: 'xtk:fileRes'
620
+
621
+ };
622
+ await client.NLWS.xtkSession.write(fileRes);
623
+ await client.NLWS.xtkFileRes.create(fileRes).publishIfNeeded();
624
+ const url = await client.NLWS.xtkFileRes.create(fileRes).getURL();
625
+ result.url = url;
626
+ }
627
+ resolve(result);
594
628
  }
595
629
  };
596
630
  const html = `<body>${okay}</body>`;
@@ -631,13 +665,28 @@ class Client {
631
665
  */
632
666
  constructor(sdk, connectionParameters) {
633
667
  this.sdk = sdk;
668
+ this.reinit(connectionParameters);
669
+
670
+ this._observers = [];
671
+ this._cacheChangeListeners = [];
672
+ }
673
+
674
+ /**
675
+ * Re-initialize a client with new connection parameters.
676
+ * Typically called from the refreshClient callback after a connection expires.
677
+ * Conserves observers
678
+ *
679
+ * @param {Campaign.ConnectionParameters} user user name, for instance admin
680
+ */
681
+ reinit(connectionParameters) {
634
682
  this._connectionParameters = connectionParameters; // ## TODO security concern (password kept in memory)
635
683
  this._representation = connectionParameters._options.representation;
636
684
 
637
685
  this._sessionInfo = undefined;
638
686
  this._sessionToken = undefined;
639
687
  this._securityToken = undefined;
640
- this._installedPackages = {}; // package set (key and value = package id, ex: "nms:amp")
688
+ this._bearerToken = undefined; // set when using Bearer authentication and "ImsBearer" credential type
689
+ this._installedPackages = {}; // package set (key and value = package id, ex: "nms:amp")
641
690
 
642
691
  this._secretKeyCipher = undefined;
643
692
 
@@ -653,7 +702,7 @@ class Client {
653
702
  const rootKeyType = connectionParameters._options.cacheRootKey;
654
703
  let rootKey = "";
655
704
  if (!rootKeyType || rootKeyType === "default")
656
- rootKey = `acc.js.sdk.${sdk.getSDKVersion().version}.${instanceKey}.cache.`;
705
+ rootKey = `acc.js.sdk.${this.sdk.getSDKVersion().version}.${instanceKey}.cache.`;
657
706
 
658
707
  // Clear storage cache if the sdk versions or the instances are different
659
708
  if (this._storage && typeof this._storage.removeItem === 'function') {
@@ -673,8 +722,6 @@ class Client {
673
722
 
674
723
  this._transport = connectionParameters._options.transport;
675
724
  this._traceAPICalls = connectionParameters._options.traceAPICalls;
676
- this._observers = [];
677
- this._cacheChangeListeners = [];
678
725
  this._refreshClient = connectionParameters._options.refreshClient;
679
726
 
680
727
  // expose utilities
@@ -735,6 +782,26 @@ class Client {
735
782
  return `${version.name}/${version.version} ${version.description}`;
736
783
  }
737
784
 
785
+ /**
786
+ * Get HTTP authentication headers
787
+ * @param
788
+ * @returns {Object} the headers
789
+ */
790
+ _getAuthHeaders(setCookie) {
791
+ const headers = {};
792
+ if (this._bearerToken) {
793
+ headers['Authorization'] = `Bearer ${this._bearerToken}`;
794
+ }
795
+ else {
796
+ headers['X-Security-Token'] = this._securityToken;
797
+ headers['X-Session-Token'] = this._sessionToken;
798
+ if (setCookie) {
799
+ headers['Cookie'] = '__sessiontoken=' + this._sessionToken;
800
+ }
801
+ }
802
+ return headers;
803
+ }
804
+
738
805
  /**
739
806
  * Convert an XML object into a representation
740
807
  *
@@ -915,6 +982,9 @@ class Client {
915
982
  const credentialsType = this._connectionParameters._credentials._type;
916
983
  if (credentialsType == "AnonymousUser")
917
984
  return true;
985
+ else if (credentialsType == "ImsBearerToken") {
986
+ return !!this._bearerToken;
987
+ }
918
988
 
919
989
  // When using bearer token authentication we are considered logged only after
920
990
  // the bearer token has been converted into session token and security token
@@ -953,7 +1023,8 @@ class Client {
953
1023
  this._sessionToken, this._securityToken,
954
1024
  this._getUserAgentString(),
955
1025
  Object.assign({}, this._connectionParameters._options, pushDownOptions),
956
- extraHttpHeaders);
1026
+ extraHttpHeaders,
1027
+ this._bearerToken);
957
1028
  soapCall.internal = !!internal;
958
1029
  soapCall.isStatic = isStatic;
959
1030
  return soapCall;
@@ -1310,6 +1381,7 @@ class Client {
1310
1381
  this.application = null;
1311
1382
  this._sessionToken = "";
1312
1383
  this._securityToken = "";
1384
+ this._bearerToken = undefined;
1313
1385
  const credentials = this._connectionParameters._credentials;
1314
1386
 
1315
1387
  const sdkVersion = this.sdk.getSDKVersion();
@@ -1335,6 +1407,7 @@ class Client {
1335
1407
  that._installedPackages = {};
1336
1408
  that._sessionToken = credentials._sessionToken;
1337
1409
  that._securityToken = "";
1410
+ that._bearerToken = undefined;
1338
1411
  that._onLogon();
1339
1412
  return Promise.resolve();
1340
1413
  }
@@ -1343,6 +1416,16 @@ class Client {
1343
1416
  that._installedPackages = {};
1344
1417
  that._sessionToken = "";
1345
1418
  that._securityToken = credentials._securityToken;
1419
+ that._bearerToken = undefined;
1420
+ that._onLogon();
1421
+ return Promise.resolve();
1422
+ }
1423
+ else if (credentials._type == "ImsBearerToken") {
1424
+ that._sessionInfo = undefined;
1425
+ that._installedPackages = {};
1426
+ that._sessionToken = "";
1427
+ that._securityToken = "";
1428
+ that._bearerToken = credentials._bearerToken;
1346
1429
  that._onLogon();
1347
1430
  return Promise.resolve();
1348
1431
  }
@@ -1393,6 +1476,7 @@ class Client {
1393
1476
  // store member variables after all parameters are decode the ensure atomicity
1394
1477
  that._sessionToken = sessionToken;
1395
1478
  that._securityToken = securityToken;
1479
+ that._bearerToken = undefined;
1396
1480
 
1397
1481
  that._onLogon();
1398
1482
  });
@@ -1430,6 +1514,7 @@ class Client {
1430
1514
  return this._makeSoapCall(soapCall).then(function() {
1431
1515
  that._sessionToken = "";
1432
1516
  that._securityToken = "";
1517
+ that._bearerToken = undefined;
1433
1518
  that.application = null;
1434
1519
  soapCall.checkNoMoreArgs();
1435
1520
  });
@@ -1437,6 +1522,7 @@ class Client {
1437
1522
  else {
1438
1523
  that._sessionToken = "";
1439
1524
  that._securityToken = "";
1525
+ that._bearerToken = undefined;
1440
1526
  that.application = null;
1441
1527
  }
1442
1528
  } finally {
@@ -1900,13 +1986,10 @@ class Client {
1900
1986
  * @returns {Campaign.PingStatus} an object describing the server status
1901
1987
  */
1902
1988
  async ping() {
1989
+ const headers = this._getAuthHeaders(true);
1903
1990
  const request = {
1904
1991
  url: `${this._connectionParameters._endpoint}/nl/jsp/ping.jsp`,
1905
- headers: {
1906
- 'X-Security-Token': this._securityToken,
1907
- 'X-Session-Token': this._sessionToken,
1908
- 'Cookie': '__sessiontoken=' + this._sessionToken
1909
- }
1992
+ headers: headers,
1910
1993
  };
1911
1994
  for (let h in this._connectionParameters._options.extraHttpHeaders)
1912
1995
  request.headers[h] = this._connectionParameters._options.extraHttpHeaders[h];
@@ -1939,14 +2022,12 @@ class Client {
1939
2022
  callContext.formData.ctx = DomUtil.toXMLString(xmlCtx);
1940
2023
  }
1941
2024
  const selectionCount = callContext.selection.split(',').length;
1942
-
2025
+
2026
+ const headers = this._getAuthHeaders(false);
2027
+ headers['Content-Type'] = 'application/x-www-form-urlencoded';
1943
2028
  const request = {
1944
2029
  url: `${this._connectionParameters._endpoint}/report/${callContext.reportName}?${encodeURI(`_noRender=true&_schema=${callContext.schema}&_context=${callContext.context}&_selection=${callContext.selection}`)}&_selectionCount=${selectionCount}`,
1945
- headers: {
1946
- 'X-Security-Token': this._securityToken,
1947
- 'X-Session-Token': this._sessionToken,
1948
- 'Content-Type': 'application/x-www-form-urlencoded'
1949
- },
2030
+ headers: headers,
1950
2031
  method: 'POST',
1951
2032
  data : qsStringify(callContext.formData)
1952
2033
  };
@@ -1971,13 +2052,10 @@ class Client {
1971
2052
  * @returns {Campaign.McPingStatus} an object describing Message Center server status
1972
2053
  */
1973
2054
  async mcPing() {
2055
+ const headers = this._getAuthHeaders(true);
1974
2056
  const request = {
1975
2057
  url: `${this._connectionParameters._endpoint}/nl/jsp/mcPing.jsp`,
1976
- headers: {
1977
- 'X-Security-Token': this._securityToken,
1978
- 'X-Session-Token': this._sessionToken,
1979
- 'Cookie': '__sessiontoken=' + this._sessionToken
1980
- }
2058
+ headers: headers
1981
2059
  };
1982
2060
  for (let h in this._connectionParameters._options.extraHttpHeaders)
1983
2061
  request.headers[h] = this._connectionParameters._options.extraHttpHeaders[h];
package/src/domUtil.js CHANGED
@@ -341,20 +341,18 @@ class DomUtil {
341
341
  xmlElement.textContent = value;
342
342
  xmlRoot.appendChild(xmlElement);
343
343
  }
344
- else if (t == "object") {
345
- if (value.length !== undefined && value.length !== null) {
346
- for (var i=0; i<value.length; i++) {
347
- const xmlElement = doc.createElement(att);
348
- this._fromJSON(doc, xmlElement, value[i], flavor);
349
- xmlRoot.appendChild(xmlElement);
350
- }
351
- }
352
- else {
344
+ else if (Util.isArray(value)) {
345
+ for (var i=0; i<value.length; i++) {
353
346
  const xmlElement = doc.createElement(att);
354
- this._fromJSON(doc, xmlElement, value, flavor);
347
+ this._fromJSON(doc, xmlElement, value[i], flavor);
355
348
  xmlRoot.appendChild(xmlElement);
356
349
  }
357
350
  }
351
+ else if (t == "object") {
352
+ const xmlElement = doc.createElement(att);
353
+ this._fromJSON(doc, xmlElement, value, flavor);
354
+ xmlRoot.appendChild(xmlElement);
355
+ }
358
356
  else
359
357
  throw new DomException(`Cannot cast JSON to XML: element '${att}' type '${t}' is unknown or not supported yet`);
360
358
  }
package/src/soap.js CHANGED
@@ -11,7 +11,7 @@ governing permissions and limitations under the License.
11
11
  */
12
12
  (function() {
13
13
  "use strict";
14
-
14
+ /*jshint sub:true*/
15
15
 
16
16
  /**********************************************************************************
17
17
  *
@@ -80,11 +80,12 @@ const NS_XSD = "http://www.w3.org/2001/XMLSchema";
80
80
  * @param {string} userAgentString The user agent string to use for HTTP requests
81
81
  * @param {string} pushDownOptions Options to push down to the request (comes from connectionParameters._options)
82
82
  * @param {{ name:string, value:string}} extraHttpHeaders key/value pair of HTTP header (will override any other headers)
83
+ * @param {string} bearerToken The bearer token to use for HTTP requests. Only required for ImsBearerToken authentication
83
84
  * @memberof SOAP
84
85
  */
85
86
  class SoapMethodCall {
86
87
 
87
- constructor(transport, urn, methodName, sessionToken, securityToken, userAgentString, pushDownOptions, extraHttpHeaders) {
88
+ constructor(transport, urn, methodName, sessionToken, securityToken, userAgentString, pushDownOptions, extraHttpHeaders, bearerToken) {
88
89
  this.request = undefined; // The HTTP request (object literal passed to the transport layer)
89
90
  this.requestOptions = undefined;
90
91
  this.response = undefined; // The HTTP response object (in case of success)
@@ -103,6 +104,7 @@ class SoapMethodCall {
103
104
 
104
105
  this._sessionToken = sessionToken || "";
105
106
  this._securityToken = securityToken || "";
107
+ this._bearerToken = bearerToken; // may be undefined if not using bearer token authentication
106
108
  this._userAgentString = userAgentString;
107
109
  this._pushDownOptions = pushDownOptions || {};
108
110
  this._charset = this._pushDownOptions.charset || '';
@@ -538,9 +540,14 @@ class SoapMethodCall {
538
540
  const headers = {
539
541
  'Content-type': `application/soap+xml${this._charset ? ";charset=" + this._charset : ""}`,
540
542
  'SoapAction': `${this.urn}#${this.methodName}`,
541
- 'X-Security-Token': this._securityToken,
542
- 'X-Session-Token': this._sessionToken,
543
543
  };
544
+ if (this._bearerToken) {
545
+ headers['Authorization'] = `Bearer ${this._bearerToken}`;
546
+ }
547
+ else {
548
+ headers['X-Security-Token'] = this._securityToken;
549
+ headers['X-Session-Token'] = this._sessionToken;
550
+ }
544
551
 
545
552
  // Add HTTP headers specific to the SOAP call for better tracing/troubleshooting
546
553
  if (this._extraHttpHeaders && this._extraHttpHeaders['ACC-SDK-Version']) {
@@ -579,6 +586,7 @@ class SoapMethodCall {
579
586
  if (client) {
580
587
  this._sessionToken = client._sessionToken;
581
588
  this._securityToken = client._securityToken;
589
+ this._bearerToken = client._bearerToken;
582
590
  }
583
591
 
584
592
  var cookieHeader = DomUtil.findElement(this._header, "Cookie");
package/src/util.js CHANGED
@@ -51,18 +51,7 @@ class Util {
51
51
  * @returns {boolean} true if the object is an array
52
52
  */
53
53
  static isArray(obj) {
54
- if (obj === null || obj === undefined) return false;
55
- // JavaScript arrays are objects
56
- if (typeof obj != "object") return false;
57
- // They also have a length property. But checking the length is not enough
58
- // since, it can also be an object literal with a "length" property. Campaign
59
- // schema attributes typically have a "length" attribute and are not arrays
60
- if (obj.length === undefined || obj.length === null) return false;
61
- // So check for a "push" function
62
- if (obj.push === undefined || obj.push === null) return false;
63
- if (typeof obj.push != "function")
64
- return false;
65
- return true;
54
+ return Array.isArray(obj);
66
55
  }
67
56
 
68
57
  // Helper function for trim() to replace text between 2 indices
@@ -3511,8 +3511,115 @@ describe('ACC Client', function () {
3511
3511
  }).catch((ex) => {
3512
3512
  expect(ex.message).toMatch('500 - Error 16384: SDK-000013 "Failed to upload file abcd.txt. Malformed data:');
3513
3513
  })
3514
+ });
3514
3515
 
3515
- })
3516
+ it("Should support 'publishIfNeeded' action", async () => {
3517
+ // Create a mock client and logon
3518
+ const client = await Mock.makeClient();
3519
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
3520
+ await client.NLWS.xtkSession.logon();
3521
+
3522
+ // Mock the upload protocol
3523
+ // - the upload.jsp (which returns the content of an iframe and JS to eval)
3524
+ // - call to xtk:counter#IncreaseValue (first, retrieve the schema xtk:counter then call the function)
3525
+ // - call to xtk:session#Write
3526
+ // - call to xtk:fileRes#PublishIfNeeded
3527
+ // - call to xtk:fileRes#GetURL
3528
+
3529
+ client._transport.mockReturnValueOnce(Promise.resolve(`Ok
3530
+ <html xmlns="http://www.w3.org/1999/xhtml">
3531
+ <head>
3532
+ <script type="text/javascript">if(window.parent&&window.parent.document.controller&&"function"==typeof window.parent.document.controller.uploadFileCallBack){var aFilesInfo=new Array;aFilesInfo.push({paramName:"file",fileName:"test.txt",newFileName:"d8e8fca2dc0f896fd7cb4cb0031ba249.txt",md5:"d8e8fca2dc0f896fd7cb4cb0031ba249"}),window.parent.document.controller.uploadFileCallBack(aFilesInfo)}</script>
3533
+ </head>
3534
+ <body></body>
3535
+ </html>`)); // upload.jsp
3536
+
3537
+ client._transport.mockReturnValueOnce(Promise.resolve(Mock.GET_XTK_COUNTER_RESPONSE)); // GetEntityIfMoreRecentResponse - counter
3538
+ client._transport.mockReturnValueOnce(Mock.INCREASE_VALUE_RESPONSE); // xtk:counter#IncreaseValue
3539
+
3540
+ client._transport.mockReturnValueOnce(Mock.GET_XTK_SESSION_SCHEMA_RESPONSE); // GetEntityIfMoreRecentResponse - session
3541
+ client._transport.mockReturnValueOnce(Mock.FILE_RES_WRITE_RESPONSE); // xtk:session#Write
3542
+
3543
+ client._transport.mockReturnValueOnce(Promise.resolve(Mock.GET_FILERES_QUERY_SCHEMA_RESPONSE)); // GetEntityIfMoreRecentResponse - fileRes
3544
+ client._transport.mockReturnValueOnce(Promise.resolve(Mock.PUBLISH_IF_NEEDED_RESPONSE)); // xtk:fileRes#PublishIfNeeded
3545
+
3546
+ client._transport.mockReturnValueOnce(Promise.resolve(Mock.GET_URL_RESPONSE)); // xtk:fileRes#GetURL
3547
+
3548
+ // Call upload
3549
+ const result = await client.fileUploader.upload({
3550
+ type: 'text/html',
3551
+ size: 12345
3552
+ }, { action: "publishIfNeeded" });
3553
+
3554
+ expect(result).toMatchObject({
3555
+ md5: "d8e8fca2dc0f896fd7cb4cb0031ba249",
3556
+ name: "test.txt",
3557
+ size: 12345,
3558
+ type: "text/html",
3559
+ url: "http://hello.com"
3560
+ });
3561
+ });
3562
+
3563
+ it("Should support 'none' action", async () => {
3564
+ // Create a mock client and logon
3565
+ const client = await Mock.makeClient();
3566
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
3567
+ await client.NLWS.xtkSession.logon();
3568
+
3569
+ // Mock the upload protocol
3570
+ // With the "none" action, we skip the counter & publication
3571
+ // - the upload.jsp (which returns the content of an iframe and JS to eval)
3572
+ client._transport.mockReturnValueOnce(Promise.resolve(`Ok
3573
+ <html xmlns="http://www.w3.org/1999/xhtml">
3574
+ <head>
3575
+ <script type="text/javascript">if(window.parent&&window.parent.document.controller&&"function"==typeof window.parent.document.controller.uploadFileCallBack){var aFilesInfo=new Array;aFilesInfo.push({paramName:"file",fileName:"test.txt",newFileName:"d8e8fca2dc0f896fd7cb4cb0031ba249.txt",md5:"d8e8fca2dc0f896fd7cb4cb0031ba249"}),window.parent.document.controller.uploadFileCallBack(aFilesInfo)}</script>
3576
+ </head>
3577
+ <body></body>
3578
+ </html>`)); // upload.jsp
3579
+
3580
+ // Call upload
3581
+ const result = await client.fileUploader.upload({
3582
+ type: 'text/html',
3583
+ size: 12345
3584
+ }, { action: "none" });
3585
+
3586
+ expect(result).toMatchObject({
3587
+ md5: "d8e8fca2dc0f896fd7cb4cb0031ba249",
3588
+ name: "test.txt",
3589
+ size: 12345,
3590
+ type: "text/html",
3591
+ });
3592
+ expect(result.url).toBeUndefined();
3593
+ });
3594
+
3595
+ it("Should failed with invalid action", async () => {
3596
+ // Create a mock client and logon
3597
+ const client = await Mock.makeClient();
3598
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
3599
+ await client.NLWS.xtkSession.logon();
3600
+
3601
+ // Mock the upload protocol
3602
+ // With the "none" action, we skip the counter & publication
3603
+ // - the upload.jsp (which returns the content of an iframe and JS to eval)
3604
+ client._transport.mockReturnValueOnce(Promise.resolve(`Ok
3605
+ <html xmlns="http://www.w3.org/1999/xhtml">
3606
+ <head>
3607
+ <script type="text/javascript">if(window.parent&&window.parent.document.controller&&"function"==typeof window.parent.document.controller.uploadFileCallBack){var aFilesInfo=new Array;aFilesInfo.push({paramName:"file",fileName:"test.txt",newFileName:"d8e8fca2dc0f896fd7cb4cb0031ba249.txt",md5:"d8e8fca2dc0f896fd7cb4cb0031ba249"}),window.parent.document.controller.uploadFileCallBack(aFilesInfo)}</script>
3608
+ </head>
3609
+ <body></body>
3610
+ </html>`)); // upload.jsp
3611
+
3612
+ // Call upload
3613
+ await expect(client.fileUploader.upload({
3614
+ type: 'text/html',
3615
+ size: 12345
3616
+ }, { action: "invalid" })).rejects.toMatchObject({
3617
+ errorCode: "SDK-000006",
3618
+ "faultCode": 16384,
3619
+ "faultString": "Bad parameter 'action' with value 'invalid'",
3620
+ "statusCode": 400
3621
+ });
3622
+ });
3516
3623
  });
3517
3624
 
3518
3625
  describe("Setting the xtkschema attribute", () => {
@@ -157,6 +157,20 @@ describe('DomUtil', function() {
157
157
  assert.strictEqual(fromJSON({ "a": null }), '<root/>');
158
158
  assert.strictEqual(fromJSON({ "a": undefined }), '<root/>');
159
159
  });
160
+
161
+ it("Should support attributes named 'length'", () => {
162
+ const json = {
163
+ element: {
164
+ attribute: {
165
+ length: "256",
166
+ name: "id",
167
+ },
168
+ }
169
+ };
170
+ const doc = DomUtil.fromJSON("extension", json);
171
+ const xml = DomUtil.toXMLString(doc);
172
+ expect(xml).toEqual('<extension><element><attribute length="256" name="id"/></element></extension>');
173
+ });
160
174
  });
161
175
 
162
176
  describe('fromJSON (default)', function() {
@@ -0,0 +1,174 @@
1
+ /*
2
+ Copyright 2023 Adobe. All rights reserved.
3
+ This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License. You may obtain a copy
5
+ of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+ Unless required by applicable law or agreed to in writing, software distributed under
8
+ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ OF ANY KIND, either express or implied. See the License for the specific language
10
+ governing permissions and limitations under the License.
11
+ */
12
+
13
+
14
+ /**********************************************************************************
15
+ *
16
+ * Unit tests for IMS Bearer Token authentication
17
+ *
18
+ *********************************************************************************/
19
+ const sdk = require('../src/index.js');
20
+ const Mock = require('./mock.js').Mock;
21
+
22
+ describe('IMS Bearer Toekn', function () {
23
+
24
+ async function makeImsClient(options) {
25
+ const connectionParameters = sdk.ConnectionParameters.ofImsBearerToken("http://acc-sdk:8080", "ey...", options);
26
+ const client = await sdk.init(connectionParameters);
27
+ if (!options || !options.transport) // allow tests to explicitely set the transport
28
+ client._transport = jest.fn();
29
+ return client;
30
+ }
31
+
32
+ it('Should logon with IMS Bearer Token', async () => {
33
+ const client = await makeImsClient();
34
+ // No "Logon" API call is made when using IMS Bearer Token
35
+ //client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
36
+ await client.NLWS.xtkSession.logon();
37
+ expect(client.isLogged()).toBe(true);
38
+ });
39
+
40
+ // The logoff API invalidates the session created on the server side and does not invalidate
41
+ // the bearer token. To invalidate the bearer token, IMS should be used
42
+ it('Should logoff', async () => {
43
+ const client = await makeImsClient();
44
+ await client.NLWS.xtkSession.logon();
45
+
46
+ client._transport.mockReturnValueOnce(Mock.LOGOFF_RESPONSE);
47
+ await client.NLWS.xtkSession.logoff();
48
+ expect(client.isLogged()).toBe(false);
49
+
50
+ expect(client._transport).toBeCalledTimes(1);
51
+ const calls = client._transport.mock.calls;
52
+ expect(calls[0][0].headers).toMatchObject({
53
+ "ACC-SDK-Auth": "ImsBearerToken",
54
+ "Authorization": "Bearer ey..."
55
+ });
56
+ expect(calls[0][0].headers["X-Security-Token"]).toBeUndefined();
57
+ expect(calls[0][0].headers["X-Session-Token"]).toBeUndefined();
58
+ });
59
+
60
+ it('Should call API with Bearer Token', async () => {
61
+ const client = await makeImsClient();
62
+ await client.NLWS.xtkSession.logon();
63
+
64
+ // Get Option
65
+ client._transport.mockReturnValueOnce(Mock.GET_XTK_SESSION_SCHEMA_RESPONSE);
66
+ client._transport.mockReturnValueOnce(Mock.GET_DATABASEID_RESPONSE);
67
+ var databaseId = await client.getOption("XtkDatabaseId");
68
+ expect(databaseId).toBe("uFE80000000000000F1FA913DD7CC7C480041161C");
69
+
70
+ // Check that headers were correctly populated for both calls
71
+ expect(client._transport).toBeCalledTimes(2);
72
+ const calls = client._transport.mock.calls;
73
+ expect(calls[0][0].headers).toMatchObject({
74
+ "ACC-SDK-Auth": "ImsBearerToken",
75
+ "ACC-SDK-Call-Internal": "1",
76
+ "Authorization": "Bearer ey..."
77
+ });
78
+ expect(calls[0][0].headers["X-Security-Token"]).toBeUndefined();
79
+ expect(calls[0][0].headers["X-Session-Token"]).toBeUndefined();
80
+
81
+ expect(calls[1][0].headers).toMatchObject({
82
+ "ACC-SDK-Auth": "ImsBearerToken",
83
+ "Authorization": "Bearer ey..."
84
+ });
85
+ expect(calls[1][0].headers["ACC-SDK-Call-Internal"]).toBeUndefined();
86
+ expect(calls[1][0].headers["X-Security-Token"]).toBeUndefined();
87
+ expect(calls[1][0].headers["X-Session-Token"]).toBeUndefined();
88
+ });
89
+
90
+ it("Expired session refresh client callback", async () => {
91
+
92
+ const refreshClient = async (client) => {
93
+ const connectionParameters = sdk.ConnectionParameters.ofImsBearerToken("http://acc-sdk:8080", "ey2...", options);
94
+ client.reinit(connectionParameters);
95
+ await client.NLWS.xtkSession.logon();
96
+ return client;
97
+ };
98
+
99
+ const transport = jest.fn();
100
+ const options = {
101
+ transport: transport,
102
+ refreshClient: refreshClient,
103
+ };
104
+ const connectionParameters = sdk.ConnectionParameters.ofImsBearerToken("http://acc-sdk:8080", "ey1...", options);
105
+ const client = await sdk.init(connectionParameters);
106
+ await client.NLWS.xtkSession.logon();
107
+
108
+ client._transport.mockReturnValueOnce(Promise.resolve(`XSV-350008 Session has expired or is invalid. Please reconnect.`));
109
+ client._transport.mockReturnValueOnce(Mock.GET_XTK_SESSION_SCHEMA_RESPONSE);
110
+ client._transport.mockReturnValueOnce(Mock.GET_DATABASEID_RESPONSE);
111
+ var databaseId = await client.getOption("XtkDatabaseId");
112
+ expect(databaseId).toBe("uFE80000000000000F1FA913DD7CC7C480041161C");
113
+ const lastCall = client._transport.mock.calls[client._transport.mock.calls.length - 1];
114
+ expect(lastCall[0].headers).toMatchObject({
115
+ "ACC-SDK-Auth": "ImsBearerToken",
116
+ "Authorization": "Bearer ey2..."
117
+ });
118
+ expect(lastCall[0].headers["X-Security-Token"]).toBeUndefined();
119
+ expect(lastCall[0].headers["X-Session-Token"]).toBeUndefined();
120
+ });
121
+
122
+
123
+ it("Should call ping API", async () => {
124
+ const client = await makeImsClient();
125
+ await client.NLWS.xtkSession.logon();
126
+
127
+ client._transport.mockReturnValueOnce(Mock.PING);
128
+ const ping = await client.ping();
129
+ expect(ping.status).toBe("OK");
130
+ expect(ping.timestamp).toBe("2021-08-27 15:43:48.862Z");
131
+
132
+ const lastCall = client._transport.mock.calls[client._transport.mock.calls.length - 1];
133
+ expect(lastCall[0].headers).toMatchObject({
134
+ "ACC-SDK-Auth": "ImsBearerToken",
135
+ "Authorization": "Bearer ey..."
136
+ });
137
+ expect(lastCall[0].headers["X-Security-Token"]).toBeUndefined();
138
+ expect(lastCall[0].headers["X-Session-Token"]).toBeUndefined();
139
+ });
140
+
141
+ it("Should call mcPing API", async () => {
142
+ const client = await makeImsClient();
143
+ await client.NLWS.xtkSession.logon();
144
+
145
+ client._transport.mockReturnValueOnce(Mock.MC_PING);
146
+ const ping = await client.mcPing();
147
+ expect(ping.status).toBe("Ok");
148
+
149
+ const lastCall = client._transport.mock.calls[client._transport.mock.calls.length - 1];
150
+ expect(lastCall[0].headers).toMatchObject({
151
+ "ACC-SDK-Auth": "ImsBearerToken",
152
+ "Authorization": "Bearer ey..."
153
+ });
154
+ expect(lastCall[0].headers["X-Security-Token"]).toBeUndefined();
155
+ expect(lastCall[0].headers["X-Session-Token"]).toBeUndefined();
156
+ });
157
+
158
+ it("Should allow to use the application object", async () => {
159
+ const client = await makeImsClient();
160
+ await client.NLWS.xtkSession.logon();
161
+ const application = client.application;
162
+ expect(application).toBeDefined();
163
+
164
+ client._transport.mockReturnValueOnce(Mock.GET_NMS_EXTACCOUNT_SCHEMA_RESPONSE);
165
+ const schema = await application.getSchema("nms:extAccount");
166
+ expect(schema).toBeDefined();
167
+ expect(schema.name).toBe("extAccount");
168
+
169
+ // But the application object does not have any info about packages
170
+ // since we are using a bearer token. Packages are only available
171
+ // when using one of the Logon methods (Logno or BearertokenLogon)
172
+ expect(application.packages).toBeUndefined();
173
+ });
174
+ });