@ideem/zsm-client-sdk 2.3.0 → 2.4.1

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.
@@ -1,6 +1,6 @@
1
1
  ZSM Client SDK for Browser Apps
2
- Latest version: v2.3.0
3
- Latest release date: March 24, 2025
2
+ Latest version: v2.4.1
3
+ Latest release date: May 5, 2025
4
4
 
5
5
  The ZSM Client SDK for Browser Apps provides the resources needed to integrate Ideem's FIDO2 Authenticator
6
6
  and Universal MFA into a browser application.
@@ -15,7 +15,23 @@ and Universal MFA into a browser application.
15
15
 
16
16
  ----
17
17
 
18
- ## Release 2.3.0 - Mar 24, 2025 [ NPM PACKAGE RELEASE ] **[ LATEST ]**
18
+ ## Release 2.4.1 - May 5, 2025 [ NPM PACKAGE RELEASE ] **[ LATEST ]**
19
+ - Minor bug fixes and improvements
20
+ - Updates to documentation
21
+ - Updates to sample apps
22
+ - Alters worker communication protocol to ensure that the worker is not blocked
23
+ by the main thread and vice versa, while still ensuring that the initialization
24
+ of the worker is completed before any other operations are performed.
25
+
26
+ ## Release 2.4.0 - May 1, 2025
27
+ - Minor bug fixes and improvements
28
+ - Updates to documentation
29
+ - Adds event emitters to the UMFAClient and FIDO2Client for on-the-fly initializations
30
+ - Adds extended multi-tenancy support for the UMFAClient and FIDO2Client
31
+ - Adds increased detail to performance logging
32
+ - Extends secure local storage support
33
+
34
+ ## Release 2.3.0 - Mar 24, 2025
19
35
  - Minor bug fixes and improvements
20
36
  - Updates to documentation
21
37
  - Introduces web-worker-driven JS SDK Client for browser-based applications
package/ZSMClientSDK.js CHANGED
@@ -1,9 +1,9 @@
1
1
  // Alias environment-specific global object
2
- const GLOBAL = typeof globalThis !== "undefined" ? globalThis
3
- : typeof self !== "undefined" ? self
4
- : typeof window !== "undefined" ? window
5
- : typeof global !== "undefined" ? global
6
- : (() => { throw new Error("Unable to determine global scope"); })();
2
+ const GLOBAL = typeof globalThis !== "undefined" ? globalThis
3
+ : typeof self !== "undefined" ? self
4
+ : typeof window !== "undefined" ? window
5
+ : typeof global !== "undefined" ? global
6
+ : (() => { throw new Error("Unable to determine global scope"); })();
7
7
 
8
8
  // Error class for worker errors
9
9
  class WorkerError extends Error {
@@ -21,114 +21,171 @@ class WorkerError extends Error {
21
21
 
22
22
  class ZSMClientBase {
23
23
  constructor(config) {
24
- this.config = config;
24
+ GLOBAL.zsmClient = null;
25
25
  this.clientWorker = null;
26
26
  this.timeoutTimer = null;
27
- GLOBAL.zsmClient = null;
27
+ this.clientType = null;
28
+ this.ready = false;
29
+ this.config = config;
28
30
  }
29
-
30
- async init(clientType = 'UMFAClient', config = this.config) {
31
- if(GLOBAL.clientWorker) GLOBAL.clientWorker.terminate();
31
+
32
+ onReady(onReadyFn=null) {
33
+ return new Promise((resolve) => {
34
+ if(this.ready) return resolve(true);
35
+ window.addEventListener(`${this.clientType}Ready`, async (e) => {
36
+ this.ready = true;
37
+ if(onReadyFn) await onReadyFn?.();
38
+ resolve(this);
39
+ }, { once: true });
40
+ });
41
+ }
42
+
43
+ /**
44
+ * @name init
45
+ * @description Initializes the ZSMClient instance and sets up the worker.
46
+ * @param {String} clientType The type of client to initialize [default: 'UMFAClient']
47
+ * @param {Object} config The configuration object for the client [default: this.config]
48
+ * @returns {Promise<ZSMClientBase>} A promise that resolves to the initialized ZSMClient instance.
49
+ */
50
+ async init(clientType='UMFAClient', config=this.config) {
51
+ this.onReady();
52
+ this.ready = false;
53
+ this.clientType = clientType;
54
+
32
55
  const workerPath = GLOBAL?.workerPath ? GLOBAL.workerPath
33
- : config?.workerPath ? config.workerPath
34
- : './worker.js';
56
+ : config?.workerPath ? config.workerPath
57
+ : './worker.js';
35
58
  const pathResolver = (import.meta && import.meta?.resolve) ? import.meta?.resolve(workerPath)
36
- : (import.meta && import.meta?.url) ? import.meta?.url.replace(/\/[^\/]+$/, '/' + workerPath)
37
- : document?.currentScript ? document?.currentScript?.src?.replace(/\/[^\/]+$/, '/' + workerPath)
38
- : workerPath;
59
+ : (import.meta && import.meta?.url) ? import.meta?.url.replace(/\/[^\/]+$/, '/' + workerPath)
60
+ : document?.currentScript ? document?.currentScript?.src?.replace(/\/[^\/]+$/, '/' + workerPath)
61
+ : workerPath;
39
62
  GLOBAL.clientWorker = this.clientWorker = new Worker(new URL(pathResolver));
40
63
  GLOBAL.zsmClient = this;
41
64
  await this.callWorkerFunction("init", {config, clientType}, false);
42
65
  return this;
43
66
  }
44
-
45
- async callWorkerFunction(functionName, data, acknowledgementExpected=true) {
46
- return new Promise((resolve, reject) => {
67
+
68
+ /**
69
+ * @name callWorkerFunction
70
+ * @description Calls a function in the worker and returns a promise that resolves with the result.
71
+ * @param {String} functionName The name of the function to call in the worker.
72
+ * @param {Object} data The data to pass to the worker function.
73
+ * @param {Boolean} responseExpected Whether an acknowledgement is expected [Default: true]
74
+ * @returns {Promise<Object>} A promise that resolves with the result of the worker function.
75
+ */
76
+ async callWorkerFunction(functionName, data, responseExpected=true) {
77
+ if (functionName !== 'init' && !this.ready) await this.onReady();
78
+ if(!this.clientWorker) {
79
+ console.error('Worker not initialized. Please call init() before using this method.');
80
+ return Promise.reject(new WorkerError('Worker', 'Worker not initialized. Please call init() before using this method.'));
81
+ }
82
+ let workerPromise = new Promise((resolve, reject) => {
47
83
  this.clientWorker.postMessage({functionName, data });
48
- if(acknowledgementExpected) this.timeoutTimer = GLOBAL.setTimeout(() => reject(new WorkerError('Transaction', 'Transaction timed out after 10 seconds!')), 10000);
49
-
84
+ if(responseExpected) this.timeoutTimer = GLOBAL.setTimeout(() => reject(new WorkerError('Transaction', 'Transaction timed out after 60 seconds!')), 60000);
85
+
50
86
  this.clientWorker.onmessage = (event) => {
51
87
  GLOBAL.clearTimeout(this.timeoutTimer);
52
-
88
+
53
89
  let workerResponse = event.data;
54
- if(GLOBAL.showWorkerTelemetry && (workerResponse.result || workerResponse.error)) {
90
+
91
+ if((GLOBAL.showWorkerTelemetry) && (workerResponse.result || workerResponse.error)) {
55
92
  console.groupCollapsed(`%cWorker posted to Main Thread a response to invocation:`, 'font-weight:bold; color:#F60;', functionName)
56
93
  console.log(`%c Function Invoked:`, 'font-weight:100; color:#AAA;', workerResponse.functionName);
57
94
  console.log(`%c Success Status:`, 'font-weight:100; color:#AAA;', workerResponse.success ? 'true' : 'false');
58
- console.log(`%c Result value:`, 'font-weight:100; color:#AAA;', (workerResponse.success ? workerResponse.result : workerResponse.error));
95
+ console.log(`%c Result value:`, 'font-weight:100; color:#AAA;', (workerResponse.success ? workerResponse.result : workerResponse.error));
59
96
  console.log(`%c Response Object:`, 'font-weight:100; color:#AAA;', workerResponse);
60
97
  console.groupEnd();
61
98
  }
62
-
99
+
63
100
  if (workerResponse instanceof Error || workerResponse.error || !workerResponse.success) {
64
101
  return reject(workerResponse.error);
65
102
  }
66
103
  resolve(workerResponse.result);
67
104
  };
68
-
105
+
69
106
  this.clientWorker.onerror = (err) => { GLOBAL.clearTimeout(this.timeoutTimer); reject(new WorkerError('', err.message)); };
70
107
  this.clientWorker.onmessageerror = (err) => { GLOBAL.clearTimeout(this.timeoutTimer); reject(new WorkerError('Message', err.message)); };
71
108
  });
109
+
110
+ /**
111
+ * @event workerEvent
112
+ * @description Event triggered when the worker posts an event to the main thread.
113
+ * @param {String} eventName The name of the event.
114
+ * @param {Object} eventData The data associated with the event.
115
+ * @returns {void}
116
+ * @emits {CustomEvent} event The event object containing the received event name and data.
117
+ * @example GLOBAL.addEventListener('workerEvent', (event) => {
118
+ * @ console.log('Received event from worker:', event.detail.eventName, event.detail.eventData);
119
+ * @ });
120
+ */
121
+ this.clientWorker.addEventListener('message', (event) => {
122
+ if(!event.data || !event.data.eventName) return;
123
+ const { eventName, eventData } = event.data;
124
+ if(GLOBAL.showWorkerTelemetry) console.log(`%cWorker posted to Main Thread an event:`, 'font-weight:bold; color:#F60;', eventName, eventData);
125
+ GLOBAL.dispatchEvent(new CustomEvent(eventName, { detail: eventData }));
126
+ });
127
+ return workerPromise;
72
128
  }
73
129
  }
74
130
 
75
131
  class UMFAClient extends ZSMClientBase {
76
132
  constructor(config) {
77
133
  super(config);
134
+ this.clientType = 'UMFAClient';
78
135
  this.init = super.init.bind(this);
79
136
  this.callWorkerFunction = super.callWorkerFunction.bind(this);
137
+ // this.onReady = super.onReady.bind(this);
80
138
  this.checkEnrollment = this.checkEnrollment.bind(this);
81
139
  this.enroll = this.enroll.bind(this);
82
140
  this.authenticate = this.authenticate.bind(this);
83
- this.optimizedEnroll = this.optimizedEnroll.bind(this);
84
- this.optimizedAuthenticate = this.optimizedAuthenticate.bind(this);
141
+ this.unenroll = this.unenroll.bind(this);
85
142
  this.init('UMFAClient');
86
143
  }
87
-
88
- async checkEnrollment(user) { return await this.callWorkerFunction('checkEnrollment', user); }
89
- async enroll(user) { return await this.callWorkerFunction('enroll', user); }
90
- async authenticate(user) { return await this.callWorkerFunction('authenticate', user); }
91
- async optimizedEnroll(user) { return await this.callWorkerFunction('optimizedEnroll', user); }
92
- async optimizedAuthenticate(user) { return await this.callWorkerFunction('optimizedAuthenticate', user); }
93
- async unenroll(user) { return await this.callWorkerFunction('unenroll', user); }
144
+
145
+ checkEnrollment(user) { return this.callWorkerFunction('checkEnrollment', user); }
146
+ enroll(user) { return this.callWorkerFunction('enroll', user); }
147
+ authenticate(user) { return this.callWorkerFunction('authenticate', user); }
148
+ unenroll(user) { return this.callWorkerFunction('unenroll', user); }
94
149
  }
95
150
 
96
151
  class FIDO2Client extends ZSMClientBase {
97
152
  constructor(config) {
98
- super(config);
153
+ super(config);
154
+ this.clientType = 'FIDO2Client';
99
155
  this.init = super.init.bind(this);
100
156
  this.callWorkerFunction = super.callWorkerFunction.bind(this);
101
- this.checkEnrollment = this.checkEnrollment.bind(this);
157
+ this.onReady = super.onReady.bind(this);
102
158
  this.checkIdentity = this.checkIdentity.bind(this);
159
+ this.checkEnrollment = this.checkEnrollment.bind(this);
103
160
  this.webauthnCreate = this.webauthnCreate.bind(this);
104
161
  this.webauthnGet = this.webauthnGet.bind(this);
105
162
  this.webauthnRetrieve = this.webauthnRetrieve.bind(this);
106
163
  this.webauthnDelete = this.webauthnDelete.bind(this);
107
164
  this.init('FIDO2Client');
108
165
  }
109
-
110
- async checkIdentity(user, auto=true) { return await this.callWorkerFunction('checkIdentity', [user, auto]); }
111
- async checkEnrollment(user) { return await this.callWorkerFunction('checkEnrollment', user); }
112
- async webauthnCreate(user) { return await this.callWorkerFunction('webauthnCreate', user); }
113
- async webauthnGet(user) { return await this.callWorkerFunction('webauthnGet', user); }
114
- async webauthnRetrieve(user) { return await this.callWorkerFunction('webauthnRetrieve', user); }
115
- async webauthnDelete(user) { return await this.callWorkerFunction('webauthnDelete', user); }
166
+
167
+ checkIdentity = (user, auto=true) => this.callWorkerFunction('checkIdentity', [user, auto]);
168
+ checkEnrollment = (user) => this.callWorkerFunction('checkEnrollment', user);
169
+ webauthnCreate = (user) => this.callWorkerFunction('webauthnCreate', user);
170
+ webauthnGet = (user) => this.callWorkerFunction('webauthnGet', user);
171
+ webauthnRetrieve = (user) => this.callWorkerFunction('webauthnRetrieve', user);
172
+ webauthnDelete = (user) => this.callWorkerFunction('webauthnDelete', user);
116
173
  }
117
174
 
118
175
  function amendPerformanceLoggingToClass(cls) {
119
176
  GLOBAL.sessionStamp = new Date();
120
177
  GLOBAL.sessionStamp = new Date(GLOBAL.sessionStamp.getTime() - (GLOBAL.sessionStamp.getTimezoneOffset() * 60000)).toISOString().slice(11, -5);
121
178
  GLOBAL.exportCount = 0;
122
-
179
+
123
180
  if(GLOBAL.logStack == null) {
124
181
  GLOBAL.logStack = [];
125
182
  GLOBAL.getLogs = () => GLOBAL.logStack.sort((a, b) => a.stamp - b.stamp);
126
183
  GLOBAL.clearLogs = () => {GLOBAL.logStack = []; };
127
184
  GLOBAL.showLogs = () => {
128
185
  if(!GLOBAL.logStack || GLOBAL.logStack.length === 0) return console.info('There are no logs currently in the stack!');
129
-
186
+
130
187
  const getTime = (ISOStamp) => new Date(ISOStamp).toISOString().split('T')[1].slice(0,-1);
131
-
188
+
132
189
  (function parseLog(stack=[...GLOBAL.logStack], parentEndTime = Infinity) {
133
190
  while (stack.length > 0 && stack[0].startTime < parentEndTime) {
134
191
  let markS, markE, spanName, indE, duration, stamps;
@@ -138,14 +195,14 @@ function amendPerformanceLoggingToClass(cls) {
138
195
  if (indE !== -1) markE = stack.splice(indE, 1)[0];
139
196
  duration = (markE != null) ? (markE.startTime - markS.startTime).toFixed(4) + 'ms' : 'ONGOING';
140
197
  stamps = (markE != null) ? getTime(markS.stamp) + " - " + getTime(markE.stamp) : getTime(markS.stamp) + " - ...";
141
-
198
+
142
199
  console.groupCollapsed(spanName.replace(/-/g, ' :: ') + ' :: ' + duration + " (" + stamps + ")");
143
200
  parseLog(stack, markE.startTime);
144
201
  console.groupEnd();
145
202
  }
146
203
  })()
147
204
  }
148
-
205
+
149
206
  GLOBAL.addPerfMarker = (tag) => {
150
207
  const {name, startTime} = performance.mark(tag).toJSON();
151
208
  GLOBAL.logStack.push({
@@ -157,14 +214,14 @@ function amendPerformanceLoggingToClass(cls) {
157
214
  });
158
215
  }
159
216
  }
160
-
217
+
161
218
  return class extends cls {
162
219
  constructor(...args) {
163
220
  super(...args);
164
-
221
+
165
222
  Object.getOwnPropertyNames(this).forEach(propName => {
166
223
  if (propName === "constructor" || typeof this[propName] !== "function") return;
167
-
224
+
168
225
  const originalMethod = this[propName];
169
226
  let markerTagPrefix = `[MAINTHREAD]-${cls.name}-${propName}`;
170
227
  this[propName] = function(...args) {
@@ -190,12 +247,11 @@ if(typeof FIDO2Client !== "undefined" && FIDO2Client instanceof Function) FIDO2C
190
247
  let masterLogs = [];
191
248
  if(window){
192
249
  let tzO = new Date().getTimezoneOffset() * 60000;
193
-
250
+
194
251
  GLOBAL.getAllLogs = async (showCSV) => {
195
- let res = await zsmClient.callWorkerFunction("getLogs");
252
+ let res = await zsmClient.callWorkerFunction("getLogs");
196
253
  let {logs, timeOrigin} = JSON.parse(res);
197
254
  let timeDifferential = performance.timeOrigin - timeOrigin;
198
- console.log('timeDifferential :', timeDifferential);
199
255
  if(!showCSV) {
200
256
  console.groupCollapsed('RAW logs');
201
257
  GLOBAL.logStack.forEach(mainRecord => console.info('mainRecord :', mainRecord));
@@ -214,20 +270,32 @@ if(window){
214
270
  allLogs.forEach(log => console.info('log :', log));
215
271
  console.groupEnd();
216
272
  }
273
+ console.groupCollapsed('ALL logs (Combined and Sorted)');
274
+ allLogs = allLogs.map(log => {
275
+ if(!localStorage) return log;
276
+ let activeEnv = localStorage.getItem('activeEnv');
277
+ if(!activeEnv) return log;
278
+ if(! /RELPARTYSERVER|CRYPTOSERVER/.test(log.type)) return log;
279
+ // log.type += '-' + activeEnv.toUpperCase();
280
+ return log;
281
+ });
282
+ allLogs.forEach(log => console.info('log :', log));
283
+ console.groupEnd();
284
+
217
285
  return allLogs;
218
286
  };
219
-
287
+
220
288
  GLOBAL.showLogs = async (showCSV=false) => {
221
- const getTime = (ISOStamp) => {
289
+ const getTime = (ISOStamp, timeOnly=true) => {
222
290
  try{
223
- return new Date((+ISOStamp - tzO)).toISOString().slice(11,-1);
291
+ return timeOnly ? new Date((+ISOStamp - tzO)).toISOString().slice(11,-1) : new Date(+ISOStamp - tzO).toISOString();
224
292
  }catch(e){
225
293
  console.error('Error parsing ISOStamp:', ISOStamp, e);
226
294
  return ISOStamp;
227
295
  }
228
296
  }
229
297
  const stripStartEnd = (name) => name.replace(/-(START|END)$/gi, '');
230
-
298
+
231
299
  function recursivelyMoveChildLogsIntoStartNode(stack) {
232
300
  let processedLogs = [];
233
301
  while (stack.length > 0) {
@@ -237,42 +305,46 @@ if(window){
237
305
  }
238
306
  let endingMarker = stack.findIndex(log => log.name === stripStartEnd(startingMarker.name) + '-END'),
239
307
  dateTimeStamps = [],
308
+ fullDateStamps = [],
240
309
  stackSubset = [],
241
310
  totalDuration = 0,
242
311
  networkLatency = 0,
243
312
  computeDuration = 0;
244
-
313
+
245
314
  if(!~endingMarker) {
246
315
  stackSubset = stack;
247
316
  endingMarker = stack.length;
248
317
  totalDuration = 'ONGOING';
249
318
  dateTimeStamps.push(getTime(startingMarker.stamp), '...?');
250
-
319
+ fullDateStamps.push(getTime(startingMarker.stamp, false), '...?');
320
+
251
321
  }else{
252
322
  stackSubset = stack.splice(0, endingMarker+1);
253
323
  endingMarker = stackSubset[stackSubset.length-1];
254
324
  totalDuration = endingMarker.absTime - startingMarker.absTime;
255
325
  dateTimeStamps.push(getTime(startingMarker.stamp), getTime(endingMarker.stamp));
256
-
326
+ fullDateStamps.push(getTime(startingMarker.stamp, false), getTime(endingMarker.stamp, false));
327
+
257
328
  }
258
-
329
+
259
330
  processedLogs.push(Object.assign(startingMarker, {
260
331
  name: stripStartEnd(startingMarker.name),
261
332
  children: recursivelyMoveChildLogsIntoStartNode(stackSubset),
262
- totalDuration,
333
+ totalDuration,
263
334
  dateTimeStamps,
335
+ fullDateStamps,
264
336
  networkLatency,
265
337
  computeDuration
266
338
  }));
267
339
  }
268
340
  return processedLogs;
269
341
  }
270
-
342
+
271
343
  function recursivelyComputeCumulativeNetworkLatency(node) {
272
344
  let nodeLatency = 0;
273
345
  if(node.clientToServerLatency) nodeLatency += node.clientToServerLatency;
274
346
  if(node.serverToClientLatency) nodeLatency += node.serverToClientLatency;
275
-
347
+
276
348
  if (Array.isArray(node.children) && node.children.length > 0) {
277
349
  node.children.forEach(child => {
278
350
  nodeLatency += recursivelyComputeCumulativeNetworkLatency(child);
@@ -281,7 +353,7 @@ if(window){
281
353
  node.networkLatency = nodeLatency;
282
354
  return nodeLatency;
283
355
  }
284
-
356
+
285
357
  function calculateComputeDurationForNode(node) {
286
358
  if(!node.children) return node.computeDuration = totalDuration;
287
359
  node.computeDuration = (node.children.reduce((acc,child)=>{
@@ -289,9 +361,9 @@ if(window){
289
361
  }, node.totalDuration))
290
362
  node.children.forEach(calculateComputeDurationForNode)
291
363
  }
292
-
364
+
293
365
  let exportableStack = [];
294
-
366
+
295
367
  function recursivelyOutputLogsToConsole(stack, depth = 0) {
296
368
  let outputColors={
297
369
  'MAINTHREAD': '#509',
@@ -301,20 +373,20 @@ if(window){
301
373
  'RELPARTY': '#076',
302
374
  'CRYPTO': '#700',
303
375
  };
304
-
376
+
305
377
  function formatNumberColumn(num, NaNValue='0.000') {
306
378
  let opValue = isNaN(num) ? NaNValue : Math.abs(num).toFixed(3);
307
379
  let lPadStr = ' '.repeat(12 - opValue.length);
308
380
  return lPadStr + opValue;
309
381
  }
310
-
382
+
311
383
  stack.forEach(item => {
312
384
  let spacePadding = ' '.repeat(100);
313
385
  let exportableRecord = {}
314
386
  let sequence = item.order;
315
- let nameElements = item.name.replace(/[\[\]]/g, '').split(/(SERVER)?-/);
316
- let threadName = (nameElements[0] + spacePadding).slice(0, 14);
317
- let processName = (nameElements[1] ? nameElements[1] : item.name + spacePadding).slice(0, 70-(depth*2));
387
+ let nameElements = (item.name.match(/\[(.*?)\]-(.*)$/) || new Array(3).fill('')).slice(1,3);
388
+ let threadName = (nameElements[0] + spacePadding).replace('SERVER', '').slice(0, 14);
389
+ let processName = (nameElements[1] + spacePadding).slice(0, 70-(depth*2));
318
390
  let totalDuration = formatNumberColumn(item?.totalDuration, 'ONGOING');
319
391
  let computeDuration = (/ONGOING/.test(totalDuration)) ? formatNumberColumn('ONGOING', 'ONGOING') : formatNumberColumn(item?.computeDuration);
320
392
  let networkLatency = (/ONGOING/.test(totalDuration)) ? formatNumberColumn('ONGOING', 'ONGOING') : formatNumberColumn(item?.networkLatency);
@@ -324,11 +396,17 @@ if(window){
324
396
  if(totalDuration < (Math.abs(networkLatency) + Math.abs(computeDuration))) totalDuration = formatNumberColumn(Math.abs(networkLatency) + Math.abs(computeDuration));
325
397
  cumulativeCompute = formatNumberColumn((totalDuration - networkLatency - computeDuration), 'ONGOING');
326
398
  }
327
- let indentionPads = depth > 0 ? ' '.repeat(depth-1) + ' ' : ' ';
328
- let detailPads = depth > 0 ? ' '.repeat(depth-1) + ' '.repeat(threadName.length) + ' ' : ' ' + ' '.repeat(threadName.length);
399
+ let indentionDepth = depth;
400
+ if (/^CRYPTO/.test(threadName)) {
401
+ indentionDepth = depth + 2;
402
+ processName = processName.slice(0, -4);
403
+ }
404
+
405
+ let indentionPads = depth > 0 ? ' '.repeat(indentionDepth-1) + ' ' : ' ';
406
+ let detailPads = depth > 0 ? ' '.repeat(indentionDepth-1) + ' '.repeat(threadName.length) + ' ' : ' ' + ' '.repeat(threadName.length);
329
407
  let timeSpan = item?.dateTimeStamps ? (' ' + item.dateTimeStamps[0]) : 'N/A';
330
-
331
- if(!showCSV) {
408
+
409
+ // if(!showCSV) {
332
410
  let outputLine = ` ${threadName}${indentionPads} ${processName} ${totalDuration} ${networkLatency} ${cumulativeCompute} ${computeDuration} ${timeSpan}`;
333
411
  console.groupCollapsed(`%c${outputLine}`, `color: ${outputColors[item.type]}; font-weight: 500;`);
334
412
  console.info(`%c${detailPads} Sequence: `, `color: ${outputColors[item.type]}; font-weight: 700;`, sequence);
@@ -343,7 +421,13 @@ if(window){
343
421
  console.info(`%c${detailPads} Process Compute Duration: `, `color: ${outputColors[item.type]}; font-weight: 700;`, (/ONGOING/.test(computeDuration) ? 'ONGOING' : computeDuration.trim() + 'ms'));
344
422
  console.info(`%c${detailPads} ================================================================`, `color: ${outputColors[item.type]}; font-weight: 700;`, "\n\n");
345
423
  console.groupEnd();
346
- } else {
424
+ // } else if(!showCSV || showCSV) {
425
+ let activeEnv = '';
426
+ let pageHost = window.location.host;
427
+
428
+ if(localStorage && localStorage.getItem('activeEnv')) {
429
+ activeEnv = (/RELPARTY|CRYPTO/.test(threadName)) ? localStorage.getItem('activeEnv') : 'N/A';
430
+ }
347
431
  exportableRecord = {
348
432
  sequence : sequence,
349
433
  threadName : threadName.trim(),
@@ -353,24 +437,27 @@ if(window){
353
437
  networkLatency : (/ONGOING/.test(networkLatency) ? 'ONGOING' : +(networkLatency.trim())),
354
438
  cumulativeCompute : (/ONGOING/.test(cumulativeCompute) ? 'ONGOING' : +(cumulativeCompute.trim())),
355
439
  computeDuration : (/ONGOING/.test(computeDuration) ? 'ONGOING' : +(computeDuration.trim())),
356
- startTime : (item?.dateTimeStamps[0]??''),
357
- endTime : (item?.dateTimeStamps[1]??''),
358
- spawnsChildren : item?.children ? item.children.length : 0
440
+ startTime : (item?.fullDateStamps[0]?.replace(/[TZ]/g, ' ').trim() ?? ''),
441
+ endTime : (item?.fullDateStamps[1]?.replace(/[TZ]/g, ' ').trim() ?? ''),
442
+ spawnsChildren : item?.children ? item.children.length : 0,
443
+ host : pageHost,
444
+ remoteAPIEnv : activeEnv,
359
445
  };
360
-
446
+
361
447
  exportableStack.push(exportableRecord);
362
- }
448
+ // }
363
449
  if(item.children.length > 0) recursivelyOutputLogsToConsole(item.children, depth + 1);
364
450
  });
365
451
  return exportableStack.length ? exportableStack : undefined;
366
452
  }
367
-
453
+
368
454
  let allLogs = await GLOBAL.getAllLogs(showCSV);
369
455
  let processedStack = recursivelyMoveChildLogsIntoStartNode(allLogs);
370
456
  processedStack.forEach(root => recursivelyComputeCumulativeNetworkLatency(root));
371
457
  processedStack.forEach(root => calculateComputeDurationForNode(root));
372
458
  if(!showCSV) console.info(`%c${' THREAD PROCESS NAME DURATION NETWORK TOT.COMP. COMPUTE START TIME'}`, 'color: #000; font-weight: 700;');
373
459
  let exportableData = recursivelyOutputLogsToConsole(processedStack);
460
+ console.log('exportableData :', exportableData);
374
461
  if(!showCSV || !exportableData) return;
375
462
  let csvData = exportableData.flatMap((log, i) => {
376
463
  let retVal = [Object.values(log)];
@@ -379,7 +466,7 @@ if(window){
379
466
  if(i === 0) retVal.unshift('"' + Object.keys(log).join('","') + '"');
380
467
  return retVal;
381
468
  });
382
-
469
+
383
470
  try {
384
471
  GLOBAL.exportCount++;
385
472
  console.groupCollapsed(`CSV Data Exported as %c${GLOBAL.sessionStamp}-${(GLOBAL.exportCount)}.csv`, 'color: #F60; font-weight: 700;');
@@ -394,15 +481,15 @@ if(window){
394
481
  URL.revokeObjectURL(url);
395
482
  GLOBAL.logStack = [];
396
483
  zsmClient.callWorkerFunction("clearLogs", true);
397
- console.groupEnd();
484
+ console.groupEnd();
398
485
  } catch (error) {
399
486
  console.error('Error during download:', error);
400
487
  console.groupEnd();
401
488
  }
402
-
489
+
403
490
  return exportableData;
404
491
  }
405
-
492
+
406
493
  }
407
494
 
408
495
  export { UMFAClient, FIDO2Client };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ideem/zsm-client-sdk",
3
- "version": "2.3.0",
3
+ "version": "2.4.1",
4
4
  "description": "ZSM makes 2FA easy and invisible for everyone, all the time, using advanced cryptography like MPC to establish cryptographic proof of the origin of any transaction or login attempt, while eliminating opportunities for social engineering. ZSM has no reliance on specific hardware. Our cryptographic module has completed FIPS 140-3 NIST approved lab testing, is FIDO2-ready, and is on the Modules In Progress (MIP) list. Superior Usability. no opt-in, no codes, no downloads, no authenticator apps. Better Security.",
5
5
  "author": "@JJ (Jason Jurusz) <jj@useideem.com>/<nerdy.deeds@done.cheap>",
6
6
  "keywords": [
@@ -40,7 +40,7 @@
40
40
  "files": [
41
41
  "worker.js",
42
42
  "ZSMClientSDK.js",
43
- "RELEASE_NOTES.txt",
43
+ "CHANGELOG.md",
44
44
  "LICENSE",
45
45
  "README.md"
46
46
  ],