Merge branch 'main' of ssh://gitea.jeffemmett.com:223/jeffemmett/rspace-online
This commit is contained in:
commit
00e5229ef7
|
|
@ -78,6 +78,15 @@ export {
|
||||||
type AccountVault,
|
type AccountVault,
|
||||||
} from './vault';
|
} from './vault';
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// WALLET SYNC
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export {
|
||||||
|
syncWalletsOnLogin,
|
||||||
|
syncWalletsToVault,
|
||||||
|
} from './wallet-sync';
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// UI COMPONENTS
|
// UI COMPONENTS
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import { getKeyManager } from '../key-derivation';
|
||||||
import { getSessionManager, AuthLevel } from '../session';
|
import { getSessionManager, AuthLevel } from '../session';
|
||||||
import { getDocBridge, resetDocBridge } from '../../../shared/local-first/encryptid-bridge';
|
import { getDocBridge, resetDocBridge } from '../../../shared/local-first/encryptid-bridge';
|
||||||
import { getVaultManager, resetVaultManager } from '../vault';
|
import { getVaultManager, resetVaultManager } from '../vault';
|
||||||
|
import { syncWalletsOnLogin } from '../wallet-sync';
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// STYLES
|
// STYLES
|
||||||
|
|
@ -415,9 +416,9 @@ export class EncryptIDLoginButton extends HTMLElement {
|
||||||
// Load encrypted account vault in background
|
// Load encrypted account vault in background
|
||||||
const docCrypto = getDocBridge().getDocCrypto();
|
const docCrypto = getDocBridge().getDocCrypto();
|
||||||
if (docCrypto) {
|
if (docCrypto) {
|
||||||
getVaultManager(docCrypto).load().catch(err =>
|
getVaultManager(docCrypto).load()
|
||||||
console.warn('Vault load failed:', err)
|
.then(() => syncWalletsOnLogin(docCrypto))
|
||||||
);
|
.catch(err => console.warn('Vault load failed:', err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -429,7 +430,7 @@ export class EncryptIDLoginButton extends HTMLElement {
|
||||||
await sessionManager.createSession(result, keys.did, {
|
await sessionManager.createSession(result, keys.did, {
|
||||||
encrypt: true,
|
encrypt: true,
|
||||||
sign: true,
|
sign: true,
|
||||||
wallet: false, // Will be true after wallet setup
|
wallet: !!keys.eoaAddress,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Dispatch success event
|
// Dispatch success event
|
||||||
|
|
@ -534,9 +535,9 @@ export class EncryptIDLoginButton extends HTMLElement {
|
||||||
// Load encrypted account vault in background
|
// Load encrypted account vault in background
|
||||||
const docCrypto = getDocBridge().getDocCrypto();
|
const docCrypto = getDocBridge().getDocCrypto();
|
||||||
if (docCrypto) {
|
if (docCrypto) {
|
||||||
getVaultManager(docCrypto).load().catch(err =>
|
getVaultManager(docCrypto).load()
|
||||||
console.warn('Vault load failed:', err)
|
.then(() => syncWalletsOnLogin(docCrypto))
|
||||||
);
|
.catch(err => console.warn('Vault load failed:', err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -546,7 +547,7 @@ export class EncryptIDLoginButton extends HTMLElement {
|
||||||
await sessionManager.createSession(result, keys.did, {
|
await sessionManager.createSession(result, keys.did, {
|
||||||
encrypt: true,
|
encrypt: true,
|
||||||
sign: true,
|
sign: true,
|
||||||
wallet: false,
|
wallet: !!keys.eoaAddress,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.dispatchEvent(new CustomEvent('login-success', {
|
this.dispatchEvent(new CustomEvent('login-success', {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,145 @@
|
||||||
|
/**
|
||||||
|
* EncryptID Wallet Sync
|
||||||
|
*
|
||||||
|
* Bridges WalletStore (encrypted localStorage) ↔ VaultManager (encrypted server blob)
|
||||||
|
* so wallet associations survive across devices.
|
||||||
|
*
|
||||||
|
* On login:
|
||||||
|
* 1. Load vault from server
|
||||||
|
* 2. Merge vault wallets into local WalletStore (new entries only)
|
||||||
|
* 3. Merge local wallets into vault (in case this device has wallets the vault doesn't)
|
||||||
|
* 4. Save vault if anything changed
|
||||||
|
*
|
||||||
|
* On wallet add/remove:
|
||||||
|
* syncWalletsToVault() pushes current WalletStore state into the vault.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getWalletStore, type SafeWalletEntry } from './wallet-store';
|
||||||
|
import { getVaultManager, type AccountVault } from './vault';
|
||||||
|
import { getKeyManager } from './key-derivation';
|
||||||
|
import type { DocCrypto } from '../../shared/local-first/crypto';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After login + vault load, merge wallets bidirectionally between
|
||||||
|
* the local WalletStore and the remote AccountVault.
|
||||||
|
*
|
||||||
|
* Requires PRF-derived encryption key (for WalletStore) and DocCrypto (for VaultManager).
|
||||||
|
*/
|
||||||
|
export async function syncWalletsOnLogin(docCrypto: DocCrypto): Promise<void> {
|
||||||
|
try {
|
||||||
|
const km = getKeyManager();
|
||||||
|
if (!km.isInitialized()) return;
|
||||||
|
|
||||||
|
const keys = await km.getKeys();
|
||||||
|
if (!keys.encryptionKey) return;
|
||||||
|
|
||||||
|
const walletStore = getWalletStore(keys.encryptionKey);
|
||||||
|
const vaultManager = getVaultManager(docCrypto);
|
||||||
|
|
||||||
|
const vault = await vaultManager.get();
|
||||||
|
const localWallets = await walletStore.list();
|
||||||
|
const vaultWallets = vault.wallets || [];
|
||||||
|
|
||||||
|
let changed = false;
|
||||||
|
|
||||||
|
// 1. Restore vault wallets → local WalletStore (if not already present)
|
||||||
|
for (const vw of vaultWallets) {
|
||||||
|
const exists = localWallets.find(
|
||||||
|
lw => lw.safeAddress.toLowerCase() === vw.safeAddress.toLowerCase()
|
||||||
|
&& lw.chainId === vw.chainId
|
||||||
|
);
|
||||||
|
if (!exists) {
|
||||||
|
await walletStore.add({
|
||||||
|
safeAddress: vw.safeAddress,
|
||||||
|
chainId: vw.chainId,
|
||||||
|
eoaAddress: vw.eoaAddress,
|
||||||
|
label: vw.label,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Push local wallets → vault (if not already present)
|
||||||
|
const updatedLocal = await walletStore.list();
|
||||||
|
const updatedVaultWallets = [...vaultWallets];
|
||||||
|
|
||||||
|
for (const lw of updatedLocal) {
|
||||||
|
const exists = updatedVaultWallets.find(
|
||||||
|
vw => vw.safeAddress.toLowerCase() === lw.safeAddress.toLowerCase()
|
||||||
|
&& vw.chainId === lw.chainId
|
||||||
|
);
|
||||||
|
if (!exists) {
|
||||||
|
updatedVaultWallets.push({
|
||||||
|
id: lw.id,
|
||||||
|
safeAddress: lw.safeAddress,
|
||||||
|
chainId: lw.chainId,
|
||||||
|
eoaAddress: lw.eoaAddress,
|
||||||
|
label: lw.label,
|
||||||
|
addedAt: lw.addedAt,
|
||||||
|
});
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Also ensure the server-side wallet_address profile field is set
|
||||||
|
// This is the fallback for mobile (no PRF) — the JWT carries it.
|
||||||
|
if (keys.eoaAddress) {
|
||||||
|
await setServerWalletAddress(keys.eoaAddress).catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Save vault if anything changed
|
||||||
|
if (changed) {
|
||||||
|
await vaultManager.update({ wallets: updatedVaultWallets });
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Wallet sync failed:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push current WalletStore state into the vault.
|
||||||
|
* Call this after any wallet add/update/remove operation.
|
||||||
|
*/
|
||||||
|
export async function syncWalletsToVault(docCrypto: DocCrypto): Promise<void> {
|
||||||
|
try {
|
||||||
|
const km = getKeyManager();
|
||||||
|
if (!km.isInitialized()) return;
|
||||||
|
|
||||||
|
const keys = await km.getKeys();
|
||||||
|
if (!keys.encryptionKey) return;
|
||||||
|
|
||||||
|
const walletStore = getWalletStore(keys.encryptionKey);
|
||||||
|
const vaultManager = getVaultManager(docCrypto);
|
||||||
|
|
||||||
|
const localWallets = await walletStore.list();
|
||||||
|
const vaultWallets = localWallets.map(lw => ({
|
||||||
|
id: lw.id,
|
||||||
|
safeAddress: lw.safeAddress,
|
||||||
|
chainId: lw.chainId,
|
||||||
|
eoaAddress: lw.eoaAddress,
|
||||||
|
label: lw.label,
|
||||||
|
addedAt: lw.addedAt,
|
||||||
|
}));
|
||||||
|
|
||||||
|
await vaultManager.update({ wallets: vaultWallets });
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Wallet vault sync failed:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the wallet address on the server user profile.
|
||||||
|
* This ensures the JWT carries the wallet address for non-PRF devices (mobile).
|
||||||
|
*/
|
||||||
|
async function setServerWalletAddress(walletAddress: string): Promise<void> {
|
||||||
|
const token = localStorage.getItem('encryptid_token');
|
||||||
|
if (!token) return;
|
||||||
|
|
||||||
|
await fetch('/encryptid/api/wallet-capability', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ walletAddress }),
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue