fix and update

This commit is contained in:
lendry
2026-06-29 12:17:25 +03:00
parent 923a028cdd
commit 75ccbe5fc4
39 changed files with 1619 additions and 63 deletions

View File

@@ -0,0 +1,274 @@
(function lendrySsoWidget(global) {
'use strict';
var MESSAGE_TYPE = 'lendry-sso-onetap';
var DEFAULT_SCOPE = 'openid profile email';
var widgetRootId = 'lendry-sso-onetap-root';
function trimSlash(value) {
return String(value || '').replace(/\/+$/, '');
}
function resolveScriptElement() {
var current = global.document.currentScript;
if (current) return current;
var scripts = global.document.getElementsByTagName('script');
for (var i = scripts.length - 1; i >= 0; i -= 1) {
var src = scripts[i].getAttribute('src') || '';
if (src.indexOf('sso-widget.js') !== -1) {
return scripts[i];
}
}
return null;
}
function resolveIdpApiBase(script) {
var override = script && script.getAttribute('data-idp-url');
if (override) return trimSlash(override);
try {
var origin = new URL(script.src).origin;
return trimSlash(origin + '/idp-api');
} catch (_error) {
return '';
}
}
function resolveFrontendBase(script, idpApiBase) {
var override = script && script.getAttribute('data-idp-frontend-url');
if (override) return trimSlash(override);
try {
return new URL(script.src).origin;
} catch (_error) {
if (idpApiBase.indexOf('/idp-api') !== -1) {
return idpApiBase.replace(/\/idp-api$/, '');
}
return idpApiBase;
}
}
function resolveProviderName(script) {
return (script && script.getAttribute('data-provider-name')) || 'MVK ID';
}
function dispatchSuccess(payload, script) {
var callbackName = script && script.getAttribute('data-on-success');
if (callbackName && typeof global[callbackName] === 'function') {
global[callbackName](payload);
}
try {
global.dispatchEvent(new CustomEvent('lendry-sso-onetap-success', { detail: payload }));
} catch (_error) {
// IE fallback not required
}
}
function supportsFedCM() {
return 'IdentityCredential' in global && global.navigator && typeof global.navigator.credentials !== 'undefined';
}
function loginWithFedCM(idpApiBase, clientId) {
if (!supportsFedCM()) {
return Promise.resolve(null);
}
return global.navigator.credentials
.get({
identity: {
providers: [
{
configURL: idpApiBase + '/fedcm/config.json',
clientId: clientId
}
]
},
mediation: 'optional'
})
.then(function (credential) {
if (!credential || typeof credential !== 'object') return null;
var token = credential.token;
if (!token) return null;
return {
token: token,
method: 'fedcm'
};
})
.catch(function (error) {
if (global.console && typeof global.console.warn === 'function') {
global.console.warn('[MVK ID] FedCM недоступен:', error);
}
return null;
});
}
function injectWidgetStyles() {
if (global.document.getElementById('lendry-sso-onetap-style')) return;
var style = global.document.createElement('style');
style.id = 'lendry-sso-onetap-style';
style.textContent =
'#' +
widgetRootId +
'{position:fixed;top:16px;right:16px;z-index:2147483000;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif}' +
'.lendry-sso-onetap-btn{display:flex;align-items:center;gap:10px;padding:10px 16px;border-radius:999px;border:1px solid #e4e8ef;background:#fff;color:#1f2430;font-size:14px;font-weight:600;line-height:1;cursor:pointer;box-shadow:0 8px 24px rgba(31,36,48,.12);transition:transform .18s ease,box-shadow .18s ease}' +
'.lendry-sso-onetap-btn:hover{transform:translateY(-1px);box-shadow:0 12px 28px rgba(31,36,48,.16)}' +
'.lendry-sso-onetap-btn:active{transform:translateY(0)}' +
'.lendry-sso-onetap-badge{display:inline-flex;align-items:center;justify-content:center;min-width:28px;height:28px;padding:0 8px;border-radius:999px;background:#111;color:#fff;font-size:12px;font-weight:700}';
global.document.head.appendChild(style);
}
function createWidget(providerName, onClick) {
injectWidgetStyles();
if (global.document.getElementById(widgetRootId)) return;
var root = global.document.createElement('div');
root.id = widgetRootId;
var button = global.document.createElement('button');
button.type = 'button';
button.className = 'lendry-sso-onetap-btn';
button.setAttribute('aria-label', 'Войти через ' + providerName);
var badge = global.document.createElement('span');
badge.className = 'lendry-sso-onetap-badge';
badge.textContent = 'ID';
var label = global.document.createElement('span');
label.textContent = 'Войти через ' + providerName;
button.appendChild(badge);
button.appendChild(label);
button.addEventListener('click', onClick);
root.appendChild(button);
global.document.body.appendChild(root);
}
function buildPopupUrl(frontendBase, clientId, origin, redirectUri, scope) {
var params = new URLSearchParams();
params.set('client_id', clientId);
params.set('redirect_uri', redirectUri);
params.set('response_type', 'code');
params.set('scope', scope || DEFAULT_SCOPE);
params.set('display', 'popup');
params.set('popup_origin', origin);
params.set('state', 'onetap_' + Math.random().toString(36).slice(2));
return frontendBase + '/auth/oauth/authorize?' + params.toString();
}
function openPopup(url, expectedOrigin, onToken) {
var width = 480;
var height = 640;
var left = Math.max(0, Math.round((global.screen.width - width) / 2));
var top = Math.max(0, Math.round((global.screen.height - height) / 2));
var features =
'popup=yes,width=' +
width +
',height=' +
height +
',left=' +
left +
',top=' +
top +
',noopener,noreferrer';
var popup = global.open(url, 'lendry_sso_onetap', features);
if (!popup) {
if (global.console && typeof global.console.error === 'function') {
global.console.error('[MVK ID] Не удалось открыть popup. Разрешите всплывающие окна.');
}
return function cleanup() {};
}
function handleMessage(event) {
if (!event || !event.data || event.data.type !== MESSAGE_TYPE) return;
if (expectedOrigin && event.origin !== expectedOrigin && event.origin !== trimSlash(expectedOrigin)) return;
cleanup();
onToken(event.data);
}
function cleanup() {
global.removeEventListener('message', handleMessage);
if (popup && !popup.closed) {
try {
popup.close();
} catch (_error) {
// ignore
}
}
}
global.addEventListener('message', handleMessage);
return cleanup;
}
function initOneTap(options) {
var script = resolveScriptElement();
var clientId = (options && options.clientId) || (script && script.getAttribute('data-client-id'));
if (!clientId) {
if (global.console && typeof global.console.error === 'function') {
global.console.error('[MVK ID] Укажите data-client-id в теге script или clientId в LendryIdOneTap.init().');
}
return;
}
var idpApiBase = trimSlash((options && options.idpUrl) || resolveIdpApiBase(script));
var frontendBase = trimSlash((options && options.frontendUrl) || resolveFrontendBase(script, idpApiBase));
var providerName = (options && options.providerName) || resolveProviderName(script);
var scope = (options && options.scope) || (script && script.getAttribute('data-scope')) || DEFAULT_SCOPE;
var redirectUri =
(options && options.redirectUri) ||
(script && script.getAttribute('data-redirect-uri')) ||
global.location.origin + '/auth/callback';
loginWithFedCM(idpApiBase, clientId).then(function (result) {
if (result && result.token) {
dispatchSuccess(
{
token: result.token,
method: 'fedcm'
},
script
);
return;
}
if (supportsFedCM()) {
return;
}
createWidget(providerName, function () {
var popupUrl = buildPopupUrl(frontendBase, clientId, global.location.origin, redirectUri, scope);
openPopup(popupUrl, frontendBase, function (payload) {
dispatchSuccess(
{
token: payload.token,
accessToken: payload.accessToken,
idToken: payload.idToken,
refreshToken: payload.refreshToken,
code: payload.code,
method: 'popup'
},
script
);
});
});
});
}
global.LendryIdOneTap = {
init: initOneTap,
loginWithFedCM: loginWithFedCM,
MESSAGE_TYPE: MESSAGE_TYPE
};
var scriptEl = resolveScriptElement();
if (scriptEl && scriptEl.getAttribute('data-auto-init') !== 'false') {
if (global.document.readyState === 'loading') {
global.document.addEventListener('DOMContentLoaded', function () {
initOneTap();
});
} else {
initOneTap();
}
}
})(window);