@ideem/zsm-client-sdk 2.4.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{RELEASE_NOTES.txt → CHANGELOG.md} +42 -6
- package/EventCoordinator.js +267 -0
- package/FIDO2Client.js +15 -0
- package/FIDO2ClientBase.js +117 -0
- package/GlobalScoping.js +8 -0
- package/IdentityIndexing.js +51 -0
- package/PluginManager.js +111 -0
- package/RelyingParty.js +15 -0
- package/RelyingPartyBase.js +197 -0
- package/UMFAClient.js +15 -0
- package/UMFAClientBase.js +125 -0
- package/Utils.js +44 -0
- package/WASMRustInterface.js +1458 -0
- package/WebAuthnClient.js +29 -0
- package/WebAuthnClientBase.js +429 -0
- package/ZSMClientSDK.js +28 -463
- package/package.json +19 -50
- package/shims/VanillaJSLoader.js +96 -0
- package/shims/es-module-shims.js +1067 -0
- package/worker.js +0 -2503
package/RelyingParty.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import eventCoordinator from './EventCoordinator.js';
|
|
2
|
+
import RelyingPartyBase from './RelyingPartyBase.js';
|
|
3
|
+
|
|
4
|
+
eventCoordinator.update('RelyingParty');
|
|
5
|
+
|
|
6
|
+
//## BEGIN RelyingParty ===================================================================================================================================
|
|
7
|
+
class RelyingParty extends RelyingPartyBase {
|
|
8
|
+
constructor(host, apiKey, applicationID, appEnvironment, usePasskeys) {
|
|
9
|
+
super(host, apiKey, applicationID, appEnvironment, (usePasskeys != 'undefined' && usePasskeys != undefined && usePasskeys))
|
|
10
|
+
eventCoordinator.update('RelyingParty', 'READY');
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
//## END RelyingParty =====================================================================================================================================
|
|
14
|
+
|
|
15
|
+
export default RelyingParty;
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import eventCoordinator from './EventCoordinator.js';
|
|
2
|
+
import GLOBAL from './GlobalScoping.js';
|
|
3
|
+
import {uid2iid} from './IdentityIndexing.js';
|
|
4
|
+
|
|
5
|
+
eventCoordinator.update('RelyingPartyBase');
|
|
6
|
+
|
|
7
|
+
function tracePrimer (obj=false) {
|
|
8
|
+
const traceBase = [[new Date().toISOString(), "TRACEPRIMER"]]
|
|
9
|
+
return (obj) ? {"trace": traceBase} : traceBase;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
//## BEGIN RelyingPartyBase ===================================================================================================================================
|
|
13
|
+
class RelyingPartyBase {
|
|
14
|
+
/**
|
|
15
|
+
* Constructs a RelyingParty object.
|
|
16
|
+
* @param {string} host - The URL of the relying party server.
|
|
17
|
+
* @param {string} apiKey - The API key for authentication.
|
|
18
|
+
* @param {string} applicationID - The ID of the application.
|
|
19
|
+
*/
|
|
20
|
+
constructor(host, apiKey, applicationID, appEnvironment, useOrigin) {
|
|
21
|
+
this.host = host;
|
|
22
|
+
this.apiKey = apiKey;
|
|
23
|
+
this.applicationId = applicationID;
|
|
24
|
+
this.appEnvironment = appEnvironment || null;
|
|
25
|
+
this.customerDefinedIdentifier = undefined;
|
|
26
|
+
this.publicKey = undefined;
|
|
27
|
+
this.identityID = undefined;
|
|
28
|
+
this.credentialID = undefined;
|
|
29
|
+
this.traceId = undefined;
|
|
30
|
+
this.xhrHeaders = { 'Content-Type' : 'application/json' };
|
|
31
|
+
this.addlRelyingPartyBodyProps = useOrigin ? {use_origin: "true"} : {};
|
|
32
|
+
eventCoordinator.update('RelyingPartyBase', 'READY');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get userIdentifier() { return this.customerDefinedIdentifier; }
|
|
36
|
+
set userIdentifier(v) { return this.customerDefinedIdentifier = v; }
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @name makePostRequest
|
|
40
|
+
* @description Performs an HTTP POST request
|
|
41
|
+
* @param {string} url The URL to send the request to.
|
|
42
|
+
* @param {Object} body The request body data.
|
|
43
|
+
* @param {Object} headers Additional headers for the request.
|
|
44
|
+
* @param {string} method The HTTP method to use (default is 'POST').
|
|
45
|
+
* @returns {Promise<Object>} Resolves with the response data.
|
|
46
|
+
*/
|
|
47
|
+
makePostRequest (url, body={}, headers={}, method='POST') {
|
|
48
|
+
url = `${this.host.endsWith('/') ? this.host.slice(0,-1) : this.host}/${url.startsWith('/') ? url.slice(1) : url}`;
|
|
49
|
+
body = ((typeof body === 'object') ? body : {});
|
|
50
|
+
if(this.appEnvironment) body = Object.assign(body, {environment: this.appEnvironment});
|
|
51
|
+
body = Object.assign({ customer_defined_identifier : this.customerDefinedIdentifier, application_id : this.applicationId }, body, this.addlRelyingPartyBodyProps, tracePrimer(true));
|
|
52
|
+
let fetchObj = {
|
|
53
|
+
method,
|
|
54
|
+
headers : Object.assign(this.xhrHeaders, ((!headers?.Authorization) ? {'Authorization': `Bearer ${this.apiKey}`} : {}), headers),
|
|
55
|
+
body : JSON.stringify(body)
|
|
56
|
+
};
|
|
57
|
+
return fetch(url, fetchObj)
|
|
58
|
+
.then(response => response.ok
|
|
59
|
+
? response.json()
|
|
60
|
+
: response.json().then(errData => { throw new Error(`[RelyingParty] :: makePostRequest :: Request failed: ${response.statusText}, ${JSON.stringify(errData)}`) })
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
.then(response => {
|
|
64
|
+
GLOBAL.dispatchEvent(new CustomEvent("AuthResponse", {bubbles: true, cancelable: false, detail: response }));
|
|
65
|
+
return response;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @name clearEnrollmentCredentials
|
|
71
|
+
* @description Clears the enrollment credentials.
|
|
72
|
+
* @returns {Promise<ZSMAPI>}
|
|
73
|
+
*/
|
|
74
|
+
clearEnrollmentCredentials () { return (this.publicKey = this.credentialID = null) };
|
|
75
|
+
/**
|
|
76
|
+
* @name clearLoginCredentials
|
|
77
|
+
* @description Clears the login credentials.
|
|
78
|
+
* @returns {Promise<Void>}
|
|
79
|
+
* @todo Verify nothing is calling this and remove it outright.
|
|
80
|
+
*/
|
|
81
|
+
clearLoginCredentials () {};
|
|
82
|
+
/**
|
|
83
|
+
* @name resetAll
|
|
84
|
+
* @description Resets all credentials.
|
|
85
|
+
* @returns {Promise<Void>}
|
|
86
|
+
*/
|
|
87
|
+
resetAll () { return (this.clearLoginCredentials(), this.clearEnrollmentCredentials()); }
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @name registrationStart
|
|
91
|
+
* @description Sends a login request to the server.
|
|
92
|
+
* @returns {Promise<Object>} Resolves with publicKey object used in the subsequent Crypto Server API call.
|
|
93
|
+
*/
|
|
94
|
+
registrationStart () {
|
|
95
|
+
return this.makePostRequest("api/webauthn/registration/start")
|
|
96
|
+
.then(data => {
|
|
97
|
+
if(data.identity_id){
|
|
98
|
+
GLOBAL.mpcConfig.consumer_id = data.identity_id;
|
|
99
|
+
uid2iid(this.userIdentifier, data.identity_id);
|
|
100
|
+
}
|
|
101
|
+
return (this.publicKey = data.ccr.publicKey)
|
|
102
|
+
})
|
|
103
|
+
.then(result => result)
|
|
104
|
+
.catch(error => Promise.reject((error instanceof Error) ? error : new Error(error)));
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* @name registrationFinish
|
|
108
|
+
* @description Completes WebAuthn registration by sending the credential to the server.
|
|
109
|
+
* @param {Object} credential The credential information to finalize registration.
|
|
110
|
+
* @returns {Promise<Object>} Resolves with the registered credential.
|
|
111
|
+
* @throws {Error} Throws an error if the request fails.
|
|
112
|
+
*/
|
|
113
|
+
registrationFinish (credential) {
|
|
114
|
+
return this.makePostRequest("api/webauthn/registration/finish", { credential })
|
|
115
|
+
.then(() => (this.credentialID = credential.rawId, credential))
|
|
116
|
+
.then(credential => credential)
|
|
117
|
+
.catch(error => (this.clearEnrollmentCredentials(), Promise.reject((error instanceof Error) ? error : new Error(error))))
|
|
118
|
+
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* @name authenticationStart
|
|
123
|
+
* @description Starts the authentication process by sending a request to the server.
|
|
124
|
+
* @param {String} credential_id The ID of the credential to use for authentication.
|
|
125
|
+
* @returns {Promise<Object>} Resolves with the authentication challenge.
|
|
126
|
+
* @throws {Error} Throws an error if the request fails.
|
|
127
|
+
*/
|
|
128
|
+
authenticationStart (credential_id=this.credentialID) {
|
|
129
|
+
return (credential_id != null)
|
|
130
|
+
? this.makePostRequest( "api/webauthn/authentication/start", { credential_id })
|
|
131
|
+
.then(result => result)
|
|
132
|
+
: (Promise.reject(new Error("[RelyingParty] :: authenticationStart :: No user ID or credential ID provided")))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @name authenticationFinish
|
|
137
|
+
* @description Completes the authentication process by sending the credential to the server.
|
|
138
|
+
* @param {Object} credential The credential information to finalize authentication.
|
|
139
|
+
* @returns {Promise<Object>} Resolves with the authentication result.
|
|
140
|
+
* @throws {Error} Throws an error if the request fails.
|
|
141
|
+
*/
|
|
142
|
+
authenticationFinish (credential) {
|
|
143
|
+
return this.makePostRequest("api/webauthn/authentication/finish", { credential })
|
|
144
|
+
.then(result => result)
|
|
145
|
+
.catch(error => Promise.reject((error instanceof Error) ? error : new Error(error)));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* @name createIdentityThenRegistrationStart
|
|
150
|
+
* @description UMFA-OPTIMIZED FLOW: Creates a server-side Identity and then starts the WebAuthn Registration challenge automatically.
|
|
151
|
+
* @returns {Promise<Object>} Resolves with an object containing the publicKey and identity_id.
|
|
152
|
+
* @throws {Error} Throws an error if the request fails.
|
|
153
|
+
*/
|
|
154
|
+
createIdentityThenRegistrationStart() {
|
|
155
|
+
return this.makePostRequest("api/webauthn/registration/create-identity-then-start")
|
|
156
|
+
.then(data => {
|
|
157
|
+
if(data.identity_id){
|
|
158
|
+
GLOBAL.mpcConfig.consumer_id = data.identity_id;
|
|
159
|
+
uid2iid(this.userIdentifier, data.identity_id);
|
|
160
|
+
}
|
|
161
|
+
return {publicKey:(this.publicKey = data.ccr.publicKey), identity_id:data.identity_id}
|
|
162
|
+
})
|
|
163
|
+
.then(result => result)
|
|
164
|
+
.catch(error => Promise.reject((error instanceof Error) ? error : new Error(error)));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* @name registrationFinishAuthenticationStart
|
|
169
|
+
* @description UMFA-OPTIMIZED FLOW: Completes WebAuthn registration by sending the credential to the server.
|
|
170
|
+
* @param {Object} credential The credential information to finalize registration.
|
|
171
|
+
* @returns {Promise<Object>} Resolves with the registered credential.
|
|
172
|
+
* @throws {Error} Throws an error if the request fails.
|
|
173
|
+
*/
|
|
174
|
+
registrationFinishAuthenticationStart (credential) {
|
|
175
|
+
return this.makePostRequest("api/webauthn/registration/finish-then-authentication-start", { credential })
|
|
176
|
+
.then(result => (this.credentialID = credential.rawId, result))
|
|
177
|
+
.then(credential => credential)
|
|
178
|
+
.catch(error => (this.clearEnrollmentCredentials(), Promise.reject((error instanceof Error) ? error : new Error(error))));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* @name checkServerSideIdentity
|
|
183
|
+
* @description Checks if an identity exists on the server and creates one if it doesn't.
|
|
184
|
+
* @param {boolean} createNewIdentity Whether to create a new identity if one doesn't exist.
|
|
185
|
+
* @returns {Promise<Object>} Resolves with the server response.
|
|
186
|
+
* @throws {Error} Throws an error if the request fails.
|
|
187
|
+
*/
|
|
188
|
+
checkServerSideIdentity (createNewIdentity) {
|
|
189
|
+
createNewIdentity = createNewIdentity ? { create_new_identity: "true" } : {};
|
|
190
|
+
return this.makePostRequest("api/umfa/check-identity", createNewIdentity)
|
|
191
|
+
.then(result => result)
|
|
192
|
+
.catch(error => Promise.reject(error));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
//## END RelyingPartyBase =====================================================================================================================================
|
|
196
|
+
|
|
197
|
+
export default RelyingPartyBase;
|
package/UMFAClient.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import eventCoordinator from './EventCoordinator.js';
|
|
2
|
+
import UMFAClientBase from './UMFAClientBase.js';
|
|
3
|
+
|
|
4
|
+
class UMFAClient extends UMFAClientBase {
|
|
5
|
+
/**
|
|
6
|
+
* Constructs a UMFAClient object by extending the UMFAClientBase class.
|
|
7
|
+
* @param {Object} config - The configuration for ZSM initialization (ref: ./zsm_app_config.json)
|
|
8
|
+
*/
|
|
9
|
+
constructor(config) {
|
|
10
|
+
super(config);
|
|
11
|
+
eventCoordinator.update('UMFAClient', 'READY');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export {UMFAClient};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import eventCoordinator from './EventCoordinator.js';
|
|
2
|
+
import {zsmPluginManager} from './PluginManager.js';
|
|
3
|
+
|
|
4
|
+
class UMFAClientBase {
|
|
5
|
+
/**
|
|
6
|
+
* Base class for UMFAClient, providing methods for user enrollment and authentication.
|
|
7
|
+
* @constructor
|
|
8
|
+
* @param {Object} config - The configuration for ZSM initialization (ref: ./zsm_app_config.json)
|
|
9
|
+
*/
|
|
10
|
+
constructor(config) {
|
|
11
|
+
if(eventCoordinator.done) eventCoordinator.reset();
|
|
12
|
+
eventCoordinator.update('UMFAClientBase');
|
|
13
|
+
this.config = config;
|
|
14
|
+
const WebAuthnClient = zsmPluginManager.classes('WEBAUTHNCLIENT');
|
|
15
|
+
this.zsmAPI = new WebAuthnClient(config);
|
|
16
|
+
this.checkEnrollment = this.checkEnrollment.bind(this);
|
|
17
|
+
this.enroll = this.enroll.bind(this);
|
|
18
|
+
this.authenticate = this.authenticate.bind(this);
|
|
19
|
+
this.unenroll = this.unenroll.bind(this);
|
|
20
|
+
this.resetDevice = this.resetDevice.bind(this);
|
|
21
|
+
eventCoordinator.update('UMFAClientBase', 'READY');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get userIdentifier() { return this.zsmAPI.userIdentifier; }
|
|
25
|
+
get credentialID() { return this.zsmAPI.credentialID; }
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @name checkEnrollment
|
|
29
|
+
* @description Checks if a ZSM credential is enrolled on the device for the specified user
|
|
30
|
+
* @param {string} userIdentifier The identifier for the user
|
|
31
|
+
* @returns {Promise<Object>|boolean} Resolves with the enrollment details or false if not enrolled, or an error if checking fails
|
|
32
|
+
* @throws {Error} If the userIdentifier is not provided or is empty
|
|
33
|
+
* @throws {Error} If an error occurs while checking enrollment
|
|
34
|
+
* @memberOf UMFAClientBase
|
|
35
|
+
*/
|
|
36
|
+
async checkEnrollment(userIdentifier) {
|
|
37
|
+
try {
|
|
38
|
+
if(userIdentifier == null
|
|
39
|
+
|| userIdentifier === '') throw new Error(`[UMFAClient] :: checkEnrollment :: A userIdentifier String is required! Received: ${userIdentifier}.`);
|
|
40
|
+
return await this.zsmAPI.webauthnRetrieve(userIdentifier);
|
|
41
|
+
} catch(e) {
|
|
42
|
+
return new Error(e.message || e || '[UMFAClient] :: checkEnrollment :: An error occurred while attempting to check enrollment.');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @name enroll
|
|
48
|
+
* @description Enrolls a user by creating a ZSM credential on the device
|
|
49
|
+
* @param {string} userIdentifier The identifier for the user
|
|
50
|
+
* @returns {Promise<Object>|boolean} Resolves with the created credential, false if user is already enrolled, or an error if enrollment fails
|
|
51
|
+
* @throws {Error} If the userIdentifier is not provided or is empty
|
|
52
|
+
* @throws {Error} If an error occurs during enrollment
|
|
53
|
+
* @throws {Error} If the object returned from the ZSM API is not valid (no credential)
|
|
54
|
+
* @memberOf UMFAClientBase
|
|
55
|
+
*/
|
|
56
|
+
async enroll(userIdentifier) {
|
|
57
|
+
try {
|
|
58
|
+
if(userIdentifier == null
|
|
59
|
+
|| userIdentifier === '') throw new Error(`[UMFAClient] :: enroll :: A userIdentifier String is required! Received: ${userIdentifier}.`);
|
|
60
|
+
|
|
61
|
+
const userIsEnrolled = await this.checkEnrollment(userIdentifier);
|
|
62
|
+
if(userIsEnrolled !== false) return false;
|
|
63
|
+
|
|
64
|
+
const createResult = await this.zsmAPI.webauthnCreateThenPartialGet(userIdentifier);
|
|
65
|
+
if(createResult instanceof Error) throw(`[UMFAClient] :: enroll :: Unable to associate ${userIdentifier}'s identity with device profile.\nDetails:\n${createResult}`);
|
|
66
|
+
if(!createResult?.credential) throw(`[UMFAClient] :: enroll :: Unable to acquire authentication token for ${userIdentifier}!`);
|
|
67
|
+
return createResult.credential;
|
|
68
|
+
}catch(e) {
|
|
69
|
+
return new Error(e.message || e || '[UMFAClient] :: enroll :: An error occurred during enrollment.');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @name authenticate
|
|
75
|
+
* @description Authenticates a user by retrieving their ZSM credential
|
|
76
|
+
* @param {string} userIdentifier The identifier for the user
|
|
77
|
+
* @returns {Promise<Object>} Resolves with the authentication result or an error if authentication fails
|
|
78
|
+
* @throws {Error} If the userIdentifier is not provided or is empty
|
|
79
|
+
* @throws {Error} If an error occurs during authentication
|
|
80
|
+
* @memberOf UMFAClientBase
|
|
81
|
+
*/
|
|
82
|
+
async authenticate(userIdentifier) {
|
|
83
|
+
try {
|
|
84
|
+
if(userIdentifier == null
|
|
85
|
+
|| userIdentifier === '') throw new Error(`[UMFAClient] :: authenticate :: A userIdentifier String is required! Received: ${userIdentifier}.`);
|
|
86
|
+
let userIsEnrolled = await this.checkEnrollment(userIdentifier);
|
|
87
|
+
if(userIsEnrolled === false) throw(`${userIdentifier} is not enrolled.`);
|
|
88
|
+
let authCredential = await this.zsmAPI.webauthnPartialGet(userIdentifier);
|
|
89
|
+
return authCredential.credential;
|
|
90
|
+
}catch(e) {
|
|
91
|
+
return new Error(e.message || e || '[UMFAClient] :: authenticate :: An error occurred during authentication.');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @name unenroll
|
|
97
|
+
* @description Unenrolls a user by removing their ZSM credential from the device
|
|
98
|
+
* @param {string} userIdentifier The identifier for the user
|
|
99
|
+
* @returns {Promise<boolean>} Resolves with true if unenrollment was successful, false if the user was not enrolled, or an error if unenrollment fails
|
|
100
|
+
* @throws {Error} If the userIdentifier is not provided or is empty
|
|
101
|
+
* @throws {Error} If an error occurs during unenrollment
|
|
102
|
+
* @memberOf UMFAClientBase
|
|
103
|
+
*/
|
|
104
|
+
async unenroll(userIdentifier) {
|
|
105
|
+
try {
|
|
106
|
+
let userIsEnrolled = await this.checkEnrollment(userIdentifier);
|
|
107
|
+
if(userIsEnrolled === false) return false;
|
|
108
|
+
let unenrollResult = await this.zsmAPI.unbindFromDevice(userIdentifier);
|
|
109
|
+
if(unenrollResult instanceof Error) throw(`Unable to unenroll ${userIdentifier}'s identity from this device.\nDetails:\n${unenrollResult}`);
|
|
110
|
+
return unenrollResult === true;
|
|
111
|
+
} catch(e) {
|
|
112
|
+
return new Error(e.message || e || '[UMFAClient] :: unenroll :: An error occurred during unenrollment.');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @name resetDevice
|
|
118
|
+
* @description Pass-through method to call WebAuthnClient's webauthnReset
|
|
119
|
+
* @returns {Promise<void>} No results are returned
|
|
120
|
+
* @memberOf UMFAClientBase, WebAuthnClient
|
|
121
|
+
*/
|
|
122
|
+
async resetDevice() { this.zsmAPI.webauthnReset(); }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export default UMFAClientBase;
|
package/Utils.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @name b64urlToBuf
|
|
3
|
+
* @description Converts a Base64 URL-encoded string to an ArrayBuffer.
|
|
4
|
+
* @param {string} b64url The Base64 URL-encoded string to convert.
|
|
5
|
+
* @returns {ArrayBuffer} The converted ArrayBuffer.
|
|
6
|
+
* @throws {TypeError} Throws a TypeError if the input is not a valid Base64 URL-encoded string.
|
|
7
|
+
* @example b64urlToBuf('c29tZV9iYXNlNjRfdGVzdA') // Converts to ArrayBuffer
|
|
8
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Base64/Using_Base64_encoding#base64url, https://developer.mozilla.org/en-US/docs/Web/API/atob, https://developer.mozilla.org/en-US/docs/Web/API/Uint8Array, https://developer.mozilla.org/en-US/docs/Web/API/ArrayBuffer
|
|
9
|
+
* @memberof Utils
|
|
10
|
+
*/
|
|
11
|
+
const b64urlToBuf = (b64url) => {
|
|
12
|
+
if (typeof b64url !== 'string' || b64url.length === 0)
|
|
13
|
+
throw new TypeError(`[Utils] :: b64urlToBuf :: This method expects a base64 encoded string as input. Received: "${b64url}" (of type ${typeof b64url} and length ${b64url.length}).`);
|
|
14
|
+
|
|
15
|
+
const padded = (b64url + '====').slice(0, -b64url.length % 4);
|
|
16
|
+
|
|
17
|
+
const binary = atob(padded.replace(/-/g, '+').replace(/_/g, '/'));
|
|
18
|
+
|
|
19
|
+
const buffer = Uint8Array.from(binary, c => c.charCodeAt(0)).buffer;
|
|
20
|
+
|
|
21
|
+
return buffer;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @name toObject
|
|
26
|
+
* @description Recursively converts potentially-nested data structures (Map, Array, Object) to a plain object.
|
|
27
|
+
* @ This is in use specifically because of wasm-bindgen's proclivity to OCCASIONALLY return Map objects instead of plain objects.
|
|
28
|
+
* @param {any} inp The input data structure to convert.
|
|
29
|
+
* @returns {Object} The converted plain object.
|
|
30
|
+
* @throws {TypeError} Throws a TypeError if the input is not a valid data structure.
|
|
31
|
+
* @example toObject(new Map([['key', 'value']]))
|
|
32
|
+
* @memberof Utils
|
|
33
|
+
*/
|
|
34
|
+
const toObject = (inp) => {
|
|
35
|
+
if (inp === null || typeof inp !== 'object') return inp; // Primitives & functions pass through
|
|
36
|
+
|
|
37
|
+
if (inp instanceof Map) return Object.fromEntries([...inp].map(([k, v]) => [k, toObject(v)])); // Recursively convert Map's nodes
|
|
38
|
+
|
|
39
|
+
if (Array.isArray(inp)) return inp.map(toObject); // Recursively convert arrays' nodes
|
|
40
|
+
|
|
41
|
+
return Object.fromEntries(Object.entries(inp).map(([k, v]) => [k, toObject(v)])); // Recursively convert plain objects' nodes
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export { b64urlToBuf, toObject };
|