698 lines
22 KiB
JavaScript
698 lines
22 KiB
JavaScript
(function () {
|
|
function init() {
|
|
const root = document.getElementById('qortal-personal-root');
|
|
if (!root) {
|
|
return false;
|
|
}
|
|
|
|
if (root.dataset.initialized === 'true') {
|
|
return true;
|
|
}
|
|
root.dataset.initialized = 'true';
|
|
|
|
const userMappingsUrl = root.dataset.userMappingsUrl;
|
|
const userCreateWalletUrl = root.dataset.userCreateWalletUrl;
|
|
const userBackupWalletUrl = root.dataset.userBackupWalletUrl;
|
|
const userImportSeedLinkUrl = root.dataset.userImportSeedLinkUrl;
|
|
const userImportBackupLinkUrl = root.dataset.userImportBackupLinkUrl;
|
|
const userUnlinkMappingUrl = root.dataset.userUnlinkMappingUrl;
|
|
const userQappsPrefsUrl = root.dataset.userQappsPrefsUrl;
|
|
const userQappsRulesUrl = root.dataset.userQappsRulesUrl;
|
|
const userQappsRuleRevokeUrl = root.dataset.userQappsRuleRevokeUrl;
|
|
const createWalletButton = document.getElementById('qortal-create-wallet-button');
|
|
const createPasswordEl = document.getElementById('qortal-create-password');
|
|
const createKdfEl = document.getElementById('qortal-create-kdf');
|
|
const createResultEl = document.getElementById('qortal-create-wallet-result');
|
|
const backupWalletIdEl = document.getElementById('qortal-backup-wallet-id');
|
|
const backupWalletPasswordEl = document.getElementById('qortal-backup-wallet-password');
|
|
const backupDownloadEl = document.getElementById('qortal-backup-download');
|
|
const backupSaveEl = document.getElementById('qortal-backup-save');
|
|
const backupWalletButton = document.getElementById('qortal-backup-wallet-button');
|
|
const backupResultEl = document.getElementById('qortal-backup-result');
|
|
const importButton = document.getElementById('qortal-import-link');
|
|
const importBackupButton = document.getElementById('qortal-import-backup-link');
|
|
const refreshMappingsButton = document.getElementById('qortal-refresh-user-mappings');
|
|
const seedPhraseEl = document.getElementById('qortal-seed-phrase');
|
|
const walletPasswordEl = document.getElementById('qortal-wallet-password');
|
|
const backupFileEl = document.getElementById('qortal-backup-file');
|
|
const backupPasswordEl = document.getElementById('qortal-backup-password');
|
|
const feedbackEl = document.getElementById('qortal-personal-feedback');
|
|
const successEl = document.getElementById('qortal-personal-success');
|
|
const resultEl = document.getElementById('qortal-personal-result');
|
|
const mappingsBody = document.getElementById('qortal-user-mappings-body');
|
|
const defaultApprovalModeEl = document.getElementById('qortal-default-approval-mode');
|
|
const defaultApprovalTempMinutesEl = document.getElementById('qortal-default-approval-temp-minutes');
|
|
const defaultApprovalUnlock10MinEl = document.getElementById('qortal-default-approval-unlock-10-min');
|
|
const defaultUnlockSession20MinEl = document.getElementById('qortal-default-unlock-session-20-min');
|
|
const saveApprovalDefaultsButton = document.getElementById('qortal-save-approval-defaults');
|
|
const resetApprovalDefaultsButton = document.getElementById('qortal-reset-approval-defaults');
|
|
const approvalDefaultsResultEl = document.getElementById('qortal-approval-defaults-result');
|
|
const refreshApprovalRulesButton = document.getElementById('qortal-refresh-approval-rules');
|
|
const approvalRulesBody = document.getElementById('qortal-user-approval-rules-body');
|
|
const approvalRulesResultEl = document.getElementById('qortal-approval-rules-result');
|
|
|
|
if (
|
|
!userMappingsUrl ||
|
|
!userCreateWalletUrl ||
|
|
!userBackupWalletUrl ||
|
|
!userImportSeedLinkUrl ||
|
|
!userImportBackupLinkUrl ||
|
|
!userUnlinkMappingUrl ||
|
|
!userQappsPrefsUrl ||
|
|
!userQappsRulesUrl ||
|
|
!userQappsRuleRevokeUrl ||
|
|
!createWalletButton ||
|
|
!createPasswordEl ||
|
|
!createKdfEl ||
|
|
!createResultEl ||
|
|
!backupWalletIdEl ||
|
|
!backupWalletPasswordEl ||
|
|
!backupDownloadEl ||
|
|
!backupSaveEl ||
|
|
!backupWalletButton ||
|
|
!backupResultEl ||
|
|
!importButton ||
|
|
!importBackupButton ||
|
|
!refreshMappingsButton ||
|
|
!seedPhraseEl ||
|
|
!walletPasswordEl ||
|
|
!backupFileEl ||
|
|
!backupPasswordEl ||
|
|
!feedbackEl ||
|
|
!successEl ||
|
|
!resultEl ||
|
|
!mappingsBody ||
|
|
!defaultApprovalModeEl ||
|
|
!defaultApprovalTempMinutesEl ||
|
|
!defaultApprovalUnlock10MinEl ||
|
|
!defaultUnlockSession20MinEl ||
|
|
!saveApprovalDefaultsButton ||
|
|
!resetApprovalDefaultsButton ||
|
|
!approvalDefaultsResultEl ||
|
|
!refreshApprovalRulesButton ||
|
|
!approvalRulesBody ||
|
|
!approvalRulesResultEl
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
const headers = {
|
|
requesttoken: OC.requestToken,
|
|
};
|
|
|
|
function setFeedback(message, isError) {
|
|
feedbackEl.textContent = message;
|
|
feedbackEl.classList.toggle('error', Boolean(isError));
|
|
feedbackEl.classList.toggle('success', !isError && message.length > 0);
|
|
}
|
|
|
|
function setSuccess(message) {
|
|
successEl.textContent = message || '';
|
|
}
|
|
|
|
function setApprovalDefaultsResult(message, isError) {
|
|
approvalDefaultsResultEl.textContent = message || '';
|
|
approvalDefaultsResultEl.classList.toggle('error', Boolean(isError));
|
|
}
|
|
|
|
function setApprovalRulesResult(message, isError) {
|
|
approvalRulesResultEl.textContent = message || '';
|
|
approvalRulesResultEl.classList.toggle('error', Boolean(isError));
|
|
}
|
|
|
|
function normalizeApprovalMode(mode) {
|
|
const normalized = String(mode || '').trim();
|
|
if (normalized === 'session') {
|
|
return 'type_minutes';
|
|
}
|
|
if (normalized === 'scope') {
|
|
return 'type_always';
|
|
}
|
|
if (normalized === 'app') {
|
|
return 'app_always';
|
|
}
|
|
if (normalized === 'type_minutes' || normalized === 'type_always' || normalized === 'app_always') {
|
|
return normalized;
|
|
}
|
|
return 'once';
|
|
}
|
|
|
|
function normalizeApprovalTempMinutes(value) {
|
|
const parsed = Number(String(value || '').trim());
|
|
if (!Number.isFinite(parsed)) {
|
|
return 10;
|
|
}
|
|
return Math.max(1, Math.min(Math.floor(parsed), 1440));
|
|
}
|
|
|
|
function applyApprovalDefaultsFromDataset() {
|
|
defaultApprovalModeEl.value = normalizeApprovalMode(root.dataset.userQappsApprovalMode || 'once');
|
|
defaultApprovalTempMinutesEl.value = String(normalizeApprovalTempMinutes(root.dataset.userQappsApprovalTempMinutes || '10'));
|
|
defaultApprovalTempMinutesEl.disabled = normalizeApprovalMode(defaultApprovalModeEl.value) !== 'type_minutes';
|
|
defaultApprovalUnlock10MinEl.checked = root.dataset.userQappsApprovalUnlock10Min === '1';
|
|
defaultUnlockSession20MinEl.checked = root.dataset.userQappsUnlockSession20Min === '1';
|
|
setApprovalDefaultsResult(
|
|
'Current defaults loaded. Save changes to apply for future Q-Apps auth popups.',
|
|
false
|
|
);
|
|
}
|
|
|
|
async function requestJson(url, options) {
|
|
const response = await fetch(url, options);
|
|
const payload = await response.json();
|
|
if (!response.ok) {
|
|
throw new Error(payload.error || 'Request failed');
|
|
}
|
|
return payload;
|
|
}
|
|
|
|
function assertBrokerOk(payload, fallbackError) {
|
|
if (payload && payload.ok === false) {
|
|
throw new Error(payload.error || fallbackError);
|
|
}
|
|
}
|
|
|
|
function extractBackupObject(payload) {
|
|
let candidate = payload;
|
|
for (let i = 0; i < 4; i++) {
|
|
if (candidate && typeof candidate === 'object' && candidate.data && typeof candidate.data === 'object') {
|
|
candidate = candidate.data;
|
|
continue;
|
|
}
|
|
if (candidate && typeof candidate === 'object' && candidate.backup !== undefined) {
|
|
candidate = candidate.backup;
|
|
if (typeof candidate === 'string') {
|
|
try {
|
|
candidate = JSON.parse(candidate);
|
|
} catch (_error) {
|
|
candidate = null;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
return candidate && typeof candidate === 'object' ? candidate : null;
|
|
}
|
|
|
|
function formatTimestamp(value) {
|
|
if (!value) {
|
|
return '';
|
|
}
|
|
try {
|
|
return new Date(value).toLocaleString();
|
|
} catch (error) {
|
|
return String(value);
|
|
}
|
|
}
|
|
|
|
function formatApprovalMode(value) {
|
|
const mode = String(value || '').trim().toLowerCase();
|
|
if (mode === 'persistent') {
|
|
return 'Always allow';
|
|
}
|
|
if (mode === 'session') {
|
|
return 'Allow for session';
|
|
}
|
|
return mode || '-';
|
|
}
|
|
|
|
function downloadJsonFile(filename, data) {
|
|
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
|
const url = URL.createObjectURL(blob);
|
|
const anchor = document.createElement('a');
|
|
anchor.href = url;
|
|
anchor.download = filename;
|
|
document.body.appendChild(anchor);
|
|
anchor.click();
|
|
anchor.remove();
|
|
URL.revokeObjectURL(url);
|
|
}
|
|
|
|
function renderMappings(payload) {
|
|
const mappings = payload && payload.ok && payload.data && Array.isArray(payload.data.mappings)
|
|
? payload.data.mappings
|
|
: [];
|
|
|
|
mappingsBody.innerHTML = '';
|
|
|
|
if (mappings.length === 0) {
|
|
mappingsBody.innerHTML = '<tr><td colspan="5">No linked Qortal accounts yet.</td></tr>';
|
|
return;
|
|
}
|
|
|
|
mappings.forEach(function (mapping) {
|
|
const row = document.createElement('tr');
|
|
|
|
const addressCell = document.createElement('td');
|
|
const addressCode = document.createElement('code');
|
|
addressCode.textContent = mapping.qortalAddress || '';
|
|
addressCell.appendChild(addressCode);
|
|
|
|
const walletCell = document.createElement('td');
|
|
const walletCode = document.createElement('code');
|
|
walletCode.textContent = mapping.walletId || '';
|
|
walletCell.appendChild(walletCode);
|
|
|
|
const statusCell = document.createElement('td');
|
|
statusCell.textContent = mapping.status || '';
|
|
|
|
const updatedCell = document.createElement('td');
|
|
updatedCell.textContent = formatTimestamp(mapping.updatedAt);
|
|
|
|
const actionCell = document.createElement('td');
|
|
const unlinkButton = document.createElement('button');
|
|
unlinkButton.className = 'button';
|
|
unlinkButton.textContent = 'Remove';
|
|
unlinkButton.addEventListener('click', async function () {
|
|
try {
|
|
await unlinkMapping(mapping.qortalAddress);
|
|
setFeedback('Linked account removed.', false);
|
|
} catch (error) {
|
|
setFeedback(error.message || 'Failed to remove linked account', true);
|
|
}
|
|
});
|
|
actionCell.appendChild(unlinkButton);
|
|
|
|
row.appendChild(addressCell);
|
|
row.appendChild(walletCell);
|
|
row.appendChild(statusCell);
|
|
row.appendChild(updatedCell);
|
|
row.appendChild(actionCell);
|
|
mappingsBody.appendChild(row);
|
|
});
|
|
}
|
|
|
|
function renderApprovalRules(payload) {
|
|
const data = payload && payload.data && typeof payload.data === 'object' ? payload.data : payload;
|
|
const rules = data && Array.isArray(data.rules) ? data.rules : [];
|
|
approvalRulesBody.innerHTML = '';
|
|
|
|
if (rules.length === 0) {
|
|
approvalRulesBody.innerHTML = '<tr><td colspan="6">No active approval rules.</td></tr>';
|
|
return;
|
|
}
|
|
|
|
rules.forEach(function (rule) {
|
|
const row = document.createElement('tr');
|
|
const modeCell = document.createElement('td');
|
|
modeCell.textContent = formatApprovalMode(rule.mode);
|
|
|
|
const appCell = document.createElement('td');
|
|
appCell.textContent = rule.app || '-';
|
|
|
|
const scopeCell = document.createElement('td');
|
|
const scopeCode = document.createElement('code');
|
|
scopeCode.textContent = rule.scope || '';
|
|
scopeCell.appendChild(scopeCode);
|
|
|
|
const requestTypeCell = document.createElement('td');
|
|
requestTypeCell.textContent = rule.requestType || '-';
|
|
|
|
const updatedCell = document.createElement('td');
|
|
updatedCell.textContent = formatTimestamp(rule.updatedAt);
|
|
|
|
const actionCell = document.createElement('td');
|
|
const revokeButton = document.createElement('button');
|
|
revokeButton.className = 'button';
|
|
revokeButton.textContent = 'Remove';
|
|
revokeButton.addEventListener('click', async function () {
|
|
try {
|
|
await revokeApprovalRule(rule);
|
|
setApprovalRulesResult('Rule removed.', false);
|
|
} catch (error) {
|
|
setApprovalRulesResult(error.message || 'Failed to remove rule', true);
|
|
}
|
|
});
|
|
actionCell.appendChild(revokeButton);
|
|
|
|
row.appendChild(modeCell);
|
|
row.appendChild(appCell);
|
|
row.appendChild(scopeCell);
|
|
row.appendChild(requestTypeCell);
|
|
row.appendChild(updatedCell);
|
|
row.appendChild(actionCell);
|
|
approvalRulesBody.appendChild(row);
|
|
});
|
|
}
|
|
|
|
async function refreshMappings() {
|
|
const payload = await requestJson(userMappingsUrl, {
|
|
method: 'GET',
|
|
headers,
|
|
});
|
|
assertBrokerOk(payload, 'Failed to fetch linked accounts');
|
|
renderMappings(payload);
|
|
}
|
|
|
|
async function refreshApprovalRules() {
|
|
const payload = await requestJson(userQappsRulesUrl, {
|
|
method: 'GET',
|
|
headers,
|
|
});
|
|
assertBrokerOk(payload, 'Failed to fetch approval rules');
|
|
renderApprovalRules(payload);
|
|
}
|
|
|
|
async function importAndLink() {
|
|
const seedPhrase = seedPhraseEl.value.trim();
|
|
const password = walletPasswordEl.value.trim();
|
|
|
|
if (!seedPhrase || !password) {
|
|
throw new Error('Seed phrase and wallet password are required');
|
|
}
|
|
|
|
setFeedback('Importing wallet and linking account...', false);
|
|
setSuccess('');
|
|
|
|
const body = new URLSearchParams({
|
|
seedPhrase,
|
|
password,
|
|
});
|
|
|
|
const payload = await requestJson(userImportSeedLinkUrl, {
|
|
method: 'POST',
|
|
headers,
|
|
body,
|
|
});
|
|
assertBrokerOk(payload, 'Import/link failed');
|
|
|
|
resultEl.textContent = JSON.stringify(payload, null, 2);
|
|
seedPhraseEl.value = '';
|
|
walletPasswordEl.value = '';
|
|
await refreshMappings();
|
|
setFeedback('Wallet imported and linked.', false);
|
|
setSuccess('Wallet linked. You can now authenticate to Nextcloud using your Qortal account.');
|
|
}
|
|
|
|
async function createWallet() {
|
|
if (createWalletButton.disabled) {
|
|
return;
|
|
}
|
|
const password = createPasswordEl.value.trim();
|
|
const kdfThreads = createKdfEl.value.trim();
|
|
|
|
if (!password) {
|
|
throw new Error('Wallet password is required');
|
|
}
|
|
|
|
createWalletButton.disabled = true;
|
|
setFeedback('Creating wallet...', false);
|
|
setSuccess('');
|
|
|
|
const body = new URLSearchParams({
|
|
password,
|
|
kdfThreads: kdfThreads || '',
|
|
});
|
|
|
|
try {
|
|
const payload = await requestJson(userCreateWalletUrl, {
|
|
method: 'POST',
|
|
headers,
|
|
body,
|
|
});
|
|
assertBrokerOk(payload, 'Wallet creation failed');
|
|
|
|
createResultEl.textContent = JSON.stringify(payload, null, 2);
|
|
const wallet = payload.wallet || payload.data || payload;
|
|
if (wallet && wallet.walletId) {
|
|
backupWalletIdEl.value = wallet.walletId;
|
|
}
|
|
if (!backupWalletPasswordEl.value) {
|
|
backupWalletPasswordEl.value = password;
|
|
}
|
|
await refreshMappings();
|
|
setFeedback('Wallet created and linked.', false);
|
|
setSuccess('Wallet created. You can now authenticate to Nextcloud using your Qortal account.');
|
|
} finally {
|
|
createWalletButton.disabled = false;
|
|
}
|
|
}
|
|
|
|
async function backupWallet() {
|
|
const walletId = backupWalletIdEl.value.trim();
|
|
const password = backupWalletPasswordEl.value.trim();
|
|
const download = backupDownloadEl.checked;
|
|
const saveToFiles = backupSaveEl.checked;
|
|
|
|
if (!walletId) {
|
|
throw new Error('Wallet ID is required');
|
|
}
|
|
if (!password) {
|
|
throw new Error('Backup password is required');
|
|
}
|
|
if (!download && !saveToFiles) {
|
|
throw new Error('Select at least one backup option');
|
|
}
|
|
|
|
setFeedback('Creating wallet backup...', false);
|
|
setSuccess('');
|
|
|
|
const body = new URLSearchParams({
|
|
walletId,
|
|
password,
|
|
saveToFiles: saveToFiles ? '1' : '',
|
|
});
|
|
|
|
const payload = await requestJson(userBackupWalletUrl, {
|
|
method: 'POST',
|
|
headers,
|
|
body,
|
|
});
|
|
assertBrokerOk(payload, 'Backup failed');
|
|
|
|
backupResultEl.textContent = JSON.stringify(payload, null, 2);
|
|
const backupData = extractBackupObject(payload);
|
|
if (download && backupData) {
|
|
const filename = `qortal-wallet-${walletId}.json`;
|
|
downloadJsonFile(filename, backupData);
|
|
}
|
|
if (saveToFiles && payload.savedPath) {
|
|
setSuccess(`Backup saved to Nextcloud Files: ${payload.savedPath}`);
|
|
}
|
|
setFeedback('Backup completed.', false);
|
|
}
|
|
|
|
async function importBackupAndLink() {
|
|
const password = backupPasswordEl.value.trim();
|
|
if (!password) {
|
|
throw new Error('Backup password is required');
|
|
}
|
|
|
|
if (!backupFileEl.files || backupFileEl.files.length === 0) {
|
|
throw new Error('Backup JSON file is required');
|
|
}
|
|
|
|
const file = backupFileEl.files[0];
|
|
const text = await file.text();
|
|
let parsedBackup;
|
|
try {
|
|
parsedBackup = JSON.parse(text);
|
|
} catch (error) {
|
|
throw new Error('Backup file is not valid JSON');
|
|
}
|
|
|
|
if (!parsedBackup || typeof parsedBackup !== 'object') {
|
|
throw new Error('Backup file must contain a JSON object');
|
|
}
|
|
const backupData = extractBackupObject(parsedBackup);
|
|
if (!backupData) {
|
|
throw new Error('Backup file does not contain a valid backup object');
|
|
}
|
|
|
|
setFeedback('Importing backup and linking account...', false);
|
|
setSuccess('');
|
|
|
|
const body = new URLSearchParams({
|
|
backupJson: JSON.stringify(backupData),
|
|
password,
|
|
});
|
|
|
|
const payload = await requestJson(userImportBackupLinkUrl, {
|
|
method: 'POST',
|
|
headers,
|
|
body,
|
|
});
|
|
assertBrokerOk(payload, 'Import/link failed');
|
|
|
|
resultEl.textContent = JSON.stringify(payload, null, 2);
|
|
backupFileEl.value = '';
|
|
backupPasswordEl.value = '';
|
|
await refreshMappings();
|
|
setFeedback('Backup imported and linked.', false);
|
|
setSuccess('Wallet linked. You can now authenticate to Nextcloud using your Qortal account.');
|
|
}
|
|
|
|
async function unlinkMapping(qortalAddress) {
|
|
if (!qortalAddress) {
|
|
throw new Error('qortalAddress is required');
|
|
}
|
|
|
|
const body = new URLSearchParams({
|
|
qortalAddress: qortalAddress,
|
|
});
|
|
|
|
const payload = await requestJson(userUnlinkMappingUrl, {
|
|
method: 'POST',
|
|
headers,
|
|
body,
|
|
});
|
|
assertBrokerOk(payload, 'Unlink failed');
|
|
resultEl.textContent = JSON.stringify(payload, null, 2);
|
|
await refreshMappings();
|
|
}
|
|
|
|
async function revokeApprovalRule(rule) {
|
|
const walletId = rule && typeof rule.walletId === 'string' ? rule.walletId.trim() : '';
|
|
const scope = rule && typeof rule.scope === 'string' ? rule.scope.trim() : '';
|
|
const app = rule && typeof rule.app === 'string' ? rule.app.trim() : '';
|
|
const requestType = rule && typeof rule.requestType === 'string' ? rule.requestType.trim() : '';
|
|
if (!walletId || !scope) {
|
|
throw new Error('Invalid rule payload');
|
|
}
|
|
|
|
const body = new URLSearchParams({
|
|
walletId,
|
|
scope,
|
|
app,
|
|
requestType,
|
|
});
|
|
|
|
const payload = await requestJson(userQappsRuleRevokeUrl, {
|
|
method: 'POST',
|
|
headers,
|
|
body,
|
|
});
|
|
assertBrokerOk(payload, 'Failed to revoke rule');
|
|
await refreshApprovalRules();
|
|
}
|
|
|
|
async function saveApprovalDefaults() {
|
|
const body = new URLSearchParams({
|
|
approvalMode: normalizeApprovalMode(defaultApprovalModeEl.value),
|
|
approvalTempMinutes: String(normalizeApprovalTempMinutes(defaultApprovalTempMinutesEl.value)),
|
|
approvalUnlock10Min: defaultApprovalUnlock10MinEl.checked ? '1' : '',
|
|
unlockSession20Min: defaultUnlockSession20MinEl.checked ? '1' : '',
|
|
});
|
|
|
|
const payload = await requestJson(userQappsPrefsUrl, {
|
|
method: 'POST',
|
|
headers,
|
|
body,
|
|
});
|
|
assertBrokerOk(payload, 'Failed to save approval defaults');
|
|
resultEl.textContent = JSON.stringify(payload, null, 2);
|
|
|
|
const saved = payload && payload.data ? payload.data : payload;
|
|
root.dataset.userQappsApprovalMode = normalizeApprovalMode(saved && saved.approvalMode ? saved.approvalMode : 'once');
|
|
root.dataset.userQappsApprovalTempMinutes = String(normalizeApprovalTempMinutes(saved && saved.approvalTempMinutes ? saved.approvalTempMinutes : '10'));
|
|
root.dataset.userQappsApprovalUnlock10Min = saved && saved.approvalUnlock10Min ? '1' : '0';
|
|
root.dataset.userQappsUnlockSession20Min = saved && saved.unlockSession20Min ? '1' : '0';
|
|
applyApprovalDefaultsFromDataset();
|
|
setApprovalDefaultsResult('Approval defaults saved.', false);
|
|
}
|
|
|
|
importButton.addEventListener('click', async function () {
|
|
try {
|
|
await importAndLink();
|
|
} catch (error) {
|
|
setFeedback(error.message || 'Import/link failed', true);
|
|
}
|
|
});
|
|
|
|
refreshMappingsButton.addEventListener('click', async function () {
|
|
try {
|
|
await refreshMappings();
|
|
setFeedback('Linked accounts refreshed.', false);
|
|
} catch (error) {
|
|
setFeedback(error.message || 'Failed to refresh linked accounts', true);
|
|
}
|
|
});
|
|
|
|
importBackupButton.addEventListener('click', async function () {
|
|
try {
|
|
await importBackupAndLink();
|
|
} catch (error) {
|
|
setFeedback(error.message || 'Backup import/link failed', true);
|
|
}
|
|
});
|
|
|
|
createWalletButton.addEventListener('click', async function () {
|
|
try {
|
|
await createWallet();
|
|
} catch (error) {
|
|
setFeedback(error.message || 'Wallet creation failed', true);
|
|
}
|
|
});
|
|
|
|
backupWalletButton.addEventListener('click', async function () {
|
|
try {
|
|
await backupWallet();
|
|
} catch (error) {
|
|
setFeedback(error.message || 'Backup failed', true);
|
|
}
|
|
});
|
|
|
|
saveApprovalDefaultsButton.addEventListener('click', async function () {
|
|
try {
|
|
await saveApprovalDefaults();
|
|
} catch (error) {
|
|
setApprovalDefaultsResult(error.message || 'Failed to save approval defaults', true);
|
|
}
|
|
});
|
|
|
|
resetApprovalDefaultsButton.addEventListener('click', function () {
|
|
defaultApprovalModeEl.value = 'once';
|
|
defaultApprovalTempMinutesEl.value = '10';
|
|
defaultApprovalTempMinutesEl.disabled = true;
|
|
defaultApprovalUnlock10MinEl.checked = false;
|
|
defaultUnlockSession20MinEl.checked = false;
|
|
setApprovalDefaultsResult('Safe defaults restored in the form. Click Save Approval Defaults to apply.', false);
|
|
});
|
|
|
|
defaultApprovalModeEl.addEventListener('change', function () {
|
|
defaultApprovalTempMinutesEl.disabled = normalizeApprovalMode(defaultApprovalModeEl.value) !== 'type_minutes';
|
|
});
|
|
|
|
refreshApprovalRulesButton.addEventListener('click', async function () {
|
|
try {
|
|
await refreshApprovalRules();
|
|
setApprovalRulesResult('Approval rules refreshed.', false);
|
|
} catch (error) {
|
|
setApprovalRulesResult(error.message || 'Failed to refresh approval rules', true);
|
|
}
|
|
});
|
|
|
|
[createPasswordEl, createKdfEl].forEach(function (el) {
|
|
el.addEventListener('keydown', function (event) {
|
|
if (event.key === 'Enter') {
|
|
event.preventDefault();
|
|
}
|
|
});
|
|
});
|
|
|
|
applyApprovalDefaultsFromDataset();
|
|
|
|
refreshMappings().catch(function (error) {
|
|
setFeedback(error.message || 'Initial linked account load failed', true);
|
|
});
|
|
refreshApprovalRules().catch(function (error) {
|
|
setApprovalRulesResult(error.message || 'Initial approval rules load failed', true);
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
if (!init()) {
|
|
const observer = new MutationObserver(function () {
|
|
if (init()) {
|
|
observer.disconnect();
|
|
}
|
|
});
|
|
|
|
if (document.body) {
|
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
}
|
|
}
|
|
})();
|