@descope/sdk-mixins
Version:
Descope JavaScript SDK mixins
125 lines (122 loc) • 3.9 kB
JavaScript
;
const getExistingScript = (scriptId) => {
return document.querySelector(`script#${scriptId}`);
};
const isScriptLoaded = (script) => {
return script.getAttribute('status') === 'loaded';
};
const isScriptError = (script) => {
return script.getAttribute('status') === 'error';
};
const hashUrl = (url) => {
let hash = 0;
const urlStr = url.toString();
for (let i = 0; i < urlStr.length; i++) {
const char = urlStr.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash = hash & hash; // Convert to 32-bit integer
}
return `${Math.abs(hash).toString()}`;
};
const setupScript = (id, integrity) => {
const scriptEle = document.createElement('script');
scriptEle.id = id;
if (integrity) {
scriptEle.integrity = integrity;
scriptEle.crossOrigin = 'anonymous';
}
if (window.DESCOPE_NONCE) {
scriptEle.setAttribute('nonce', window.DESCOPE_NONCE);
}
return scriptEle;
};
const injectScript = (scriptId, url, integrity) => {
return new Promise((res, rej) => {
const scriptEle = setupScript(scriptId, integrity);
scriptEle.onerror = (error) => {
scriptEle.setAttribute('status', 'error');
rej(error);
};
scriptEle.onload = () => {
scriptEle.setAttribute('status', 'loaded');
res(scriptEle);
};
scriptEle.src = url.toString();
document.body.appendChild(scriptEle);
});
};
const handleExistingScript = (existingScript, expectedIntegrity) => {
if (expectedIntegrity) {
const actualIntegrity = existingScript.integrity;
if (actualIntegrity !== expectedIntegrity) {
return Promise.reject(new Error(`Integrity mismatch: expected ${expectedIntegrity}, found ${actualIntegrity}`));
}
}
if (isScriptLoaded(existingScript)) {
return Promise.resolve(existingScript);
}
if (isScriptError(existingScript)) {
return Promise.reject();
}
return new Promise((res, rej) => {
existingScript.addEventListener('load', () => {
res(existingScript);
});
existingScript.addEventListener('error', (error) => {
rej(error);
});
});
};
const injectScriptWithFallbacks = async (scriptsData, onError) => {
for (const scriptData of scriptsData) {
const { id, url, integrity } = scriptData;
const existingScript = getExistingScript(id);
if (existingScript) {
try {
await handleExistingScript(existingScript, integrity);
return scriptData;
}
catch (e) {
onError(scriptData, true);
}
}
else {
try {
await injectScript(id, url, integrity);
return scriptData;
}
catch (e) {
onError(scriptData, false);
}
}
}
throw new Error('All scripts failed to load');
};
const generateLibUrls = (baseUrls, libName, version, path = '', integrity) => baseUrls.reduce((prev, curr) => {
const baseUrl = curr;
if (!baseUrl) {
return prev;
}
let url;
try {
url = new URL(baseUrl);
}
catch (e) {
throw new Error(`Invalid URL: ${baseUrl}`);
}
const isUrlIncludesPath = url.pathname !== '/';
if (!isUrlIncludesPath) {
url.pathname = `/npm/${libName}@${version}/${path}`;
}
const scriptData = {
url: url,
id: `npmlib-${libName.replaceAll('@', '').replaceAll('/', '_')}-${hashUrl(url)}`,
};
if (integrity) {
scriptData.integrity = integrity;
}
return [...prev, scriptData];
}, []);
exports.generateLibUrls = generateLibUrls;
exports.injectScriptWithFallbacks = injectScriptWithFallbacks;
//# sourceMappingURL=helpers.js.map