@ideem/zsm-client-sdk 2.4.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,15 @@ and Universal MFA into a browser application.
15
15
 
16
16
  ----
17
17
 
18
- ## Release 2.4.0 - May 1, 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
19
27
  - Minor bug fixes and improvements
20
28
  - Updates to documentation
21
29
  - Adds event emitters to the UMFAClient and FIDO2Client for on-the-fly initializations
package/ZSMClientSDK.js CHANGED
@@ -21,10 +21,23 @@ 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;
30
+ }
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
+ });
28
41
  }
29
42
 
30
43
  /**
@@ -35,7 +48,10 @@ class ZSMClientBase {
35
48
  * @returns {Promise<ZSMClientBase>} A promise that resolves to the initialized ZSMClient instance.
36
49
  */
37
50
  async init(clientType='UMFAClient', config=this.config) {
38
- if(GLOBAL.clientWorker) GLOBAL.clientWorker.terminate();
51
+ this.onReady();
52
+ this.ready = false;
53
+ this.clientType = clientType;
54
+
39
55
  const workerPath = GLOBAL?.workerPath ? GLOBAL.workerPath
40
56
  : config?.workerPath ? config.workerPath
41
57
  : './worker.js';
@@ -58,6 +74,11 @@ class ZSMClientBase {
58
74
  * @returns {Promise<Object>} A promise that resolves with the result of the worker function.
59
75
  */
60
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
+ }
61
82
  let workerPromise = new Promise((resolve, reject) => {
62
83
  this.clientWorker.postMessage({functionName, data });
63
84
  if(responseExpected) this.timeoutTimer = GLOBAL.setTimeout(() => reject(new WorkerError('Transaction', 'Transaction timed out after 60 seconds!')), 60000);
@@ -67,7 +88,7 @@ class ZSMClientBase {
67
88
 
68
89
  let workerResponse = event.data;
69
90
 
70
- if(GLOBAL.showWorkerTelemetry && (workerResponse.result || workerResponse.error)) {
91
+ if((GLOBAL.showWorkerTelemetry) && (workerResponse.result || workerResponse.error)) {
71
92
  console.groupCollapsed(`%cWorker posted to Main Thread a response to invocation:`, 'font-weight:bold; color:#F60;', functionName)
72
93
  console.log(`%c Function Invoked:`, 'font-weight:100; color:#AAA;', workerResponse.functionName);
73
94
  console.log(`%c Success Status:`, 'font-weight:100; color:#AAA;', workerResponse.success ? 'true' : 'false');
@@ -110,31 +131,32 @@ class ZSMClientBase {
110
131
  class UMFAClient extends ZSMClientBase {
111
132
  constructor(config) {
112
133
  super(config);
134
+ this.clientType = 'UMFAClient';
113
135
  this.init = super.init.bind(this);
114
136
  this.callWorkerFunction = super.callWorkerFunction.bind(this);
137
+ // this.onReady = super.onReady.bind(this);
115
138
  this.checkEnrollment = this.checkEnrollment.bind(this);
116
139
  this.enroll = this.enroll.bind(this);
117
140
  this.authenticate = this.authenticate.bind(this);
118
- this.optimizedEnroll = this.optimizedEnroll.bind(this);
119
- this.optimizedAuthenticate = this.optimizedAuthenticate.bind(this);
141
+ this.unenroll = this.unenroll.bind(this);
120
142
  this.init('UMFAClient');
121
143
  }
122
144
 
123
- async checkEnrollment(user) { return await this.callWorkerFunction('checkEnrollment', user); }
124
- async enroll(user) { return await this.callWorkerFunction('enroll', user); }
125
- async authenticate(user) { return await this.callWorkerFunction('authenticate', user); }
126
- async optimizedEnroll(user) { return await this.callWorkerFunction('optimizedEnroll', user); }
127
- async optimizedAuthenticate(user) { return await this.callWorkerFunction('optimizedAuthenticate', user); }
128
- async unenroll(user) { return await this.callWorkerFunction('unenroll', user); }
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); }
129
149
  }
130
150
 
131
151
  class FIDO2Client extends ZSMClientBase {
132
152
  constructor(config) {
133
153
  super(config);
154
+ this.clientType = 'FIDO2Client';
134
155
  this.init = super.init.bind(this);
135
156
  this.callWorkerFunction = super.callWorkerFunction.bind(this);
136
- this.checkEnrollment = this.checkEnrollment.bind(this);
157
+ this.onReady = super.onReady.bind(this);
137
158
  this.checkIdentity = this.checkIdentity.bind(this);
159
+ this.checkEnrollment = this.checkEnrollment.bind(this);
138
160
  this.webauthnCreate = this.webauthnCreate.bind(this);
139
161
  this.webauthnGet = this.webauthnGet.bind(this);
140
162
  this.webauthnRetrieve = this.webauthnRetrieve.bind(this);
@@ -142,12 +164,12 @@ class FIDO2Client extends ZSMClientBase {
142
164
  this.init('FIDO2Client');
143
165
  }
144
166
 
145
- async checkIdentity(user, auto=true) { return await this.callWorkerFunction('checkIdentity', [user, auto]); }
146
- async checkEnrollment(user) { return await this.callWorkerFunction('checkEnrollment', user); }
147
- async webauthnCreate(user) { return await this.callWorkerFunction('webauthnCreate', user); }
148
- async webauthnGet(user) { return await this.callWorkerFunction('webauthnGet', user); }
149
- async webauthnRetrieve(user) { return await this.callWorkerFunction('webauthnRetrieve', user); }
150
- async webauthnDelete(user) { return await this.callWorkerFunction('webauthnDelete', user); }
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);
151
173
  }
152
174
 
153
175
  function amendPerformanceLoggingToClass(cls) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ideem/zsm-client-sdk",
3
- "version": "2.4.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
  ],
package/worker.js CHANGED
@@ -16,12 +16,13 @@ GLOBAL.clientsReadyDash = {
16
16
 
17
17
  GLOBAL.checkReadiness = (client=null, status=null) => {
18
18
  let retVal = false;
19
- if(GLOBAL.showWorkerTelemetry) console.log('GLOBAL.checkReadiness(client, status):', client, status, Object.values(GLOBAL.clientsReadyDash).reduce((c,i)=>!!c && !!i, true));
20
19
  if(status != null) retVal = (GLOBAL.clientsReadyDash[client] = status);
21
20
  else if(client != null) retVal = GLOBAL.clientsReadyDash[client];
21
+ if(GLOBAL.showWorkerTelemetry) console.log('GLOBAL.checkReadiness(client, status):', client, status, '\n Fully initialized?', Object.values(GLOBAL.clientsReadyDash).every(v=>v));
22
22
 
23
23
  const allClientsReady = retVal = Object.values(GLOBAL.clientsReadyDash).every(v=>v);
24
24
  if(allClientsReady) {
25
+ GLOBAL.dispatchEvent(new CustomEvent(`${GLOBAL.clientType}Ready`, {bubbles: true, cancelable: false, detail: true }));
25
26
  GLOBAL.dispatchEventToMain(`${GLOBAL.clientType}Ready`, true);
26
27
  GLOBAL.checkReadiness = () => true;
27
28
  }
@@ -150,7 +151,7 @@ GLOBAL.uid2iid = (uid, iid) => {
150
151
  idbInstance.onsuccess = (e, db=e.target.result) => {
151
152
  const zsmTbl = db.transaction("zsm", "readwrite");
152
153
  const write = zsmTbl.objectStore("zsm").put(iid, uid);
153
- write.onsuccess = () => (db.close(), console.log(`Bound UID "${uid}" to IID "${iid}".`));
154
+ write.onsuccess = () => (db.close());
154
155
  write.onerror = (err=write.error) => (db.close(), console.error(`Unable to bind UID to IID:\n${err}`));
155
156
  };
156
157
  }
@@ -1687,7 +1688,6 @@ class RelyingParty {
1687
1688
  * @returns {Promise<Object>} Resolves with the response data.
1688
1689
  */
1689
1690
  makePostRequest = (url, body={}, headers={}, method='POST') => {
1690
- console.log('this.customerDefinedIdentifier :', this.customerDefinedIdentifier);
1691
1691
  url = `${this.host.endsWith('/') ? this.host.slice(0,-1) : this.host}/${url.startsWith('/') ? url.slice(1) : url}`;
1692
1692
  body = ((typeof body === 'object') ? body : {});
1693
1693
  body = Object.assign({customer_defined_identifier: this.customerDefinedIdentifier, application_id: this.applicationId}, body);
@@ -1885,8 +1885,9 @@ class WebAuthnClient {
1885
1885
 
1886
1886
  if(!config?.authenticator_host_url) config.authenticator_host = config.authenticator_host_url = config.host_url;
1887
1887
  if(!config?.zsm_host_url) config.zsm_host_url = config.zsm_host = config.host_url.replace("zsm-authenticator", "zsm");
1888
+ config.host_url = config.zsm_host_url;
1888
1889
 
1889
- this.config = config;
1890
+ this.config = GLOBAL.zsmAppConfig = config;
1890
1891
  GLOBAL.iid4uid(this.config.consumer_id)
1891
1892
  .then(v=>{
1892
1893
  GLOBAL.mpcConfig = Object.assign({}, config, {consumer_id: v});
@@ -1935,11 +1936,13 @@ class WebAuthnClient {
1935
1936
  */
1936
1937
  initializeZsm = async () => {
1937
1938
  if(GLOBAL.mpcConfig.consumer_id !== this.MPCConfig.consumer_id) {
1938
- await this.zsmApi.configure(GLOBAL.mpcConfig);
1939
- this.MPCConfig = Object.assign({}, GLOBAL.mpcConfig);
1940
- GLOBAL.uid2iid(this.config.consumer_id, this.MPCConfig.consumer_id);
1939
+ // console.log('GLOBAL.mpcConfig :', Object.assign({},GLOBAL.mpcConfig));
1940
+ let xyz = await this.zsmApi.configure(GLOBAL.mpcConfig);
1941
+ await setTimeout(() => Promise.resolve(), 5000);
1942
+ this.MPCConfig = await Object.assign({}, GLOBAL.mpcConfig);
1943
+ await GLOBAL.uid2iid(this.config.consumer_id, this.MPCConfig.consumer_id);
1941
1944
  }
1942
- return this.zsmApi;
1945
+ return await this.zsmApi;
1943
1946
  }
1944
1947
 
1945
1948
  /**
@@ -2244,7 +2247,7 @@ class WebAuthnClient {
2244
2247
  await this.initializeZsm('unbindFromDevice');
2245
2248
 
2246
2249
  const rawID = await this.webauthnRetrieve(userIdentifier);
2247
- if (!rawID) throw new Error(`[unenroll] unbindFromDevice failed :: ${userIdentifier} is not enrolled!`);
2250
+ if (!rawID) return false;
2248
2251
 
2249
2252
  const request = indexedDB.open("ideem", 1);
2250
2253
  request.onsuccess = function(event) {
@@ -2287,6 +2290,8 @@ GLOBAL.UMFAClient = class UMFAClient {
2287
2290
  this.authenticate = this.authenticate.bind(this);
2288
2291
  this.resetDevice = this.resetDevice.bind(this);
2289
2292
  this.unenroll = this.unenroll.bind(this);
2293
+ this.loaded = false;
2294
+ GLOBAL.addEventListener(`${GLOBAL.clientType}Ready`, (e) => this.loaded=true);
2290
2295
  GLOBAL.dispatchEvent(new CustomEvent("ActiveClientReady", {bubbles: true, cancelable: false, detail: true }));
2291
2296
  }
2292
2297