@adobe/acc-js-sdk 1.1.6 → 1.1.8

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/src/client.js CHANGED
@@ -10,14 +10,14 @@ OF ANY KIND, either express or implied. See the License for the specific languag
10
10
  governing permissions and limitations under the License.
11
11
  */
12
12
  (function() {
13
- "use strict";
14
-
13
+ "use strict";
14
+
15
15
 
16
16
  /**********************************************************************************
17
- *
17
+ *
18
18
  * ACC JavaScript SDK
19
19
  * See README.md for usage
20
- *
20
+ *
21
21
  *********************************************************************************/
22
22
 
23
23
  /**
@@ -39,16 +39,16 @@ const { Util } = require('./util.js');
39
39
 
40
40
  /**
41
41
  * @namespace Campaign
42
- *
42
+ *
43
43
  * @typedef {Object} SessionInfo
44
44
  * @memberOf Campaign
45
- *
45
+ *
46
46
  * @typedef {Object} RedirStatus
47
47
  * @memberOf Campaign
48
- *
48
+ *
49
49
  * @typedef {Object} PingStatus
50
50
  * @memberOf Campaign
51
- *
51
+ *
52
52
  * @typedef {Object} McPingStatus
53
53
  * @memberOf Campaign
54
54
  *
@@ -59,14 +59,14 @@ const { Util } = require('./util.js');
59
59
 
60
60
  /**
61
61
  * Java Script Proxy handler for an XTK object. An XTK object is one constructed with the following syntax:
62
- *
62
+ *
63
63
  * <code>
64
64
  * NLWS.xtkQueryDef.create(...)
65
65
  * </code>
66
- *
66
+ *
67
67
  * Any Xtk methods can be called directly on such an object using this proxy handler which will lookup
68
68
  * the method definition and manage parameters marshalling and SOAP call
69
- *
69
+ *
70
70
  * @private
71
71
  * @memberof Campaign
72
72
  */
@@ -84,20 +84,20 @@ const xtkObjectHandler = {
84
84
  return new Proxy(caller, {
85
85
  apply: function(target, thisArg, argumentsList) {
86
86
  return target(thisArg, argumentsList);
87
- }
87
+ }
88
88
  });
89
89
 
90
90
  }
91
91
  };
92
92
 
93
93
  /**
94
- * Java Script Proxy handler for NLWS.
94
+ * Java Script Proxy handler for NLWS.
95
95
  * The proxy resolves constructs such as
96
- *
96
+ *
97
97
  * <code>
98
98
  * result = await client.NLWS.xtkSession.getServerTime();
99
99
  * </code>
100
- *
100
+ *
101
101
  * To get a handler, call the `clientHandler` function and optionally pass a representation.
102
102
  * If no representation is passed (undefined), the representation set at the client level
103
103
  * will be used, which is the default behavior.
@@ -195,7 +195,7 @@ const clientHandler = (representation, headers, pushDownOptions) => {
195
195
  return new Proxy(caller, {
196
196
  apply: function(target, thisArg, argumentsList) {
197
197
  return target(thisArg, argumentsList);
198
- }
198
+ }
199
199
  });
200
200
  }
201
201
  });
@@ -207,7 +207,7 @@ const clientHandler = (representation, headers, pushDownOptions) => {
207
207
  // Campaign credentials
208
208
  // ========================================================================================
209
209
 
210
- /**
210
+ /**
211
211
  * @class
212
212
  * @constructor
213
213
  * @private
@@ -223,7 +223,7 @@ class Credentials {
223
223
  * @param {string} securityToken the security token. Will use an empty token if not specified
224
224
  */
225
225
  constructor(type, sessionToken, securityToken) {
226
- if (type != "UserPassword" && type != "ImsServiceToken" && type != "SessionToken" &&
226
+ if (type != "UserPassword" && type != "ImsServiceToken" && type != "SessionToken" &&
227
227
  type != "AnonymousUser" && type != "SecurityToken" && type != "BearerToken")
228
228
  throw CampaignException.INVALID_CREDENTIALS_TYPE(type);
229
229
  this._type = type;
@@ -237,7 +237,7 @@ class Credentials {
237
237
 
238
238
  /**
239
239
  * For "UserPassword" type credentials, return the user name
240
- *
240
+ *
241
241
  * @private
242
242
  * @returns {string} the user name
243
243
  */
@@ -251,7 +251,7 @@ class Credentials {
251
251
 
252
252
  /**
253
253
  * For "UserPassword" type credentials, return the user password
254
- *
254
+ *
255
255
  * @private
256
256
  * @returns {string} the user password
257
257
  */
@@ -289,7 +289,7 @@ class Credentials {
289
289
  * @property {number} timeout - Can be set to change the HTTP call timeout. Value is passed in ms.
290
290
  * @memberOf Campaign
291
291
  */
292
-
292
+
293
293
 
294
294
  /**
295
295
  * @class
@@ -331,7 +331,7 @@ class ConnectionParameters {
331
331
 
332
332
  this._options.entityCacheTTL = options.entityCacheTTL || 1000*300; // 5 mins
333
333
  this._options.methodCacheTTL = options.methodCacheTTL || 1000*300; // 5 mins
334
- this._options.optionCacheTTL = options.optionCacheTTL || 1000*300; // 5 mins
334
+ this._options.optionCacheTTL = options.optionCacheTTL || 1000*300; // 5 mins
335
335
  this._options.traceAPICalls = options.traceAPICalls === null || options.traceAPICalls ? !!options.traceAPICalls : false;
336
336
  this._options.transport = options.transport || request;
337
337
 
@@ -343,10 +343,10 @@ class ConnectionParameters {
343
343
  storage = options.storage;
344
344
  try {
345
345
  if (!storage)
346
- storage = localStorage;
346
+ storage = localStorage;
347
347
  } catch (ex) {
348
348
  /* ignore error if localStorage not found */
349
- }
349
+ }
350
350
  }
351
351
  this._options._storage = storage;
352
352
  this._options.refreshClient = options.refreshClient;
@@ -362,7 +362,7 @@ class ConnectionParameters {
362
362
 
363
363
  /**
364
364
  * Creates connection parameters for a Campaign instance, using a user name and password
365
- *
365
+ *
366
366
  * @param {string} endpoint The campaign endpoint (URL)
367
367
  * @param {string} user The user name
368
368
  * @param {string} password The user password
@@ -376,7 +376,7 @@ class ConnectionParameters {
376
376
 
377
377
  /**
378
378
  * Creates connection parameters for a Campaign instance from bearer token
379
- *
379
+ *
380
380
  * @param {string} endpoint The campaign endpoint (URL)
381
381
  * @param {string} bearerToken IMS bearer token
382
382
  * @param {*} options connection options
@@ -388,7 +388,7 @@ class ConnectionParameters {
388
388
  }
389
389
  /**
390
390
  * Creates connection parameters for a Campaign instance, using an IMS service token and a user name (the user to impersonate)
391
- *
391
+ *
392
392
  * @param {string} endpoint The campaign endpoint (URL)
393
393
  * @param {string} user The user name
394
394
  * @param {string} serviceToken The IMS service token
@@ -402,7 +402,7 @@ class ConnectionParameters {
402
402
 
403
403
  /**
404
404
  * Creates connection parameters for a Campaign instance, using a session token
405
- *
405
+ *
406
406
  * @static
407
407
  * @param {string} endpoint The campaign endpoint (URL)
408
408
  * @param {string} sessionToken The session token
@@ -419,7 +419,7 @@ class ConnectionParameters {
419
419
  * Typically, called when embedding the SDK in Campaign: the session token will be
420
420
  * passed automatically as a cookie, so only the security token is actually needed
421
421
  * to logon
422
- *
422
+ *
423
423
  * @static
424
424
  * @param {string} endpoint The campaign endpoint (URL)
425
425
  * @param {string} securityToken The session token
@@ -433,7 +433,7 @@ class ConnectionParameters {
433
433
 
434
434
  /**
435
435
  * Creates connection parameters for a Campaign instance for an anonymous user
436
- *
436
+ *
437
437
  * @param {string} endpoint The campaign endpoint (URL)
438
438
  * @param {Campaign.ConnectionOptions} options connection options
439
439
  * @returns {ConnectionParameters} a ConnectionParameters object which can be used to create a Client
@@ -448,7 +448,7 @@ class ConnectionParameters {
448
448
  * Creates connection parameters for a Campaign instance, using an external account. This can be used to connect
449
449
  * to a mid-sourcing instance, or to a message center instance. This function will lookup the external account,
450
450
  * and use its credentials to get connection parameters to the corresponding Campaign instance
451
- *
451
+ *
452
452
  * @param {Client} client The Campaign Client from which to lookup the external account (normally, a connected client to the marketing instance)
453
453
  * @param {string} extAccountName The name of the external account. Only mid-sourcing accounts (type 3) are supported
454
454
  * @returns {ConnectionParameters} a ConnectionParameters object which can be used to create a Client
@@ -494,6 +494,102 @@ class ConnectionParameters {
494
494
 
495
495
  }
496
496
 
497
+ // ========================================================================================
498
+ // File Uploader
499
+ // ========================================================================================
500
+
501
+ /**
502
+ * File Uploader API for JS SDK(Currently available only in browsers)
503
+ * @private
504
+ * @ignore
505
+ * @memberof Campaign
506
+ * @param client
507
+ * @returns {{upload: (function(*=): Promise<{name: string, md5: string, type: string, size: string, url: string}>)}}
508
+ */
509
+ const fileUploader = (client) => {
510
+
511
+ return {
512
+ /**
513
+ * 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.
514
+ * @ignore
515
+ * @param file, where file is an instance of [File](https://developer.mozilla.org/en-US/docs/Web/API/File)
516
+ * @returns {Promise<{name: string, md5: string, type: string, size: string, url: string}>}
517
+ */
518
+ upload: (file) => {
519
+ 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.`);
520
+ return new Promise((resolve, reject) => {
521
+ try {
522
+ if (!Util.isBrowser()) {
523
+ throw 'File uploading is only supported in browser based calls.';
524
+ }
525
+ const data = new FormData();
526
+ data.append('file_noMd5', file);
527
+ //TODO: Needs to be refactored after cookie issue get resolved.
528
+ client._makeHttpCall({
529
+ url: `${client._connectionParameters._endpoint}/nl/jsp/uploadFile.jsp`,
530
+ processData: false,
531
+ credentials: 'include',
532
+ method: 'POST',
533
+ body: data,
534
+ headers: {
535
+ 'x-security-token': client._securityToken,
536
+ 'Cookie': '__sessiontoken=' + client._sessionToken,
537
+ }
538
+ }).then((okay) => {
539
+ if (!okay.startsWith('Ok')) {
540
+ throw okay;
541
+ }
542
+ const iframe = document.createElement('iframe');
543
+ iframe.style.height = 0;
544
+ iframe.style.width = 0;
545
+ document.controller = { // Written to support https://git.corp.adobe.com/Campaign/ac/blob/v6-master/nl/datakit/nl/jsp/uploadFile.jsp
546
+ uploadFileCallBack: async (data) => {
547
+ if (!data || data.length !== 1) {
548
+ // Tried to replicate the logic for file upload functionality written here:
549
+ // https://git.corp.adobe.com/Campaign/ac/blob/v6-master/wpp/xtk/web/dce/uploader.js
550
+ return reject(CampaignException.FILE_UPLOAD_FAILED(file.name, 'Malformed data:' + data.toString()));
551
+ }
552
+ const counter = await client.NLWS.xtkCounter.increaseValue({name: 'xtkResource'});
553
+ const fileRes= {
554
+ internalName: 'RES' + counter,
555
+ md5: data[0].md5,
556
+ label: data[0].fileName,
557
+ fileName: data[0].fileName,
558
+ originalName: data[0].fileName,
559
+ useMd5AsFilename: '1',
560
+ storageType: 5,
561
+ xtkschema: 'xtk:fileRes'
562
+
563
+ };
564
+ await client.NLWS.xtkSession.write(fileRes);
565
+ await client.NLWS.xtkFileRes.create(fileRes).publishIfNeeded();
566
+ const url = await client.NLWS.xtkFileRes.create(fileRes).getURL();
567
+ resolve({
568
+ name: data[0].fileName,
569
+ md5: data[0].md5,
570
+ type: file.type,
571
+ size: file.size,
572
+ url: url
573
+ });
574
+ }
575
+ };
576
+ const html = `<body>${okay}</body>`;
577
+ document.body.appendChild(iframe);
578
+ iframe.contentWindow.document.open();
579
+ iframe.contentWindow.document.write(html);
580
+ iframe.contentWindow.document.close();
581
+ }).catch((ex) => {
582
+ reject(CampaignException.FILE_UPLOAD_FAILED(file.name, ex));
583
+ });
584
+ } catch (ex) {
585
+ reject(CampaignException.FILE_UPLOAD_FAILED(file.name, ex));
586
+ }
587
+ });
588
+ }
589
+ };
590
+ };
591
+
592
+
497
593
  // ========================================================================================
498
594
  // ACC Client
499
595
  // ========================================================================================
@@ -509,7 +605,7 @@ class Client {
509
605
  /**
510
606
  * ACC API Client.
511
607
  * Do not create directly, use SDK.init instead
512
- *
608
+ *
513
609
  * @param {Campaign.SDK} sdk is the global sdk object used to create the client
514
610
  * @param {Campaign.ConnectionParameters} user user name, for instance admin
515
611
  */
@@ -522,9 +618,9 @@ class Client {
522
618
  this._sessionToken = undefined;
523
619
  this._securityToken = undefined;
524
620
  this._installedPackages = {}; // package set (key and value = package id, ex: "nms:amp")
525
-
621
+
526
622
  this._secretKeyCipher = undefined;
527
-
623
+
528
624
  this._storage = connectionParameters._options._storage;
529
625
  // TODO late cache initiallzation because need XtkDatabaseId / instance name
530
626
  var instanceKey = connectionParameters._endpoint || "";
@@ -546,13 +642,18 @@ class Client {
546
642
  this._refreshClient = connectionParameters._options.refreshClient;
547
643
 
548
644
  // expose utilities
645
+ /**
646
+ * File Uploader API
647
+ * @type {{upload: (function(*=): Promise<{name: string, md5: string, type: string, size: string, url: string}>)}}
648
+ */
649
+ this.fileUploader = fileUploader(this);
549
650
 
550
- /**
651
+ /**
551
652
  * Accessor to DOM helpers
552
653
  * @type {XML.DomUtil}
553
654
  */
554
655
  this.DomUtil = DomUtil;
555
- /**
656
+ /**
556
657
  * Accessor to a XtkCaster
557
658
  * @type {XtkCaster}
558
659
  */
@@ -562,12 +663,27 @@ class Client {
562
663
  * @type {Campaign.Application}
563
664
  */
564
665
  this.application = null;
666
+
667
+ // Context for observability. See logon() function which will fill this context
668
+ this._lastStatsReport = Date.now();
669
+ this._observabilityContext = {
670
+ eventId: 0,
671
+ client: {
672
+ sdkVersion: this.sdk.getSDKVersion().version,
673
+ endpoint: this._connectionParameters._endpoint,
674
+ createdAt: Date.now(),
675
+ clientApp: this._connectionParameters._options.clientApp,
676
+ }
677
+ };
678
+ if (this._connectionParameters._credentials) {
679
+ this._observabilityContext.client.type = this._connectionParameters._credentials.type;
680
+ }
565
681
  }
566
682
 
567
683
  /**
568
684
  * Override the transport. By default, we are using axios, but this can be customised.
569
685
  * See transport.js and documentation in the README for more details
570
- * @param {*} transport
686
+ * @param {*} transport
571
687
  */
572
688
  setTransport(transport) {
573
689
  this._transport = transport;
@@ -575,7 +691,7 @@ class Client {
575
691
 
576
692
  /**
577
693
  * Get the user agent string to use in all HTTP requests
578
- *
694
+ *
579
695
  * @returns {string} the user agent string
580
696
  */
581
697
  _getUserAgentString() {
@@ -585,7 +701,7 @@ class Client {
585
701
 
586
702
  /**
587
703
  * Convert an XML object into a representation
588
- *
704
+ *
589
705
  * @private
590
706
  * @param {DOMElement} xml the XML DOM element to convert
591
707
  * @param {string} representation the expected representation ('xml', 'BadgerFish', or 'SimpleJson'). If not set, will use the current representation
@@ -602,7 +718,7 @@ class Client {
602
718
 
603
719
  /**
604
720
  * Convert to an XML object from a representation
605
- *
721
+ *
606
722
  * @private
607
723
  * @param {string} rootName the name of the root XML element
608
724
  * @param {XML.XtkObject} entity the object to convert
@@ -622,7 +738,7 @@ class Client {
622
738
 
623
739
  /**
624
740
  * Convert between 2 representations
625
- *
741
+ *
626
742
  * @private
627
743
  * @param {XML.XtkObject} entity the object to convert
628
744
  * @param {string} fromRepresentation the source representation ('xml', 'BadgerFish', or 'SimpleJson').
@@ -640,7 +756,7 @@ class Client {
640
756
 
641
757
  /**
642
758
  * Compare two representations
643
- *
759
+ *
644
760
  * @private
645
761
  * @param {string} rep1 the first representation ('xml', 'BadgerFish', or 'SimpleJson')
646
762
  * @param {string} rep2 the second representation ('xml', 'BadgerFish', or 'SimpleJson')
@@ -656,7 +772,7 @@ class Client {
656
772
 
657
773
  /**
658
774
  * Activate / deactivate tracing of API calls
659
- *
775
+ *
660
776
  * @param {boolean} trace indicates whether to activate tracing or not
661
777
  */
662
778
  traceAPICalls(trace) {
@@ -692,9 +808,67 @@ class Client {
692
808
  this._observers.map((observer) => callback(observer));
693
809
  }
694
810
 
811
+ _trackEvent(eventName, parentEvent, payload) {
812
+ try {
813
+ if (payload && payload.name === 'CampaignException') {
814
+ payload = {
815
+ detail: payload.detail,
816
+ errorCode: payload.errorCode,
817
+ faultCode: payload.faultCode,
818
+ faultString: payload.faultString,
819
+ message: payload.message,
820
+ statusCode: payload.statusCode,
821
+ };
822
+ }
823
+ this._observabilityContext.eventId = this._observabilityContext.eventId + 1;
824
+ const now = Date.now();
825
+ const event = {
826
+ client: this._observabilityContext.client,
827
+ session: this._observabilityContext.session,
828
+ eventId: this._observabilityContext.eventId,
829
+ eventName: eventName,
830
+ payload: payload,
831
+ timestamp: now,
832
+ };
833
+ if (parentEvent) event.parentEventId = parentEvent.eventId;
834
+ this._notifyObservers((observer) => observer.event && observer.event(event, parentEvent));
835
+
836
+ // Regularly report internal stats every 5 mins
837
+ if ((now - this._lastStatsReport) >= 300000) {
838
+ this._lastStatsReport = now;
839
+ this._trackInternalStats();
840
+ }
841
+
842
+ return event;
843
+ } catch (error) {
844
+ console.info(`Failed to track observability event`, error);
845
+ }
846
+ }
847
+
848
+ _trackInternalStats() {
849
+ this._trackCacheStats('entityCache', this._entityCache);
850
+ this._trackCacheStats('optionCache', this._optionCache);
851
+ this._trackCacheStats('methodCache', this._methodCache);
852
+ }
853
+
854
+ _trackCacheStats(name, cache) {
855
+ if (!cache || !cache._stats) return;
856
+ this._trackEvent('CACHE//stats', undefined, {
857
+ name: name,
858
+ reads: cache._stats.reads,
859
+ writes: cache._stats.writes,
860
+ removals: cache._stats.removals,
861
+ clears: cache._stats.clears,
862
+ memoryHits: cache._stats.memoryHits,
863
+ storageHits: cache._stats.storageHits,
864
+ loads: cache._stats.loads,
865
+ saves: cache._stats.saves,
866
+ });
867
+ }
868
+
695
869
  /**
696
870
  * Is the client logged?
697
- *
871
+ *
698
872
  * @returns {boolean} a boolean indicating if the client is logged or not
699
873
  */
700
874
  isLogged() {
@@ -729,7 +903,7 @@ class Client {
729
903
 
730
904
  /**
731
905
  * Prepares a SOAP call, including authentication, headers...
732
- *
906
+ *
733
907
  * @private
734
908
  * @param {string} urn is the API name space, usually the schema. For instance xtk:session
735
909
  * @param {string} method is the method to call, for instance Logon
@@ -738,8 +912,8 @@ class Client {
738
912
  * parameters should be set
739
913
  */
740
914
  _prepareSoapCall(urn, method, internal, extraHttpHeaders, pushDownOptions) {
741
- const soapCall = new SoapMethodCall(this._transport, urn, method,
742
- this._sessionToken, this._securityToken,
915
+ const soapCall = new SoapMethodCall(this._transport, urn, method,
916
+ this._sessionToken, this._securityToken,
743
917
  this._getUserAgentString(),
744
918
  Object.assign({}, this._connectionParameters._options, pushDownOptions),
745
919
  extraHttpHeaders);
@@ -757,16 +931,30 @@ class Client {
757
931
  async _retrySoapCall(soapCall) {
758
932
  soapCall.retry = false;
759
933
  soapCall._retryCount = soapCall._retryCount + 1;
760
- var newClient = await this._refreshClient(this);
934
+ var newClient = await this._refreshClient(this);
761
935
  soapCall.finalize(newClient._soapEndPoint(), newClient);
936
+ const safeCallData = Util.trim(soapCall.request.data);
762
937
  if (this._traceAPICalls) {
763
- const safeCallData = Util.trim(soapCall.request.data);
764
938
  console.log(`RETRY SOAP//request ${safeCallData}`);
765
939
  }
766
- await soapCall.execute();
767
- if (this._traceAPICalls) {
940
+ const event = this._trackEvent('SOAP//request', undefined, {
941
+ urn: soapCall.urn,
942
+ methodName: soapCall.methodName,
943
+ internal: soapCall.internal,
944
+ retry: true,
945
+ retryCount: soapCall._retryCount,
946
+ safeCallData: safeCallData,
947
+ });
948
+ try {
949
+ await soapCall.execute();
768
950
  const safeCallResponse = Util.trim(soapCall.response);
769
- console.log(`SOAP//response ${safeCallResponse}`);
951
+ if (this._traceAPICalls) {
952
+ console.log(`SOAP//response ${safeCallResponse}`);
953
+ }
954
+ this._trackEvent('SOAP//response', event, { safeCallResponse: safeCallResponse });
955
+ } catch(error) {
956
+ this._trackEvent('SOAP//failure', event, error);
957
+ throw error;
770
958
  }
771
959
  return;
772
960
  }
@@ -783,7 +971,7 @@ class Client {
783
971
  /**
784
972
  * After a SOAP method call has been prepared with '_prepareSoapCall', and parameters have been added,
785
973
  * this function actually executes the SOAP call
786
- *
974
+ *
787
975
  * @private
788
976
  * @param {SOAP.SoapMethodCall} soapCall the SOAP method to call
789
977
  */
@@ -792,24 +980,34 @@ class Client {
792
980
  if (soapCall.requiresLogon() && !that.isLogged())
793
981
  throw CampaignException.NOT_LOGGED_IN(soapCall, `Cannot execute SOAP call ${soapCall.urn}#${soapCall.methodName}: you are not logged in. Use the Logon function first`);
794
982
  soapCall.finalize(this._soapEndPoint());
795
-
983
+
796
984
  const safeCallData = Util.trim(soapCall.request.data);
797
985
  if (that._traceAPICalls)
798
986
  console.log(`SOAP//request ${safeCallData}`);
799
987
  that._notifyObservers((observer) => observer.onSOAPCall && observer.onSOAPCall(soapCall, safeCallData) );
800
-
988
+
989
+ const event = this._trackEvent('SOAP//request', undefined, {
990
+ urn: soapCall.urn,
991
+ methodName: soapCall.methodName,
992
+ internal: soapCall.internal,
993
+ retry: false,
994
+ retryCount: soapCall._retryCount,
995
+ safeCallData: safeCallData,
996
+ });
801
997
  return soapCall.execute()
802
998
  .then(() => {
803
999
  const safeCallResponse = Util.trim(soapCall.response);
804
1000
  if (that._traceAPICalls)
805
1001
  console.log(`SOAP//response ${safeCallResponse}`);
806
1002
  that._notifyObservers((observer) => observer.onSOAPCallSuccess && observer.onSOAPCallSuccess(soapCall, safeCallResponse) );
1003
+ this._trackEvent('SOAP//response', event, { safeCallResponse: safeCallResponse });
807
1004
  return Promise.resolve();
808
1005
  })
809
1006
  .catch((ex) => {
810
1007
  if (that._traceAPICalls)
811
1008
  console.log(`SOAP//failure ${ex.toString()}`);
812
1009
  that._notifyObservers((observer) => observer.onSOAPCallFailure && observer.onSOAPCallFailure(soapCall, ex) );
1010
+ this._trackEvent('SOAP//failure', event, ex);
813
1011
  // Call session expiration callback in case of 401
814
1012
  if (ex.statusCode == 401 && that._refreshClient && soapCall.retry) {
815
1013
  return this._retrySoapCall(soapCall);
@@ -819,6 +1017,32 @@ class Client {
819
1017
  });
820
1018
  }
821
1019
 
1020
+ _onLogon() {
1021
+ this.application = new Application(this);
1022
+ this.application._registerCacheChangeListener();
1023
+ this._observabilityContext.instance = {
1024
+ buildNumber: this.application.buildNumber,
1025
+ version: this.application.version,
1026
+ instanceName: this.application.instanceName,
1027
+ };
1028
+ if (this.application.operator) {
1029
+ this._observabilityContext.instance.operator = {
1030
+ login: this.application.operator.login,
1031
+ id: this.application.operator.id,
1032
+ timezone: this.application.operator.timezone,
1033
+ rights: this.application.operator.rights,
1034
+ packages: this.application.operator.packages,
1035
+ };
1036
+ }
1037
+ this._observabilityContext.session = {
1038
+ logonAt: Date.now(),
1039
+ };
1040
+ }
1041
+
1042
+ _onLogoff() {
1043
+ delete this._observabilityContext.instance;
1044
+ delete this._observabilityContext.session;
1045
+ }
822
1046
 
823
1047
  /**
824
1048
  * Login to an instance
@@ -843,6 +1067,8 @@ class Client {
843
1067
  // See NEO-35259
844
1068
  this._connectionParameters._options.extraHttpHeaders['X-Query-Source'] = `${version}${clientApp? "," + clientApp : ""}`;
845
1069
 
1070
+ this._trackEvent('SDK//logon', undefined, {});
1071
+
846
1072
  // Clear session token cookie to ensure we're not inheriting an expired cookie. See NEO-26589
847
1073
  if (credentials._type != "SecurityToken" && typeof document != "undefined") {
848
1074
  document.cookie = '__sessiontoken=;path=/;';
@@ -852,8 +1078,7 @@ class Client {
852
1078
  that._installedPackages = {};
853
1079
  that._sessionToken = credentials._sessionToken;
854
1080
  that._securityToken = "";
855
- that.application = new Application(that);
856
- that.application._registerCacheChangeListener();
1081
+ that._onLogon();
857
1082
  return Promise.resolve();
858
1083
  }
859
1084
  else if (credentials._type == "SecurityToken") {
@@ -861,8 +1086,7 @@ class Client {
861
1086
  that._installedPackages = {};
862
1087
  that._sessionToken = "";
863
1088
  that._securityToken = credentials._securityToken;
864
- that.application = new Application(that);
865
- that.application._registerCacheChangeListener();
1089
+ that._onLogon();
866
1090
  return Promise.resolve();
867
1091
  }
868
1092
  else if (credentials._type == "UserPassword" || credentials._type == "BearerToken") {
@@ -886,10 +1110,10 @@ class Client {
886
1110
  else {
887
1111
  const bearerToken = credentials._bearerToken;
888
1112
  soapCall.writeString("bearerToken", bearerToken);
889
- }
1113
+ }
890
1114
  return this._makeSoapCall(soapCall).then(function() {
891
1115
  const sessionToken = soapCall.getNextString();
892
-
1116
+
893
1117
  that._sessionInfo = soapCall.getNextDocument();
894
1118
  that._installedPackages = {};
895
1119
  const userInfo = DomUtil.findElement(that._sessionInfo, "userInfo");
@@ -901,7 +1125,7 @@ class Client {
901
1125
  that._installedPackages[name] = name;
902
1126
  pack = DomUtil.getNextSiblingElement(pack);
903
1127
  }
904
-
1128
+
905
1129
  const securityToken = soapCall.getNextString();
906
1130
  soapCall.checkNoMoreArgs();
907
1131
  // Sanity check: we should have both a session token and a security token.
@@ -913,8 +1137,7 @@ class Client {
913
1137
  that._sessionToken = sessionToken;
914
1138
  that._securityToken = securityToken;
915
1139
 
916
- that.application = new Application(that);
917
- that.application._registerCacheChangeListener();
1140
+ that._onLogon();
918
1141
  });
919
1142
  }
920
1143
  else {
@@ -925,7 +1148,7 @@ class Client {
925
1148
 
926
1149
  /**
927
1150
  * Get details about the session (assumes client is logged)
928
- *
1151
+ *
929
1152
  * @param {string} representation the expected representation. If not set, will use the default client representation
930
1153
  * @returns {Campaign.SessionInfo} details about the session
931
1154
  */
@@ -939,30 +1162,35 @@ class Client {
939
1162
  */
940
1163
  logoff() {
941
1164
  var that = this;
942
- if (!that.isLogged()) return;
943
- that.application._unregisterCacheChangeListener();
944
- that._unregisterAllCacheChangeListeners();
945
- this.stopRefreshCaches();
946
- const credentials = this._connectionParameters._credentials;
947
- if (credentials._type != "SessionToken" && credentials._type != "AnonymousUser") {
948
- var soapCall = that._prepareSoapCall("xtk:session", "Logoff", false, this._connectionParameters._options.extraHttpHeaders);
949
- return this._makeSoapCall(soapCall).then(function() {
1165
+ try {
1166
+ if (!that.isLogged()) return;
1167
+ that.application._unregisterCacheChangeListener();
1168
+ that._unregisterAllCacheChangeListeners();
1169
+ this.stopRefreshCaches();
1170
+ const credentials = this._connectionParameters._credentials;
1171
+ if (credentials._type != "SessionToken" && credentials._type != "AnonymousUser") {
1172
+ var soapCall = that._prepareSoapCall("xtk:session", "Logoff", false, this._connectionParameters._options.extraHttpHeaders);
1173
+ return this._makeSoapCall(soapCall).then(function() {
1174
+ that._sessionToken = "";
1175
+ that._securityToken = "";
1176
+ that.application = null;
1177
+ soapCall.checkNoMoreArgs();
1178
+ });
1179
+ }
1180
+ else {
950
1181
  that._sessionToken = "";
951
1182
  that._securityToken = "";
952
1183
  that.application = null;
953
- soapCall.checkNoMoreArgs();
954
- });
955
- }
956
- else {
957
- that._sessionToken = "";
958
- that._securityToken = "";
959
- that.application = null;
1184
+ }
1185
+ } finally {
1186
+ this._trackEvent('SDK//logoff', undefined, {});
1187
+ this._onLogoff();
960
1188
  }
961
1189
  }
962
1190
 
963
1191
  /**
964
1192
  * Get the value of an option
965
- *
1193
+ *
966
1194
  * @param {string} name is the option name, for instance XtkDatabaseId
967
1195
  * @param {boolean} useCache indicates whether to use the cache or not. Default is true
968
1196
  * @return the option value, casted in the expected data type. If the option does not exist, it will return null.
@@ -982,7 +1210,7 @@ class Client {
982
1210
  /**
983
1211
  * Set an option value. Creates the option if it does not exists. Update the option
984
1212
  * if it exists already
985
- *
1213
+ *
986
1214
  * @param {string} name the option name
987
1215
  * @param {*} rawValue the value to set
988
1216
  * @param {string} description the optional description of the option
@@ -999,7 +1227,7 @@ class Client {
999
1227
  var attName = XtkCaster._variantStorageAttribute(type);
1000
1228
  if (!attName) {
1001
1229
  // could not infer the storage type of the attribute to use to store the value (when option did not exist before) => assume string
1002
- type = 6;
1230
+ type = 6;
1003
1231
  attName = "stringValue";
1004
1232
  }
1005
1233
  var doc = { xtkschema: "xtk:option", _operation: "insertOrUpdate", _key: "@name", name: name, dataType: type };
@@ -1088,7 +1316,7 @@ class Client {
1088
1316
 
1089
1317
  /**
1090
1318
  * Tests if a package is installed
1091
- *
1319
+ *
1092
1320
  * @param {string} packageId the package identifier, for instance: "nms:amp"
1093
1321
  * @param {string} optionalName if set, the first parameter will be interpreted as the namespace (ex: "nms") and the second as the name, ex: "amp"
1094
1322
  * @returns {boolean} a boolean indicating if the package is installed or not
@@ -1103,8 +1331,8 @@ class Client {
1103
1331
 
1104
1332
  /**
1105
1333
  * Obtains a cipher that can be used to encrypt/decrypt passwords, using the database secret key.
1106
- * This is used for example for mid-sourcing account.
1107
- *
1334
+ * This is used for example for mid-sourcing account.
1335
+ *
1108
1336
  * @private
1109
1337
  * @deprecated since version 1.0.0
1110
1338
  */
@@ -1119,10 +1347,10 @@ class Client {
1119
1347
 
1120
1348
  /**
1121
1349
  * Gets an entity, such as a schema, a source schema, a form, a navtree, etc. Each Campaign entity has a MD5 which is used to determine
1122
- * if an entity has changed or not. The GetEntityIfMoreRecent SOAP call can use this MD5 to avoid returning entities that did not
1350
+ * if an entity has changed or not. The GetEntityIfMoreRecent SOAP call can use this MD5 to avoid returning entities that did not
1123
1351
  * actually changed. Currently, the SDK, however is not able to use the MD5 and will perform a SOAP call every time the function is
1124
1352
  * called and return the whole entity
1125
- *
1353
+ *
1126
1354
  * @param {string} entityType is the type of entity requested, such as "xtk:schema", "xtk:srcSchema", "xtk:navtree", "xtk:form", etc.
1127
1355
  * @param {string} fullName is the fully qualified name of the entity (i.e. <namespace>:<name>)
1128
1356
  * @param {string} representation the expected representation, or undefined to set the default
@@ -1145,7 +1373,7 @@ class Client {
1145
1373
 
1146
1374
  /**
1147
1375
  * Get a compiled schema (not a source schema) definition as a DOM or JSON object depending on hte current representation
1148
- *
1376
+ *
1149
1377
  * @param {string} schemaId the schema id, such as "xtk:session", or "nms:recipient"
1150
1378
  * @param {string} representation an optional representation of the schema: "BadgerFish", "SimpleJson" or "xml". If not set, we'll use the client default representation
1151
1379
  * @param {boolean} internal indicates an "internal" call, i.e. a call performed by the SDK itself rather than the user of the SDK. For instance, the SDK will dynamically load schemas to find method definitions
@@ -1167,7 +1395,7 @@ class Client {
1167
1395
 
1168
1396
  /**
1169
1397
  * Get the definition of a system enumeration (SysEnum). Will be returned as JSON or XML depending on the client 'representation' attribute
1170
- *
1398
+ *
1171
1399
  * @param {string} enumName
1172
1400
  * @param {string} optionalStartSchemaOrSchemaName
1173
1401
  * @returns {XML.XtkObject} the enumeration definition in the current representation
@@ -1193,13 +1421,13 @@ class Client {
1193
1421
  if (index == -1)
1194
1422
  throw CampaignException.BAD_PARAMETER("optionalStartSchemaOrSchemaName", optionalStartSchemaOrSchemaName, `getEnum expects a valid schema name. '${optionalStartSchemaOrSchemaName}' is not a valid name.`);
1195
1423
  optionalStartSchemaOrSchemaName = await this.getSchema(optionalStartSchemaOrSchemaName, undefined, true);
1196
- if (!optionalStartSchemaOrSchemaName)
1424
+ if (!optionalStartSchemaOrSchemaName)
1197
1425
  throw CampaignException.BAD_PARAMETER("optionalStartSchemaOrSchemaName", optionalStartSchemaOrSchemaName, `Schema '${optionalStartSchemaOrSchemaName}' not found.`);
1198
1426
  }
1199
- else
1427
+ else
1200
1428
  throw CampaignException.BAD_PARAMETER("optionalStartSchemaOrSchemaName", optionalStartSchemaOrSchemaName, `getEnum expects a valid schema name wich is a string. Given ${typeof optionalStartSchemaOrSchemaName} instead`);
1201
1429
 
1202
- const schema = optionalStartSchemaOrSchemaName;
1430
+ const schema = optionalStartSchemaOrSchemaName;
1203
1431
  for (const e of EntityAccessor.getChildElements(schema, "enumeration")) {
1204
1432
  const n = EntityAccessor.getAttributeAsString(e, "name");
1205
1433
  if (n == enumName)
@@ -1209,7 +1437,7 @@ class Client {
1209
1437
 
1210
1438
  /**
1211
1439
  * Call Campaign SOAP method
1212
- *
1440
+ *
1213
1441
  * @private
1214
1442
  * @param {string} methodName is the method to call. In order to be more JavaScript friendly, the first char can be lower-cased
1215
1443
  * @param {*} callContext the call context)
@@ -1220,7 +1448,7 @@ class Client {
1220
1448
  const that = this;
1221
1449
  const result = [];
1222
1450
  const schemaId = callContext.schemaId;
1223
-
1451
+
1224
1452
  var schema = await that.getSchema(schemaId, "xml", true);
1225
1453
  if (!schema)
1226
1454
  throw CampaignException.SOAP_UNKNOWN_METHOD(schemaId, methodName, `Schema '${schemaId}' not found`);
@@ -1315,7 +1543,7 @@ class Client {
1315
1543
  param = DomUtil.getNextSiblingElement(param, "param");
1316
1544
  }
1317
1545
  }
1318
-
1546
+
1319
1547
  return that._makeSoapCall(soapCall).then(function() {
1320
1548
  if (!isStatic) {
1321
1549
  // Non static methods, such as xtk:query#SelectAll return a element named "entity" which is the object itself on which
@@ -1414,30 +1642,40 @@ class Client {
1414
1642
  request.headers = request.headers || [];
1415
1643
  if (!request.headers['User-Agent'])
1416
1644
  request.headers['User-Agent'] = this._getUserAgentString();
1645
+ let event;
1417
1646
  try {
1418
1647
  const safeCallData = Util.trim(request.data);
1419
1648
  if (this._traceAPICalls)
1420
1649
  console.log(`HTTP//request ${request.method} ${request.url}${safeCallData ? " " + safeCallData : ""}`);
1421
1650
  this._notifyObservers((observer) => observer.onHTTPCall && observer.onHTTPCall(request, safeCallData) );
1651
+ event = this._trackEvent('HTTP//request', undefined, {
1652
+ url: request.url,
1653
+ method: request.method,
1654
+ headers: request.request,
1655
+ safeCallData: safeCallData,
1656
+ });
1422
1657
  const body = await this._transport(request);
1423
1658
 
1424
1659
  const safeCallResponse = Util.trim(body);
1425
1660
  if (this._traceAPICalls)
1426
1661
  console.log(`HTTP//response${safeCallResponse ? " " + safeCallResponse : ""}`);
1427
1662
  this._notifyObservers((observer) => observer.onHTTPCallSuccess && observer.onHTTPCallSuccess(request, safeCallResponse) );
1663
+ this._trackEvent('HTTP//response', event, { safeCallResponse: safeCallResponse });
1428
1664
  return body;
1429
1665
  } catch(err) {
1430
1666
  if (this._traceAPICalls)
1431
1667
  console.log("HTTP//failure", err);
1432
1668
  this._notifyObservers((observer) => observer.onHTTPCallFailure && observer.onHTTPCallFailure(request, err) );
1433
- throw makeCampaignException({ request:request, reqponse:err.response }, err);
1669
+ const ex = makeCampaignException({ request:request, reqponse:err.response }, err);
1670
+ this._trackEvent('HTTP//failure', event, { }, ex);
1671
+ throw ex;
1434
1672
  }
1435
1673
  }
1436
1674
 
1437
1675
  /**
1438
1676
  * Tests if the Campaign redirection server is up (/r/test).
1439
1677
  * Does not require a logged client
1440
- *
1678
+ *
1441
1679
  * @returns {Campaign.RedirStatus} an object describing the status of the redirection server
1442
1680
  */
1443
1681
  async test() {
@@ -1455,12 +1693,12 @@ class Client {
1455
1693
 
1456
1694
  /**
1457
1695
  * Ping the Campaign server (/nl/jsp/ping.jsp)
1458
- *
1696
+ *
1459
1697
  * @returns {Campaign.PingStatus} an object describing the server status
1460
1698
  */
1461
1699
  async ping() {
1462
1700
  const request = {
1463
- url: `${this._connectionParameters._endpoint}/nl/jsp/ping.jsp`,
1701
+ url: `${this._connectionParameters._endpoint}/nl/jsp/ping.jsp`,
1464
1702
  headers: {
1465
1703
  'X-Security-Token': this._securityToken,
1466
1704
  'Cookie': '__sessiontoken=' + this._sessionToken
@@ -1486,12 +1724,12 @@ class Client {
1486
1724
  /**
1487
1725
  * Ping a Message Center Campaign server (/nl/jsp/mcPing.jsp).
1488
1726
  * Assumes Message Center is installed
1489
- *
1727
+ *
1490
1728
  * @returns {Campaign.McPingStatus} an object describing Message Center server status
1491
1729
  */
1492
1730
  async mcPing() {
1493
1731
  const request = {
1494
- url: `${this._connectionParameters._endpoint}/nl/jsp/mcPing.jsp`,
1732
+ url: `${this._connectionParameters._endpoint}/nl/jsp/mcPing.jsp`,
1495
1733
  headers: {
1496
1734
  'X-Security-Token': this._securityToken,
1497
1735
  'Cookie': '__sessiontoken=' + this._sessionToken
@@ -1505,7 +1743,7 @@ class Client {
1505
1743
  const root = doc.documentElement;
1506
1744
  var status = lines[0].trim();
1507
1745
  if (status != "") root.setAttribute("status", status);
1508
-
1746
+
1509
1747
  var rtCount;
1510
1748
  var threshold;
1511
1749
  if (status == "Error") {
@@ -1548,4 +1786,4 @@ exports.Client = Client;
1548
1786
  exports.Credentials = Credentials;
1549
1787
  exports.ConnectionParameters = ConnectionParameters;
1550
1788
 
1551
- })();
1789
+ })();