console
function spinnerBusy(element, busy) {
element.data("spin", busy);
if (busy) {
element.finish().fadeIn();
} else {
element.finish().fadeOut();
}
}
$(() => {
let signOutButton = $('#signout');
let user = $('#user');
let userForm = user.find('form');
let userName = user.find('#userName');
let userNameInput = userName.find('input');
let userSecret = user.find('#userSecret');
let userSecretInput = userSecret.find('input');
let userSecretSpinner = userSecret.find('.fa-spin');
let userAlgorithm = user.find('#algorithmVersion');
let userAlgorithmInput = userAlgorithm.find('select');
let userMessage = user.find('p.info');
let userError = user.find('p.error');
let site = $('#site');
let siteForm = site.find('form');
let siteName = site.find('#siteName');
let siteNameInput = siteName.find('input');
let sitePurposeInputs = site.find('input[name="sitePurpose"]');
let siteCounter = site.find('#siteCounter');
let siteCounterInput = siteCounter.find('input');
let siteType = site.find('#siteType');
let siteTypeInput = siteType.find('select');
let siteResult = site.find('#siteResult');
let siteResultSpinner = siteResult.find('.fa-spin');
let siteResultButton = siteResult.find('button');
let siteResultInput = siteResultButton.find('input')
let siteMessage = site.find('p.info');
let siteError = site.find('p.error');
for (template in spectre.templates) {
let option = document.createElement('option');
option.text = spectre.resultName[template];
option.value = template;
siteTypeInput[0].add(option);
}
for (option of sitePurposeInputs) {
option.checked = option.value === spectre.purpose.authentication;
}
function updateDefaults() {
userAlgorithmInput[0].value = spectre.algorithm.current;
siteCounterInput[0].value = spectre.counter.initial;
switch (sitePurposeInputs.filter(':checked')[0].value) {
case spectre.purpose.authentication: {
siteTypeInput[0].value = spectre.resultType.defaultPassword;
break;
}
case spectre.purpose.identification: {
siteTypeInput[0].value = spectre.resultType.defaultLogin;
break;
}
case spectre.purpose.recovery: {
siteTypeInput[0].value = spectre.resultType.defaultAnswer;
break;
}
}
}
function updateSpectre() {
spectre.request(
siteNameInput[0].value,
siteTypeInput[0].value,
siteCounterInput[0].value,
sitePurposeInputs.filter(':checked')[0].value,
null
);
}
function updateView() {
spinnerBusy(userSecretSpinner, spectre.operations.user.pending);
spinnerBusy(siteResultSpinner, spectre.operations.site.pending);
userNameInput.val(spectre.operations.user.userName);
userSecretInput.val(null);
siteResultInput.val(spectre.result(siteNameInput[0].value, sitePurposeInputs.filter(':checked')[0].value));
if (spectre.operations.user.authenticated) {
user.attr("data-active", false);
site.attr("data-active", true);
siteNameInput.focus()
} else {
user.attr("data-active", true);
site.attr("data-active", false);
userAlgorithmInput.val(spectre.algorithm.current);
siteNameInput.val(null);
userNameInput.focus()
}
}
updateDefaults();
spectre.observers.push(updateView);
updateView();
userForm.on('submit', (e) => {
e.preventDefault();
spectre.authenticate(userNameInput[0].value, userSecretInput[0].value, userAlgorithmInput[0].value);
});
siteForm.on('submit', (e) => {
e.preventDefault()
siteResultInput.select()
if (navigator.clipboard.writeText(siteResultInput[0].value) || document.execCommand('copy')) {
siteResultButton.attr("title", "Copied!").tooltip("_fixTitle").tooltip("show");
setTimeout(() => {
siteResultButton.tooltip("hide").attr("title", "Copy Password").tooltip("_fixTitle");
}, 1000);
}
});
signOutButton.on('click', () => {
spectre.invalidate();
});
siteNameInput.on('input', () => {
updateSpectre();
});
sitePurposeInputs.on('input', () => {
updateDefaults();
updateSpectre();
});
siteCounterInput.on('input', () => {
updateSpectre();
});
siteTypeInput.on('input', () => {
updateSpectre();
});
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Spectre Web</title>
<meta name="description" content="Password manager and secure identity platform. Spectre is a free and offline password cipher." />
<meta name="keywords" content="password manager,password generator,passwords,free,secure,strong,random,online,offline,privacy,web" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#3E8989">
<meta property="og:image" content="images/product/environment/spectre-dark-desk.jpg">
<meta name="apple-itunes-app" content="app-id=1526402806" />
<meta name="twitter:card" content="app">
<meta name="twitter:app:id:iphone" content="1526402806">
<meta name="twitter:app:name:iphone" content="Spectre">
<meta name="twitter:app:url:iphone" content="spectre://">
<meta name="author" content="Maarten Billemont">
<link rel="author" href="https://lhunath.com">
<link rel="icon" href="images/spectre.png">
<link rel="stylesheet" href="plugins/bootstrap/bootstrap.min.css">
<link rel="stylesheet" href="plugins/fontawesome/css/all.min.css">
<link rel="stylesheet" href="css/spectre-base.css" media="screen">
<link rel="stylesheet" href="css/spectre-web.css" media="screen">
<script defer src="plugins/jquery/jquery.min.js"></script>
</head>
<body>
<div class="banner">
<div class="container">
<h4>Alpha Version</h4>
<p>You are trying out a pre-release of Spectre's web version.</p>
<a href="https://gitlab.com/spectre.app/web/" class="btn btn-transparent">Source Code</a>
</div>
</div>
<section id="application">
<div id="user" class="container" data-active="true">
<div class="row justify-content-center">
<div class="col-lg-6 text-center box">
<i class="fa-duotone fa-user-circle"></i>
<hr>
<form>
<p>
<label id="userName">
<i class="fa-duotone fa-fw fa-user-large"></i>
Your Full Name
<input type="text" placeholder="eg. Robert Lee Mitchell" autocomplete="username">
</label>
</p>
<p>
<label id="userSecret">
<i class="fa-duotone fa-fw fa-user-lock"></i>
你的密语
<i class="fa-duotone fa-fw fa-galaxy fa-spin"></i>
<input type="password" placeholder="eg. banana colored duckling" autocomplete="current-password">
</label>
</p>
<p>
<label id="algorithmVersion">
<i class="fa-duotone fa-fw fa-rectangle-vertical-history"></i>
Algorithm Version
<select>
<option value="0">V0 (2012:03)</option>
<option value="1">V1 (2012:07)</option>
<option value="2">V2 (2014:09)</option>
<option value="3" selected>V3 (2015:01)</option>
</select>
</label>
</p>
<hr>
<p class="info"></p>
<p class="error"></p>
<p>
<button type="submit" id="signin">
<i class="fa-duotone fa-fw fa-2x fa-right-to-bracket"></i><br>
Sign In
</button>
</p>
</form>
<p class="caption">Your information remains 100% offline.</p>
</div>
</div>
</div>
<div id="site" class="container" data-active="false">
<div class="row justify-content-center">
<div class="col-lg-6 text-center box">
<i class="fa-duotone fa-globe"></i>
<hr>
<form>
<p>
<label id="siteName">
<i class="fa-duotone fa-fw fa-globe"></i>
Site Domain
<input type="text" placeholder="eg. twitter.com">
</label>
</p>
<p> </p>
<p>
<input type="radio" name="sitePurpose" id="sitePurposePassword" value="com.lyndir.masterpassword">
<label for="sitePurposePassword">Site Password</label>
<input type="radio" name="sitePurpose" id="sitePurposeLogin" value="com.lyndir.masterpassword.login">
<label for="sitePurposeLogin">Site Login Name</label>
<input type="radio" name="sitePurpose" id="sitePurposeAnswer" value="com.lyndir.masterpassword.answer">
<label for="sitePurposeAnswer">Site Security Answer</label>
</p>
<hr>
<p>
<label id="siteCounter">
<i class="fa-duotone fa-fw fa-rectangle-vertical-history"></i>
Site Counter
<input type="number" placeholder="1" min="1" max="100" value="1" class="half" />
</label>
</p>
<p>
<label id="siteType">
<i class="fa-duotone fa-fw fa-rectangle-vertical-history"></i>
Type
<select></select>
</label>
</p>
<p>
<label id="siteResult">
<i class="fa-duotone fa-fw" style="background: url('images/spectre-light-glyph.svg') no-repeat center/contain"></i>
Result
<i class="fa-duotone fa-fw fa-galaxy fa-spin"></i>
<button type="submit" class="fa-solid fa-copy">
<input class="code" value="PozoLalv0_Yelo" readonly="">
</button>
</label>
</p>
</form>
<p class="info"></p>
<p class="error"></p>
<hr>
<p>
<button type="submit" id="signout">
<i class="fa-duotone fa-fw fa-2x fa-right-from-bracket"></i><br>
Sign Out
</button>
</p>
<p class="caption">Your information remains 100% offline.</p>
</div>
</div>
</div>
</section>
<footer id="footer">
<div class="container">
<div class="row wow fadeInUp" data-wow-duration="500ms">
<div class="col-12 text-center">
<div class="copyright">
<a href="https://spectre.app/">
<img src="http://cdn.jsrun.top/res/meichenhui/spectre-light.png" alt="Spectre: Passwords, Privacy-first" height="42">
</a>
<br>
<p>Copyright © 2011 <a href="https://lhunath.com">Maarten Billemont</a></p>
</div>
</div>
</div>
</div>
</footer>
<script defer src="plugins/bootstrap/bootstrap.bundle.min.js"></script>
<script defer src="js/spectre/spectre-types.js"></script>
<script defer src="js/spectre/spectre-service.js"></script>
<script defer src="js/main.js"></script>
</body>
</html>