Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 106 additions & 27 deletions wasmaudioworklet/wasmgit/nearacl.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { loadScript } from '../common/scriptloader.js';
import { modal } from '../common/ui/modal.js';


// configure minimal network settings and key storage
Expand All @@ -24,52 +25,130 @@ export const nearconfig = {

export let authdata = null;

// open a connection to the NEAR platform
// ============================================================================
// Credential Management (Password Manager)
// ============================================================================

async function getCredentials() {
const credentials = await navigator.credentials.get({
mediation: 'required',
password: true,
});

if (!credentials) {
throw new Error('No credentials selected');
}

const credentialData = JSON.parse(atob(credentials.password));
return credentialData;
}

export async function createCredentials() {
const result = await modal(`
<h3>Create NEAR Credentials</h3>
<p>Store a function access key for your NEAR account.</p>
<p>
<label>Account ID:</label><br>
<input type="text" id="accountIdInput" placeholder="myaccount.near" style="width: 100%; box-sizing: border-box;">
</p>
<p>
<label>Function Access Key:</label><br>
<input type="text" id="accessKeyInput" placeholder="ed25519:..." style="width: 100%; box-sizing: border-box;">
</p>
<p>
<button onclick="getRootNode().result(null)">Cancel</button>
<button onclick="getRootNode().result({ accountId: getRootNode().getElementById('accountIdInput').value, accessKey: getRootNode().getElementById('accessKeyInput').value })">Save</button>
</p>
`);

if (!result || !result.accountId || !result.accessKey) return;

try {
const credentials = await navigator.credentials.create({
password: {
id: result.accountId,
name: `WASM-git: ${result.accountId}`,
origin: location.origin,
password: btoa(JSON.stringify({
accountId: result.accountId,
accessKey: result.accessKey,
})),
},
});

await navigator.credentials.store(credentials);
await modal(`
<h3>Success</h3>
<p>✅ Credentials stored for ${result.accountId}</p>
<button onclick="getRootNode().result(null)">OK</button>
`);
} catch (error) {
console.error('Failed to create credentials:', error);
await modal(`
<h3>Error</h3>
<p>Failed to create credentials: ${error.message}</p>
<button onclick="getRootNode().result(null)">OK</button>
`);
}
}

export async function login() {
await walletConnection.requestSignIn(
nearconfig.contractName,
'WASM-git'
);
await loadAccountData();
try {
const credentials = await getCredentials();
// Store credentials in localStorage for auto-login on reload
localStorage.setItem('wasmgit_credentials', JSON.stringify(credentials));
await loadAccountData(credentials);
} catch (error) {
console.error('Login failed:', error);
throw new Error('Failed to retrieve credentials. Please create credentials first.');
}
}

export async function logout() {
await walletConnection.signOut();
authdata = null;
localStorage.removeItem('wasmgit_credentials');
}

async function loadAccountData() {
const currentUser = {
accountId: (await walletConnection.account()).accountId
}
async function loadAccountData(credentials) {
const { accountId, accessKey } = credentials;

// Set up the key in the keystore
const keyPair = nearApi.utils.KeyPair.fromString(accessKey);
await nearconfig.deps.keyStore.setKey(nearconfig.networkId, accountId, keyPair);

const tokenMessage = btoa(JSON.stringify({ accountId: currentUser.accountId, iat: new Date().getTime() }));
const signature = await walletConnection.account()
.connection.signer
.signMessage(new TextEncoder().encode(tokenMessage), currentUser.accountId, nearconfig.networkId);
const tokenMessage = btoa(JSON.stringify({ accountId: accountId, iat: new Date().getTime() }));
const signature = await window.near.connection.signer.signMessage(
new TextEncoder().encode(tokenMessage),
accountId,
nearconfig.networkId
);

authdata = {
accessToken: tokenMessage + '.' + btoa(String.fromCharCode(...signature.signature)),
useremail: currentUser.accountId,
username: currentUser.accountId
useremail: accountId,
username: accountId
};
}

export async function initNear() {
await loadScript('https://cdn.jsdelivr.net/npm/near-api-js@0.44.2/dist/near-api-js.min.js');

nearconfig.deps.keyStore = new nearApi.keyStores.BrowserLocalStorageKeyStore();
nearconfig.networkId = 'mainnet';
nearconfig.deps.keyStore = new nearApi.keyStores.InMemoryKeyStore();
window.near = await nearApi.connect(nearconfig);
const walletConnection = new nearApi.WalletConnection(near);
window.walletConnection = walletConnection;

// Load in account data
const accountId = (await walletConnection.account()).accountId;
if (accountId) {
console.log('logged in as', accountId);
await loadAccountData();

// Try to auto-login from stored credentials
const storedCredentials = localStorage.getItem('wasmgit_credentials');
if (storedCredentials) {
try {
const credentials = JSON.parse(storedCredentials);
console.log('logged in as', credentials.accountId);
await loadAccountData(credentials);
} catch (error) {
console.log('failed to load stored credentials:', error);
localStorage.removeItem('wasmgit_credentials');
}
} else {
console.log('no loggedin user');
console.log('no stored credentials');
}
}
13 changes: 10 additions & 3 deletions wasmaudioworklet/wasmgit/wasmgitclient.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { initNear, authdata as nearAuthData, login as nearLogin, logout as nearLogout } from './nearacl.js';
import { initNear, authdata as nearAuthData, login as nearLogin, logout as nearLogout, createCredentials as nearCreateCredentials } from './nearacl.js';
import { toggleSpinner } from '../common/ui/progress-spinner.js';
import { modal } from '../common/ui/modal.js';

Expand Down Expand Up @@ -319,12 +319,19 @@ customElements.define('wasmgit-ui',
await nearLogout();
location.reload();
};
this.shadowRoot.getElementById('loginButton').style.display = 'none';
this.shadowRoot.getElementById('createCredentialsButton').style.display = 'none';
} else {
this.shadowRoot.getElementById('loginButton').style.display = 'block';
this.shadowRoot.getElementById('createCredentialsButton').style.display = 'block';
this.shadowRoot.getElementById('logoutButton').style.display = 'none';
this.shadowRoot.getElementById('loggedinuserspan').style.display = 'none';
this.shadowRoot.getElementById('loginButton').onclick = () => {
nearLogin();
this.shadowRoot.getElementById('loginButton').onclick = async () => {
await nearLogin();
location.reload();
};
this.shadowRoot.getElementById('createCredentialsButton').onclick = async () => {
await nearCreateCredentials();
};
}
updateCommitAndSyncButtonState(await repoHasChanges());
Expand Down
1 change: 1 addition & 0 deletions wasmaudioworklet/wasmgit/wasmgitui.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<span id="loggedinuserspan" style="display: none"></span>
<button id="logoutButton" style="display: none">logout</button>
<button id="loginButton" style="display: none">login</button>
<button id="createCredentialsButton" style="display: none">add key</button>
<button id="syncRemoteButton">Commit & Sync</button>
<button id="discardChangesButton">Discard changes</button>
<button id="deleteLocalButton">Delete local</button>
Expand Down
Loading