onesearch / components /QRGenerator.js
jessikat29's picture
OneSearch Technical Specification
11a16a2 verified
Raw
History Blame Contribute Delete
12.4 kB
// QR Generation and Trust Network Component
class QRGenerator {
static init() {
console.log('QRGenerator component initialized');
this.setupQRValidation();
this.loadTrustNetwork();
}
static generateTrustQR(productId, recommenderId, reason, expirationDays = 30) {
const qrId = this.generateQRId();
const issuedAt = new Date().toISOString();
const expiresAt = new Date(Date.now() + expirationDays * 24 * 60 * 60 * 1000).toISOString();
const payload = {
v: 1,
p: recommenderId,
m: this.getMerchantId(productId),
pr: productId,
q: qrId,
t: issuedAt,
exp: expiresAt,
r: reason
};
const payloadString = `TSQR:v1|p:${payload.p}|m:${payload.m}|pr:${payload.pr}|q:${payload.q}|t:${payload.t}|exp:${payload.exp}|r:${encodeURIComponent(payload.r)}`;
// In a real implementation, this would be signed with the user's private key
const signature = this.signPayload(payloadString, recommenderId);
const signedPayload = `${payloadString}|sig:${signature}`;
return {
qrId,
payload: signedPayload,
expiresAt,
status: 'active'
};
}
static signPayload(payload, userId) {
// In a real implementation, this would use WebCrypto API or similar
// to sign the payload with the user's private key
const message = `${payload}|user:${userId}|nonce:${Math.random().toString(36).substring(7)}`;
// Simulate signature generation
return btoa(message).substring(0, 44); // Base64 encoded signature
}
static validateQRScan(scannedPayload, scannerContext = {}) {
try {
// Parse the QR payload
const parsed = this.parseQRPpayload(scannedPayload);
if (!parsed) {
return { valid: false, error: 'Invalid QR format' };
}
// Check expiration
if (new Date(parsed.exp) < new Date()) {
return { valid: false, error: 'QR code has expired' };
}
// Validate signature
if (!this.validateSignature(scannedPayload, parsed)) {
return { valid: false, error: 'Invalid signature' };
}
// Check usage limits
const usageCount = this.getUsageCount(parsed.q);
const maxUses = this.getMaxUses(parsed);
if (usageCount >= maxUses) {
return { valid: false, error: 'QR code usage limit reached' };
}
// Record the scan
this.recordScan(parsed, scannerContext);
return {
valid: true,
data: parsed,
recommendation: {
productId: parsed.pr,
recommenderId: parsed.p,
reason: parsed.r,
issuedAt: parsed.t
}
};
} catch (error) {
console.error('QR validation error:', error);
return { valid: false, error: 'Validation failed' };
}
}
static parseQRPpayload(payload) {
try {
// Parse TSQR format
const parts = payload.split('|');
const data = {};
parts.forEach(part => {
const [key, value] = part.split(':');
if (key && value) {
data[key] = value;
}
});
// Validate required fields
const required = ['v', 'p', 'm', 'pr', 'q', 't', 'sig'];
const missing = required.filter(field => !data[field]);
if (missing.length > 0) {
return null;
}
return data;
} catch (error) {
return null;
}
}
static validateSignature(payload, parsed) {
// In a real implementation, this would verify the signature
// against the user's public key
try {
const payloadWithoutSig = payload.split('|sig:')[0];
const providedSig = parsed.sig;
// Simulate signature validation
const expectedSig = btoa(payloadWithoutSig).substring(0, 44);
return providedSig === expectedSig;
} catch (error) {
return false;
}
}
static recordScan(parsed, context) {
// Record the QR scan for attribution tracking
const scanRecord = {
qrId: parsed.q,
productId: parsed.pr,
recommenderId: parsed.p,
scannerUserId: context.userId || null,
timestamp: new Date().toISOString(),
geolocation: context.geolocation || null,
userAgent: navigator.userAgent,
ip: context.ip || null
};
// In a real implementation, this would save to the database
console.log('QR scan recorded:', scanRecord);
// Store in local storage for demo purposes
const scans = JSON.parse(localStorage.getItem('qrScans') || '[]');
scans.push(scanRecord);
localStorage.setItem('qrScans', JSON.stringify(scans));
}
static getUsageCount(qrId) {
// Get how many times this QR has been used
const scans = JSON.parse(localStorage.getItem('qrScans') || '[]');
return scans.filter(scan => scan.qrId === qrId).length;
}
static getMaxUses(parsed) {
// Different products may have different usage limits
// For now, return a default of 10 uses
return 10;
}
static getMerchantId(productId) {
// In a real implementation, this would lookup the merchant ID
// For demo purposes, return a mock merchant ID
return `merchant_${productId.substring(0, 8)}`;
}
static generateQRId() {
return 'QR' + Date.now().toString(36) + Math.random().toString(36).substring(2, 8);
}
static setupQRValidation() {
// Setup QR scanner validation handlers
const scannerInputs = document.querySelectorAll('input[placeholder*="QR code"]');
scannerInputs.forEach(input => {
input.addEventListener('input', (e) => {
if (e.target.value.trim()) {
this.validateManualQR(e.target.value.trim());
}
});
});
}
static async validateManualQR(payload) {
const result = this.validateQRScan(payload, { manualEntry: true });
if (result.valid) {
this.showValidationSuccess(result.recommendation);
} else {
this.showValidationError(result.error);
}
}
static showValidationSuccess(recommendation) {
// Show success message with recommendation details
const message = `
Valid QR from trusted recommender
Product: ${recommendation.productId}
Reason: ${recommendation.reason}
`;
if (window.OneSearchApp) {
window.OneSearchApp.showNotification('Trust QR validated successfully', 'success');
}
}
static showValidationError(error) {
// Show error message
if (window.OneSearchApp) {
window.OneSearchApp.showNotification(`QR validation failed: ${error}`, 'error');
}
}
static loadTrustNetwork() {
// Load recent recommendations from the trust network
const networkData = [
{
id: 'rec_1',
productId: '1',
recommenderId: 'user_123',
recommenderName: 'Sarah M.',
productTitle: 'Kitchen Storage Solutions',
timestamp: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(),
trustLevel: 'verified',
reason: 'Perfect for small kitchens with excellent build quality'
},
{
id: 'rec_2',
productId: '2',
recommenderId: 'user_456',
recommenderName: 'Mike R.',
productTitle: 'Smart Home Automation',
timestamp: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
trustLevel: 'high',
reason: 'Seamless integration and reliable performance'
},
{
id: 'rec_3',
productId: '3',
recommenderId: 'user_789',
recommenderName: 'Lisa K.',
productTitle: 'Space-Saving Organizer',
timestamp: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(),
trustLevel: 'verified',
reason: 'Transforms cluttered spaces into organized areas'
}
];
this.updateTrustNetworkDisplay(networkData);
}
static updateTrustNetworkDisplay(networkData) {
const container = document.querySelector('#recommendations .space-y-3');
if (!container) return;
// Update the recent recommendations display
networkData.forEach(rec => {
const existingElement = Array.from(container.children).find(el =>
el.textContent.includes(rec.productTitle)
);
if (!existingElement) {
const newElement = this.createRecommendationElement(rec);
container.insertBefore(newElement, container.firstChild);
}
});
}
static createRecommendationElement(recommendation) {
const element = document.createElement('div');
element.className = 'flex items-center space-x-3 p-3 border rounded-lg fade-in';
const timeAgo = this.getTimeAgo(recommendation.timestamp);
const trustColor = recommendation.trustLevel === 'verified' ? 'green' : 'blue';
element.innerHTML = `
<div class="w-8 h-8 bg-${trustColor}-500 rounded-full flex items-center justify-center">
<i data-feather="check" class="h-4 w-4 text-white"></i>
</div>
<div class="flex-1">
<div class="text-sm font-medium">${recommendation.productTitle}</div>
<div class="text-xs text-gray-600">by ${recommendation.recommenderName}${timeAgo}</div>
</div>
<div class="text-xs text-${trustColor}-600 font-medium">${recommendation.trustLevel}</div>
`;
// Replace icons
setTimeout(() => {
if (window.feather) {
window.feather.replace();
}
}, 0);
return element;
}
static getTimeAgo(timestamp) {
const now = new Date();
const time = new Date(timestamp);
const diffInMs = now - time;
const diffInDays = Math.floor(diffInMs / (1000 * 60 * 60 * 24));
if (diffInDays === 0) {
return 'today';
} else if (diffInDays === 1) {
return 'yesterday';
} else {
return `${diffInDays} days ago`;
}
}
static createQRTemplate(productId, productTitle) {
return {
productId,
productTitle,
recommendationReasons: [
'Excellent build quality',
'Great value for money',
'Solved my specific problem',
'Highly recommended by community',
'Verified by multiple users'
],
defaultExpirationDays: 30,
maxUsages: 10
};
}
static exportQRData(qrId) {
// Export QR generation data for auditing
const scans = JSON.parse(localStorage.getItem('qrScans') || '[]');
const qrScans = scans.filter(scan => scan.qrId === qrId);
return {
qrId,
generatedAt: new Date().toISOString(),
totalScans: qrScans.length,
scans: qrScans,
conversions: qrScans.filter(scan => scan.conversion === true).length
};
}
}
// Export for use in main app
if (typeof window !== 'undefined') {
window.QRGenerator = QRGenerator;
}
if (typeof module !== 'undefined' && module.exports) {
module.exports = QRGenerator;
}