airsign-web-demo
Version:
AirSign Protocol Web Demo - Shows crypto payments between browser tabs
694 lines (593 loc) • 23.9 kB
JavaScript
/**
* AirSign Embeddable SDK
*
* Drop-in Web3 payment sharing solution for any website or app
*
* Usage:
* <script src="airsign-embed.js"></script>
* <script>
* const airsign = new AirSignEmbed({
* apiKey: 'your-api-key',
* network: 'mainnet' // or 'testnet'
* });
*
* airsign.enable(); // Adds AirSign to your site
* </script>
*/
class AirSignEmbed {
constructor(options = {}) {
this.options = {
apiKey: options.apiKey || 'demo',
network: options.network || 'mainnet',
theme: options.theme || 'auto',
position: options.position || 'bottom-right',
serverUrl: options.serverUrl || 'wss://airsign.app/ws',
...options
};
this.isEnabled = false;
this.widget = null;
this.sdk = null;
this.peers = new Map();
}
// Initialize and show AirSign widget on the page
async enable() {
if (this.isEnabled) return;
try {
await this.initializeSDK();
this.createWidget();
this.connectToNetwork();
this.isEnabled = true;
console.log('🚀 AirSign enabled on your site!');
return true;
} catch (error) {
console.error('❌ AirSign initialization failed:', error);
return false;
}
}
// Remove AirSign from the page
disable() {
if (!this.isEnabled) return;
if (this.widget) {
this.widget.remove();
this.widget = null;
}
this.isEnabled = false;
console.log('🔌 AirSign disabled');
}
// Initialize the crypto SDK
async initializeSDK() {
// Load AirSign browser SDK if not already loaded
if (!window.AirSignBrowser) {
await this.loadScript('/airsign-browser.js');
}
this.sdk = new window.AirSignBrowser();
await this.sdk.init();
}
// Create floating widget UI
createWidget() {
// Remove existing widget
if (this.widget) {
this.widget.remove();
}
// Create widget container
this.widget = document.createElement('div');
this.widget.id = 'airsign-widget';
this.widget.innerHTML = this.getWidgetHTML();
// Add widget styles
this.addWidgetStyles();
// Position widget
this.positionWidget();
// Add event listeners
this.attachWidgetEvents();
// Add to page
document.body.appendChild(this.widget);
}
getWidgetHTML() {
return `
<div class="airsign-fab" id="airsign-fab">
<div class="airsign-fab-icon">💰</div>
<div class="airsign-fab-tooltip">Send Crypto Nearby</div>
</div>
<div class="airsign-panel" id="airsign-panel" style="display: none;">
<div class="airsign-header">
<h3>🚀 AirSign</h3>
<button class="airsign-close" id="airsign-close">×</button>
</div>
<div class="airsign-status" id="airsign-status">
<div class="airsign-device-name" id="airsign-device-name">Connecting...</div>
<div class="airsign-connection-status" id="airsign-connection-status">Initializing...</div>
</div>
<div class="airsign-discover-section">
<button class="airsign-discover-btn" id="airsign-discover-btn">
🔍 Find Nearby Devices
</button>
</div>
<div class="airsign-peers" id="airsign-peers">
<div class="airsign-empty-state">
<div class="airsign-empty-icon">📱</div>
<p>No devices found nearby</p>
<p>Click "Find Nearby Devices" to start</p>
</div>
</div>
<div class="airsign-footer">
<small>Powered by <strong>AirSign</strong></small>
</div>
</div>
<!-- Payment Modal -->
<div class="airsign-modal" id="airsign-payment-modal" style="display: none;">
<div class="airsign-modal-content">
<div class="airsign-modal-header">
<h3>💰 Send Crypto Payment</h3>
<p>To: <span id="airsign-selected-peer"></span></p>
</div>
<form class="airsign-payment-form" id="airsign-payment-form">
<div class="airsign-form-group">
<label>Amount</label>
<input type="number" id="airsign-amount" step="0.000001" placeholder="0.01" required>
</div>
<div class="airsign-form-group">
<label>Currency</label>
<select id="airsign-currency" required>
<option value="BTC">Bitcoin (BTC)</option>
<option value="ETH">Ethereum (ETH)</option>
<option value="USDC">USD Coin (USDC)</option>
<option value="SOL">Solana (SOL)</option>
</select>
</div>
<div class="airsign-form-group">
<label>Your Wallet Address</label>
<input type="text" id="airsign-wallet-address" placeholder="Your receiving address" required>
</div>
<div class="airsign-modal-actions">
<button type="button" class="airsign-btn airsign-btn-cancel" id="airsign-cancel-payment">Cancel</button>
<button type="submit" class="airsign-btn airsign-btn-send">Send Request</button>
</div>
</form>
</div>
</div>
`;
}
addWidgetStyles() {
if (document.getElementById('airsign-styles')) return;
const styles = document.createElement('style');
styles.id = 'airsign-styles';
styles.textContent = `
#airsign-widget {
position: fixed;
z-index: 999999;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
.airsign-fab {
width: 60px;
height: 60px;
background: linear-gradient(45deg, #FF6B6B, #4ECDC4);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
transition: all 0.3s ease;
position: relative;
}
.airsign-fab:hover {
transform: scale(1.1);
box-shadow: 0 6px 25px rgba(0,0,0,0.2);
}
.airsign-fab-icon {
font-size: 24px;
color: white;
}
.airsign-fab-tooltip {
position: absolute;
right: 70px;
top: 50%;
transform: translateY(-50%);
background: rgba(0,0,0,0.8);
color: white;
padding: 8px 12px;
border-radius: 6px;
font-size: 14px;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease;
}
.airsign-fab:hover .airsign-fab-tooltip {
opacity: 1;
}
.airsign-panel {
position: absolute;
bottom: 80px;
right: 0;
width: 350px;
max-height: 500px;
background: white;
border-radius: 15px;
box-shadow: 0 10px 40px rgba(0,0,0,0.15);
border: 1px solid #e0e0e0;
overflow: hidden;
}
.airsign-header {
background: linear-gradient(45deg, #FF6B6B, #4ECDC4);
color: white;
padding: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.airsign-header h3 {
margin: 0;
font-size: 18px;
}
.airsign-close {
background: none;
border: none;
color: white;
font-size: 24px;
cursor: pointer;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: background 0.3s ease;
}
.airsign-close:hover {
background: rgba(255,255,255,0.2);
}
.airsign-status {
padding: 20px;
border-bottom: 1px solid #f0f0f0;
text-align: center;
}
.airsign-device-name {
font-weight: bold;
font-size: 16px;
color: #333;
margin-bottom: 5px;
}
.airsign-connection-status {
font-size: 14px;
color: #666;
}
.airsign-discover-section {
padding: 20px;
text-align: center;
}
.airsign-discover-btn {
background: linear-gradient(45deg, #4ECDC4, #44A08D);
color: white;
border: none;
padding: 12px 24px;
border-radius: 25px;
cursor: pointer;
font-weight: bold;
font-size: 14px;
transition: all 0.3s ease;
width: 100%;
}
.airsign-discover-btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(78, 205, 196, 0.3);
}
.airsign-peers {
max-height: 200px;
overflow-y: auto;
padding: 0 20px 20px;
}
.airsign-empty-state {
text-align: center;
padding: 20px;
color: #666;
}
.airsign-empty-icon {
font-size: 48px;
margin-bottom: 10px;
}
.airsign-peer-item {
background: #f8f9fa;
border-radius: 10px;
padding: 15px;
margin-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
transition: all 0.3s ease;
cursor: pointer;
}
.airsign-peer-item:hover {
background: #e9ecef;
transform: translateY(-1px);
}
.airsign-peer-info h4 {
margin: 0 0 5px 0;
color: #333;
font-size: 14px;
}
.airsign-peer-info p {
margin: 0;
color: #666;
font-size: 12px;
}
.airsign-send-btn {
background: linear-gradient(45deg, #FF6B6B, #FF8E8E);
color: white;
border: none;
padding: 8px 16px;
border-radius: 15px;
cursor: pointer;
font-weight: bold;
font-size: 12px;
transition: all 0.3s ease;
}
.airsign-send-btn:hover {
transform: scale(1.05);
}
.airsign-footer {
background: #f8f9fa;
padding: 15px 20px;
text-align: center;
color: #666;
font-size: 12px;
}
.airsign-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000000;
}
.airsign-modal-content {
background: white;
border-radius: 15px;
padding: 30px;
max-width: 400px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
}
.airsign-modal-header {
text-align: center;
margin-bottom: 20px;
}
.airsign-modal-header h3 {
margin: 0 0 5px 0;
color: #333;
}
.airsign-payment-form {
display: flex;
flex-direction: column;
gap: 15px;
}
.airsign-form-group {
display: flex;
flex-direction: column;
}
.airsign-form-group label {
margin-bottom: 5px;
font-weight: bold;
color: #333;
font-size: 14px;
}
.airsign-form-group input,
.airsign-form-group select {
padding: 12px;
border: 2px solid #dee2e6;
border-radius: 8px;
font-size: 14px;
transition: border-color 0.3s ease;
}
.airsign-form-group input:focus,
.airsign-form-group select:focus {
outline: none;
border-color: #4ECDC4;
}
.airsign-modal-actions {
display: flex;
gap: 10px;
margin-top: 20px;
}
.airsign-btn {
flex: 1;
padding: 12px 20px;
border: none;
border-radius: 8px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
}
.airsign-btn-cancel {
background: #6c757d;
color: white;
}
.airsign-btn-cancel:hover {
background: #5a6268;
}
.airsign-btn-send {
background: linear-gradient(45deg, #FF6B6B, #FF8E8E);
color: white;
}
.airsign-btn-send:hover {
transform: translateY(-2px);
}
`;
document.head.appendChild(styles);
}
positionWidget() {
const positions = {
'bottom-right': { bottom: '20px', right: '20px' },
'bottom-left': { bottom: '20px', left: '20px' },
'top-right': { top: '20px', right: '20px' },
'top-left': { top: '20px', left: '20px' }
};
const pos = positions[this.options.position] || positions['bottom-right'];
Object.assign(this.widget.style, pos);
}
attachWidgetEvents() {
const fab = this.widget.querySelector('#airsign-fab');
const panel = this.widget.querySelector('#airsign-panel');
const closeBtn = this.widget.querySelector('#airsign-close');
const discoverBtn = this.widget.querySelector('#airsign-discover-btn');
const paymentForm = this.widget.querySelector('#airsign-payment-form');
const cancelPayment = this.widget.querySelector('#airsign-cancel-payment');
const paymentModal = this.widget.querySelector('#airsign-payment-modal');
// Toggle panel
fab.addEventListener('click', () => {
const isVisible = panel.style.display !== 'none';
panel.style.display = isVisible ? 'none' : 'block';
});
// Close panel
closeBtn.addEventListener('click', () => {
panel.style.display = 'none';
});
// Discovery
discoverBtn.addEventListener('click', () => {
this.startDiscovery();
});
// Payment form
paymentForm.addEventListener('submit', (e) => {
e.preventDefault();
this.sendPayment();
});
// Cancel payment
cancelPayment.addEventListener('click', () => {
paymentModal.style.display = 'none';
});
// Close modal on backdrop click
paymentModal.addEventListener('click', (e) => {
if (e.target === paymentModal) {
paymentModal.style.display = 'none';
}
});
}
async connectToNetwork() {
// Implementation would connect to AirSign network
// For demo, simulate connection
this.updateStatus('Connected to AirSign Network');
this.updateDeviceName(this.generateDeviceName());
}
startDiscovery() {
// Implementation would start device discovery
this.updatePeerList([
{ id: '1', name: "Alice's iPhone", capabilities: ['crypto-payments'] },
{ id: '2', name: "Bob's MacBook", capabilities: ['crypto-payments'] }
]);
}
updateStatus(status) {
const statusEl = this.widget.querySelector('#airsign-connection-status');
if (statusEl) statusEl.textContent = status;
}
updateDeviceName(name) {
const nameEl = this.widget.querySelector('#airsign-device-name');
if (nameEl) nameEl.textContent = name;
}
updatePeerList(peers) {
const peersEl = this.widget.querySelector('#airsign-peers');
if (!peersEl) return;
if (peers.length === 0) {
peersEl.innerHTML = `
<div class="airsign-empty-state">
<div class="airsign-empty-icon">📱</div>
<p>No devices found nearby</p>
<p>Make sure other devices are running AirSign</p>
</div>
`;
} else {
peersEl.innerHTML = peers.map(peer => `
<div class="airsign-peer-item" onclick="window.airsignEmbed.openPaymentModal('${peer.id}', '${peer.name}')">
<div class="airsign-peer-info">
<h4>${peer.name}</h4>
<p>💎 Crypto enabled</p>
</div>
<button class="airsign-send-btn" onclick="event.stopPropagation(); window.airsignEmbed.openPaymentModal('${peer.id}', '${peer.name}')">
💰 Send
</button>
</div>
`).join('');
}
}
openPaymentModal(peerId, peerName) {
const modal = this.widget.querySelector('#airsign-payment-modal');
const selectedPeer = this.widget.querySelector('#airsign-selected-peer');
this.selectedPeer = { id: peerId, name: peerName };
selectedPeer.textContent = peerName;
modal.style.display = 'flex';
}
async sendPayment() {
// Implementation would send encrypted payment
const amount = this.widget.querySelector('#airsign-amount').value;
const currency = this.widget.querySelector('#airsign-currency').value;
const address = this.widget.querySelector('#airsign-wallet-address').value;
console.log(`💰 Sending ${amount} ${currency} to ${this.selectedPeer.name}`);
// Close modal
this.widget.querySelector('#airsign-payment-modal').style.display = 'none';
// Show success
this.showNotification(`Payment request sent to ${this.selectedPeer.name}!`);
}
showNotification(message) {
// Simple notification
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: #4ECDC4;
color: white;
padding: 15px 20px;
border-radius: 8px;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
z-index: 1000001;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
animation: slideIn 0.3s ease;
`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.remove();
}, 3000);
}
generateDeviceName() {
const names = [
"User's iPhone", "User's MacBook", "User's Pixel", "User's iPad",
"User's Surface", "User's Galaxy", "User's Desktop", "User's Laptop"
];
return names[Math.floor(Math.random() * names.length)];
}
async loadScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
}
// Auto-attach to window for easy access
window.AirSignEmbed = AirSignEmbed;
// Auto-initialize if data attributes are present
document.addEventListener('DOMContentLoaded', () => {
const autoInit = document.querySelector('[data-airsign-auto]');
if (autoInit) {
const options = {
apiKey: autoInit.dataset.airsignApiKey,
network: autoInit.dataset.airsignNetwork,
theme: autoInit.dataset.airsignTheme,
position: autoInit.dataset.airsignPosition
};
window.airsignEmbed = new AirSignEmbed(options);
window.airsignEmbed.enable();
}
});
// Export for module systems
if (typeof module !== 'undefined' && module.exports) {
module.exports = AirSignEmbed;
}