/**
* VrilsoftApi integration: JWT login, premium check, server-side secret protection.
* @module ellyPremium
*/
const K = {
apiBase: 'ellyVrilApiBaseUrl',
jwt: 'ellyVrilsoftJwt',
/** Last successful login email (for Premium UI greeting only). */
lastLoginEmail: 'ellyVrilsoftLastLoginEmail',
encApiKey: 'ellyProtectedApiKey',
encSecret: 'ellyProtectedApiSecret'
};
const DEFAULT_API_BASE = 'https://vrilsoftapi.com';
/** Relative to API origin: plan picker, then cart checkout. */
export const ELLY_PREMIUM_PLANS_PATH = 'Client/VrilPremium';
/**
* @returns {Promise<string>}
*/
export async function getApiBase() {
const x = await chrome.storage.local.get([K.apiBase]);
const b = x[K.apiBase];
const raw = typeof b === 'string' && b.trim() !== '' ? b : DEFAULT_API_BASE;
return raw.replace(/\/$/, '');
}
/**
* @param {string} base
* @returns {Promise<void>}
*/
export async function setApiBase(base) {
const t = (base || '').trim().replace(/\/$/, '');
await chrome.storage.local.set({ [K.apiBase]: t || DEFAULT_API_BASE });
}
/**
* Vril One premium plans (user picks a tier, then proceeds to cart).
* @returns {Promise<string>}
*/
export async function getPremiumPlansPageUrl() {
const base = await getApiBase();
return `${base}/${ELLY_PREMIUM_PLANS_PATH}`;
}
/**
* Public site base for /Legal/Terms etc. (matches VrilWebHosting Register links).
* @returns {Promise<string>}
*/
export async function getLegalPagesBaseUrl() {
const api = await getApiBase();
try {
const u = new URL(api);
if (u.hostname === 'vrilsoftapi.com' || u.hostname.endsWith('.vrilsoftapi.com')) {
return 'https://vrilsoft.com';
}
return `${u.protocol}//${u.hostname}`;
} catch {
return 'https://vrilsoft.com';
}
}
/**
* Canonical Elly proprietary software license page.
* @returns {Promise<string>}
*/
export async function getEllyLicenseUrl() {
const base = await getLegalPagesBaseUrl();
return `${base}/Legal/Elly`;
}
/**
* @returns {Promise<string|null>}
*/
export async function getJwt() {
const x = await chrome.storage.local.get([K.jwt]);
const j = x[K.jwt];
return typeof j === 'string' && j.length > 0 ? j : null;
}
/**
* @param {string|null} token
* @returns {Promise<void>}
*/
export async function setJwt(token) {
if (!token) {
await chrome.storage.local.remove([K.jwt, K.lastLoginEmail]);
return;
}
await chrome.storage.local.set({ [K.jwt]: token });
}
/**
* Email from the last successful login (Premium greeting). Cleared when JWT is cleared.
* @returns {Promise<string|null>}
*/
export async function getLastLoginEmail() {
const x = await chrome.storage.local.get([K.lastLoginEmail]);
const e = x[K.lastLoginEmail];
return typeof e === 'string' && e.trim() !== '' ? e.trim() : null;
}
/**
* Whether server-encrypted API key / secret blobs exist in extension storage.
* @returns {Promise<{ hasApiKey: boolean, hasSecret: boolean }>}
*/
export async function getProtectedCredentialPresence() {
const x = await chrome.storage.local.get([K.encApiKey, K.encSecret]);
const hasApiKey = typeof x[K.encApiKey] === 'string' && x[K.encApiKey].trim().length > 0;
const hasSecret = typeof x[K.encSecret] === 'string' && x[K.encSecret].trim().length > 0;
return { hasApiKey, hasSecret };
}
/**
* @param {string} email
* @param {string} password
* @returns {Promise<{ ok: boolean, error?: string }>}
*/
/**
* @param {{
* realName: string,
* userName: string,
* email: string,
* mobile: string,
* password: string,
* confirmPassword: string,
* acceptTos: boolean,
* acceptRefund: boolean,
* acceptPrivacy: boolean
* }} body
* @returns {Promise<{ ok: boolean, message?: string, error?: string }>}
*/
export async function registerVrilsoftAccount(body) {
const base = await getApiBase();
try {
const res = await fetch(`${base}/Api/Auth/Register`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
credentials: 'omit',
body: JSON.stringify(body)
});
const data = await res.json().catch(() => ({}));
if (!res.ok) {
return {
ok: false,
error: data.message || `Registration failed (${res.status})`
};
}
return { ok: true, message: data.message || 'Account created.' };
} catch (e) {
return { ok: false, error: e instanceof Error ? e.message : 'Network error' };
}
}
/**
* @param {string} email
* @param {string} password
* @returns {Promise<{
* ok: boolean,
* error?: string,
* requiresMfa?: boolean,
* mfaTicket?: string,
* requiresMfaEnrollment?: boolean,
* message?: string
* }>}
*/
export async function loginVrilsoft(email, password) {
const base = await getApiBase();
try {
const res = await fetch(`${base}/Api/Auth/Login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
credentials: 'omit',
body: JSON.stringify({ emailOrUserName: email, password })
});
const data = await res.json().catch(() => ({}));
const requiresMfa = !!(data.requiresMfa ?? data.RequiresMfa);
const mfaTicket = data.mfaTicket ?? data.MfaTicket ?? null;
const requiresMfaEnrollment = !!(data.requiresMfaEnrollment ?? data.RequiresMfaEnrollment);
const token = data.data?.token ?? data.Data?.token ?? data.data?.Token ?? null;
const success = !!(data.success ?? data.Success);
if (requiresMfaEnrollment) {
return {
ok: false,
requiresMfaEnrollment: true,
message: data.message || data.Message || 'Authenticator MFA enrollment required.',
error: data.message || data.Message || 'MFA enrollment required on your Vrilsoft / Vril One account before signing in here.'
};
}
if (success && requiresMfa && mfaTicket) {
return {
ok: false,
requiresMfa: true,
mfaTicket: String(mfaTicket),
message: data.message || data.Message || 'MFA required.'
};
}
if (!res.ok || !success || !token) {
return { ok: false, error: data.message || data.Message || `Login failed (${res.status})` };
}
await setJwt(token);
await chrome.storage.local.set({ [K.lastLoginEmail]: email.trim() });
return { ok: true };
} catch (e) {
return { ok: false, error: e instanceof Error ? e.message : 'Network error' };
}
}
/**
* Completes login after authenticator MFA.
* @param {string} mfaTicket
* @param {string} code
* @param {string} [emailForGreeting]
* @returns {Promise<{ ok: boolean, error?: string }>}
*/
export async function verifyVrilsoftMfa(mfaTicket, code, emailForGreeting) {
const base = await getApiBase();
try {
const res = await fetch(`${base}/Api/Auth/MfaVerify`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
credentials: 'omit',
body: JSON.stringify({ mfaTicket, code })
});
const data = await res.json().catch(() => ({}));
const success = !!(data.success ?? data.Success);
const token = data.data?.token ?? data.Data?.token ?? data.data?.Token ?? null;
if (!res.ok || !success || !token) {
return { ok: false, error: data.message || data.Message || `MFA verify failed (${res.status})` };
}
await setJwt(token);
if (emailForGreeting && String(emailForGreeting).trim()) {
await chrome.storage.local.set({ [K.lastLoginEmail]: String(emailForGreeting).trim() });
}
return { ok: true };
} catch (e) {
return { ok: false, error: e instanceof Error ? e.message : 'Network error' };
}
}
/**
* @returns {Promise<{ isPremium: boolean, raw?: unknown, error?: string }>}
*/
export async function fetchAmIPremium() {
const jwt = await getJwt();
if (!jwt) {
return { isPremium: false, error: 'not_logged_in' };
}
const base = await getApiBase();
try {
const res = await fetch(`${base}/ellyext/am-i-premium`, {
method: 'POST',
headers: {
Authorization: `Bearer ${jwt}`,
Accept: 'application/json'
}
});
const raw = await res.json().catch(() => ({}));
if (!res.ok) {
if (res.status === 401) {
await setJwt(null);
}
return { isPremium: false, error: (raw && raw.error) || `HTTP ${res.status}`, raw };
}
return { isPremium: !!raw.isPremium, raw };
} catch (e) {
return { isPremium: false, error: e instanceof Error ? e.message : 'network' };
}
}
/**
* @param {string} plaintext
* @returns {Promise<{ ok: boolean, protectedPayload?: string, error?: string }>}
*/
export async function protectPlaintext(plaintext) {
const jwt = await getJwt();
if (!jwt) {
return { ok: false, error: 'not_logged_in' };
}
const base = await getApiBase();
try {
const res = await fetch(`${base}/ellyext/protect-secret`, {
method: 'POST',
headers: {
Authorization: `Bearer ${jwt}`,
'Content-Type': 'application/json',
Accept: 'application/json'
},
body: JSON.stringify({ plaintext })
});
const raw = await res.json().catch(() => ({}));
if (!res.ok) {
return { ok: false, error: raw.error || `HTTP ${res.status}` };
}
if (!raw.protectedPayload) {
return { ok: false, error: 'invalid_response' };
}
return { ok: true, protectedPayload: raw.protectedPayload };
} catch (e) {
return { ok: false, error: e instanceof Error ? e.message : 'network' };
}
}
export { K as EllyStorageKeys };