Upload 70 files
Browse files- .htaccess +18 -1
- fricuit_animated_receiver.js +75 -60
- fricuit_animated_streamer.js +44 -29
- fricuit_audio_transmitter.js +16 -10
- fricuit_budget_planner.js +11 -8
- fricuit_camera_scanner_pipeline.js +12 -9
- fricuit_contacts_address_book.js +11 -8
- fricuit_country_database.js +14 -9
- fricuit_currency_database.js +14 -8
- fricuit_currency_gold_market.js +11 -7
- fricuit_currency_rates_fetcher.js +13 -7
- fricuit_device_telemetry.js +12 -15
- fricuit_image_qr_extractor.js +180 -46
- fricuit_ledger_ui.js +14 -9
- fricuit_merchant_invoices.js +11 -8
- fricuit_merchant_store.js +11 -8
- fricuit_p2p_receive_controller.js +15 -10
- fricuit_p2p_send_controller.js +1 -1
- fricuit_permissions_manager.js +11 -8
- fricuit_pos_kiosk_terminal.js +11 -8
- fricuit_qr_matrix_generator.js +39 -31
- fricuit_roundup_savings.js +11 -8
- fricuit_security_audit_reporter.js +11 -7
- fricuit_security_biometric.js +11 -8
- fricuit_security_pin.js +11 -8
- fricuit_security_qa.js +11 -8
- fricuit_split_bill_controller.js +11 -8
- fricuit_user_profile.js +11 -9
- fricuit_vision_filters.js +40 -10
- vercel.json +25 -0
.htaccess
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
DirectoryIndex index.html
|
|
|
|
| 2 |
<IfModule mod_rewrite.c>
|
| 3 |
RewriteEngine On
|
| 4 |
RewriteBase /
|
|
@@ -7,10 +8,26 @@ DirectoryIndex index.html
|
|
| 7 |
RewriteCond %{REQUEST_FILENAME} !-d
|
| 8 |
RewriteRule . /index.html [L]
|
| 9 |
</IfModule>
|
|
|
|
| 10 |
<IfModule mod_headers.c>
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
Header set Cache-Control "no-cache, no-store, must-revalidate"
|
| 13 |
</FilesMatch>
|
|
|
|
| 14 |
<FilesMatch "manifest\.json$">
|
| 15 |
Header set Content-Type "application/manifest+json"
|
| 16 |
</FilesMatch>
|
|
|
|
| 1 |
DirectoryIndex index.html
|
| 2 |
+
|
| 3 |
<IfModule mod_rewrite.c>
|
| 4 |
RewriteEngine On
|
| 5 |
RewriteBase /
|
|
|
|
| 8 |
RewriteCond %{REQUEST_FILENAME} !-d
|
| 9 |
RewriteRule . /index.html [L]
|
| 10 |
</IfModule>
|
| 11 |
+
|
| 12 |
<IfModule mod_headers.c>
|
| 13 |
+
# Security Headers against Tampering & Embedding
|
| 14 |
+
Header always set X-Frame-Options "DENY"
|
| 15 |
+
Header always set X-Content-Type-Options "nosniff"
|
| 16 |
+
Header always set X-XSS-Protection "1; mode=block"
|
| 17 |
+
Header always set Referrer-Policy "no-referrer"
|
| 18 |
+
Header always set Permissions-Policy "camera=(self), microphone=(self), geolocation=(self)"
|
| 19 |
+
|
| 20 |
+
# Disable Source Maps to prevent code inspection
|
| 21 |
+
<FilesMatch "\.map$">
|
| 22 |
+
Order allow,deny
|
| 23 |
+
Deny from all
|
| 24 |
+
</FilesMatch>
|
| 25 |
+
|
| 26 |
+
# Cache Control for Service Worker & Manifest
|
| 27 |
+
<FilesMatch "(r8x4m9q2v7z3c6n5t0y8p4w9h2k6w\.js|manifest\.json)$">
|
| 28 |
Header set Cache-Control "no-cache, no-store, must-revalidate"
|
| 29 |
</FilesMatch>
|
| 30 |
+
|
| 31 |
<FilesMatch "manifest\.json$">
|
| 32 |
Header set Content-Type "application/manifest+json"
|
| 33 |
</FilesMatch>
|
fricuit_animated_receiver.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Animated Multi-Frame QR Stream Receiver & Assembler
|
| 3 |
* File: fricuit_animated_receiver.js
|
| 4 |
-
* Responsibility: Ingesting rapid stream packets, validating CRC32 chunk integrity, tracking frame reception progress, and reassembling multi-part transfers.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
@@ -45,74 +45,82 @@
|
|
| 45 |
if (!frameText || typeof frameText !== 'string') return false;
|
| 46 |
if (!frameText.startsWith("FSTREAM:")) return false;
|
| 47 |
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
|
|
|
| 55 |
|
| 56 |
-
|
| 57 |
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
// New session detected
|
| 64 |
-
if (this.activeSessionId !== sessionId) {
|
| 65 |
-
this.activeSessionId = sessionId;
|
| 66 |
-
this.totalChunks = total;
|
| 67 |
-
this.collectedChunks.clear();
|
| 68 |
-
this.isCompleted = false;
|
| 69 |
-
}
|
| 70 |
-
|
| 71 |
-
this.lastReceivedTime = Date.now();
|
| 72 |
-
|
| 73 |
-
if (!this.collectedChunks.has(seq)) {
|
| 74 |
-
this.collectedChunks.set(seq, chunkData);
|
| 75 |
-
const count = this.collectedChunks.size;
|
| 76 |
-
const percent = Math.round((count / this.totalChunks) * 100);
|
| 77 |
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
missingCount: this.totalChunks - count
|
| 85 |
-
});
|
| 86 |
}
|
| 87 |
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
}
|
| 92 |
-
}
|
| 93 |
|
| 94 |
-
|
|
|
|
|
|
|
|
|
|
| 95 |
}
|
| 96 |
|
| 97 |
/**
|
| 98 |
* Reassembles collected chunks and decompresses payload.
|
| 99 |
*/
|
| 100 |
async assembleAndFinish() {
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
|
|
|
| 105 |
|
| 106 |
-
|
| 107 |
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
|
| 115 |
-
|
|
|
|
|
|
|
|
|
|
| 116 |
}
|
| 117 |
|
| 118 |
/**
|
|
@@ -135,13 +143,20 @@
|
|
| 135 |
decompressTextPayload: Compression.decompressTextPayload
|
| 136 |
};
|
| 137 |
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
global.FricuitAnimatedReceiver = FricuitQRReceiver;
|
| 146 |
global.FricuitAnimatedStream = FricuitAnimatedStream;
|
| 147 |
}
|
|
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Animated Multi-Frame QR Stream Receiver & Assembler
|
| 3 |
* File: fricuit_animated_receiver.js
|
| 4 |
+
* Responsibility: Ingesting rapid stream packets, validating CRC32 chunk integrity, tracking frame reception progress, and reassembling multi-part transfers with zero console logging.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
|
|
| 45 |
if (!frameText || typeof frameText !== 'string') return false;
|
| 46 |
if (!frameText.startsWith("FSTREAM:")) return false;
|
| 47 |
|
| 48 |
+
try {
|
| 49 |
+
const parts = frameText.split(':');
|
| 50 |
+
const isV2 = parts[1] === 'v2';
|
| 51 |
+
const sessionId = isV2 ? parts[2] : parts[1];
|
| 52 |
+
const seq = parseInt(isV2 ? parts[3] : parts[2], 10);
|
| 53 |
+
const total = parseInt(isV2 ? parts[4] : parts[3], 10);
|
| 54 |
+
const crc = isV2 ? parts[5] : null;
|
| 55 |
+
const chunkData = isV2 ? parts.slice(6).join(':') : parts.slice(4).join(':');
|
| 56 |
|
| 57 |
+
if (isNaN(seq) || isNaN(total) || !sessionId || !chunkData) return false;
|
| 58 |
|
| 59 |
+
// Validate CRC32 checksum for v2 frames
|
| 60 |
+
if (crc && AnimatedStreamer.computeCRC32(chunkData) !== crc) {
|
| 61 |
+
return false;
|
| 62 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
+
// New session detected
|
| 65 |
+
if (this.activeSessionId !== sessionId) {
|
| 66 |
+
this.activeSessionId = sessionId;
|
| 67 |
+
this.totalChunks = total;
|
| 68 |
+
this.collectedChunks.clear();
|
| 69 |
+
this.isCompleted = false;
|
|
|
|
|
|
|
| 70 |
}
|
| 71 |
|
| 72 |
+
this.lastReceivedTime = Date.now();
|
| 73 |
+
|
| 74 |
+
if (!this.collectedChunks.has(seq)) {
|
| 75 |
+
this.collectedChunks.set(seq, chunkData);
|
| 76 |
+
const count = this.collectedChunks.size;
|
| 77 |
+
const percent = Math.round((count / this.totalChunks) * 100);
|
| 78 |
+
|
| 79 |
+
if (typeof this.onProgress === 'function') {
|
| 80 |
+
this.onProgress({
|
| 81 |
+
received: count,
|
| 82 |
+
total: this.totalChunks,
|
| 83 |
+
percent: percent,
|
| 84 |
+
sessionId: this.activeSessionId,
|
| 85 |
+
missingCount: this.totalChunks - count
|
| 86 |
+
});
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
if (count === this.totalChunks && !this.isCompleted) {
|
| 90 |
+
this.isCompleted = true;
|
| 91 |
+
this.assembleAndFinish();
|
| 92 |
+
}
|
| 93 |
}
|
|
|
|
| 94 |
|
| 95 |
+
return true;
|
| 96 |
+
} catch (e) {
|
| 97 |
+
return false;
|
| 98 |
+
}
|
| 99 |
}
|
| 100 |
|
| 101 |
/**
|
| 102 |
* Reassembles collected chunks and decompresses payload.
|
| 103 |
*/
|
| 104 |
async assembleAndFinish() {
|
| 105 |
+
try {
|
| 106 |
+
let combinedPacked = "";
|
| 107 |
+
for (let i = 0; i < this.totalChunks; i++) {
|
| 108 |
+
combinedPacked += (this.collectedChunks.get(i) || "");
|
| 109 |
+
}
|
| 110 |
|
| 111 |
+
const fullDecodedString = await Compression.decompressTextPayload(combinedPacked);
|
| 112 |
|
| 113 |
+
if (typeof this.onComplete === 'function') {
|
| 114 |
+
this.onComplete(fullDecodedString, {
|
| 115 |
+
sessionId: this.activeSessionId,
|
| 116 |
+
totalChunks: this.totalChunks
|
| 117 |
+
});
|
| 118 |
+
}
|
| 119 |
|
| 120 |
+
this.reset();
|
| 121 |
+
} catch (err) {
|
| 122 |
+
this.reset();
|
| 123 |
+
}
|
| 124 |
}
|
| 125 |
|
| 126 |
/**
|
|
|
|
| 143 |
decompressTextPayload: Compression.decompressTextPayload
|
| 144 |
};
|
| 145 |
|
| 146 |
+
Object.freeze(FricuitAnimatedStream);
|
| 147 |
+
|
| 148 |
+
try {
|
| 149 |
+
Object.defineProperty(global, 'FricuitAnimatedReceiver', {
|
| 150 |
+
value: FricuitQRReceiver,
|
| 151 |
+
writable: false,
|
| 152 |
+
configurable: false
|
| 153 |
+
});
|
| 154 |
+
Object.defineProperty(global, 'FricuitAnimatedStream', {
|
| 155 |
+
value: FricuitAnimatedStream,
|
| 156 |
+
writable: false,
|
| 157 |
+
configurable: false
|
| 158 |
+
});
|
| 159 |
+
} catch (e) {
|
| 160 |
global.FricuitAnimatedReceiver = FricuitQRReceiver;
|
| 161 |
global.FricuitAnimatedStream = FricuitAnimatedStream;
|
| 162 |
}
|
fricuit_animated_streamer.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Animated Multi-Frame QR Stream Generator (FSTREAM:v2)
|
| 3 |
* File: fricuit_animated_streamer.js
|
| 4 |
-
* Responsibility: Chunking large payloads (>1200 chars), Deflate compression, CRC32 packet integrity hashing, and rapid FPS video-rate QR stream broadcasting.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
@@ -24,6 +24,8 @@
|
|
| 24 |
return table;
|
| 25 |
})();
|
| 26 |
|
|
|
|
|
|
|
| 27 |
/**
|
| 28 |
* Computes CRC32 checksum for payload chunk validation.
|
| 29 |
* @param {string} str
|
|
@@ -62,29 +64,38 @@
|
|
| 62 |
*/
|
| 63 |
async prepareStream(payloadString) {
|
| 64 |
this.stopStream();
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
|
|
|
| 80 |
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
}
|
| 89 |
|
| 90 |
/**
|
|
@@ -139,17 +150,21 @@
|
|
| 139 |
}
|
| 140 |
}
|
| 141 |
|
| 142 |
-
// Export module to global scope
|
| 143 |
const AnimatedStreamer = {
|
| 144 |
computeCRC32,
|
| 145 |
Streamer: FricuitQRStreamer
|
| 146 |
};
|
| 147 |
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
global.FricuitAnimatedStreamer = AnimatedStreamer;
|
| 154 |
}
|
| 155 |
|
|
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Animated Multi-Frame QR Stream Generator (FSTREAM:v2)
|
| 3 |
* File: fricuit_animated_streamer.js
|
| 4 |
+
* Responsibility: Chunking large payloads (>1200 chars), Deflate compression, CRC32 packet integrity hashing, and rapid FPS video-rate QR stream broadcasting with zero console logging.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
|
|
| 24 |
return table;
|
| 25 |
})();
|
| 26 |
|
| 27 |
+
Object.freeze(CRC32_TABLE);
|
| 28 |
+
|
| 29 |
/**
|
| 30 |
* Computes CRC32 checksum for payload chunk validation.
|
| 31 |
* @param {string} str
|
|
|
|
| 64 |
*/
|
| 65 |
async prepareStream(payloadString) {
|
| 66 |
this.stopStream();
|
| 67 |
+
try {
|
| 68 |
+
const compressed = await Compression.compressTextPayload(payloadString);
|
| 69 |
+
this.activeSessionId = "FS" + Math.random().toString(36).substring(2, 8).toUpperCase();
|
| 70 |
+
this.chunks = [];
|
| 71 |
+
|
| 72 |
+
const totalLen = compressed.length;
|
| 73 |
+
const numChunks = Math.ceil(totalLen / this.chunkSize);
|
| 74 |
+
|
| 75 |
+
for (let i = 0; i < numChunks; i++) {
|
| 76 |
+
const start = i * this.chunkSize;
|
| 77 |
+
const end = Math.min(start + this.chunkSize, totalLen);
|
| 78 |
+
const chunkData = compressed.substring(start, end);
|
| 79 |
+
const crc = computeCRC32(chunkData);
|
| 80 |
+
const packet = `${STREAM_HEADER_PREFIX}:${this.activeSessionId}:${i}:${numChunks}:${crc}:${chunkData}`;
|
| 81 |
+
this.chunks.push(packet);
|
| 82 |
+
}
|
| 83 |
|
| 84 |
+
this.currentIndex = 0;
|
| 85 |
+
return {
|
| 86 |
+
sessionId: this.activeSessionId,
|
| 87 |
+
totalChunks: this.chunks.length,
|
| 88 |
+
originalSize: payloadString.length,
|
| 89 |
+
compressedSize: compressed.length
|
| 90 |
+
};
|
| 91 |
+
} catch (e) {
|
| 92 |
+
return {
|
| 93 |
+
sessionId: "FS_ERR",
|
| 94 |
+
totalChunks: 0,
|
| 95 |
+
originalSize: payloadString.length,
|
| 96 |
+
compressedSize: 0
|
| 97 |
+
};
|
| 98 |
+
}
|
| 99 |
}
|
| 100 |
|
| 101 |
/**
|
|
|
|
| 150 |
}
|
| 151 |
}
|
| 152 |
|
| 153 |
+
// Export module to global scope with immutable descriptor
|
| 154 |
const AnimatedStreamer = {
|
| 155 |
computeCRC32,
|
| 156 |
Streamer: FricuitQRStreamer
|
| 157 |
};
|
| 158 |
|
| 159 |
+
Object.freeze(AnimatedStreamer);
|
| 160 |
+
|
| 161 |
+
try {
|
| 162 |
+
Object.defineProperty(global, 'FricuitAnimatedStreamer', {
|
| 163 |
+
value: AnimatedStreamer,
|
| 164 |
+
writable: false,
|
| 165 |
+
configurable: false
|
| 166 |
+
});
|
| 167 |
+
} catch (e) {
|
| 168 |
global.FricuitAnimatedStreamer = AnimatedStreamer;
|
| 169 |
}
|
| 170 |
|
fricuit_audio_transmitter.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Acoustic Voucher & Chirp Audio Transmitter
|
| 3 |
* File: fricuit_audio_transmitter.js
|
| 4 |
-
* Responsibility: Frequency-Shift Keying (FSK) modulation, audio buffer synthesis, embedding token metadata into WAV container, and speaker broadcasting.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
@@ -27,8 +27,12 @@
|
|
| 27 |
}
|
| 28 |
};
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
const WavEncoder = global.FricuitAudioWavEncoder || {
|
| 31 |
-
createWavBlobFromAudioSamples: (
|
| 32 |
triggerWavDownload: () => false
|
| 33 |
};
|
| 34 |
|
|
@@ -69,7 +73,6 @@
|
|
| 69 |
const cleanStr = (payloadString || '').trim();
|
| 70 |
const symbols = [];
|
| 71 |
|
| 72 |
-
// Add Preamble
|
| 73 |
this.cfg.PREAMBLE.forEach(f => symbols.push(f));
|
| 74 |
|
| 75 |
let checksum = 0;
|
|
@@ -82,7 +85,6 @@
|
|
| 82 |
symbols.push(this.cfg.BASE_FREQ + (lowNibble * this.cfg.STEP_FREQ));
|
| 83 |
}
|
| 84 |
|
| 85 |
-
// Append checksum symbol and postamble
|
| 86 |
symbols.push(this.cfg.BASE_FREQ + (checksum * this.cfg.STEP_FREQ));
|
| 87 |
this.cfg.POSTAMBLE.forEach(f => symbols.push(f));
|
| 88 |
|
|
@@ -168,18 +170,22 @@
|
|
| 168 |
}
|
| 169 |
}
|
| 170 |
|
| 171 |
-
// Export module to global scope
|
| 172 |
const AudioTransmitter = {
|
| 173 |
CHIRP_CONFIG,
|
| 174 |
Transmitter: FricuitAudioTransmitter,
|
| 175 |
getOrCreateAudioContext
|
| 176 |
};
|
| 177 |
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
global.FricuitAudioTransmitter = AudioTransmitter;
|
| 184 |
}
|
| 185 |
|
|
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Acoustic Voucher & Chirp Audio Transmitter
|
| 3 |
* File: fricuit_audio_transmitter.js
|
| 4 |
+
* Responsibility: Frequency-Shift Keying (FSK) modulation, audio buffer synthesis, embedding token metadata into WAV container, and speaker broadcasting with zero console logging.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
|
|
| 27 |
}
|
| 28 |
};
|
| 29 |
|
| 30 |
+
Object.freeze(CHIRP_CONFIG.AUDIBLE);
|
| 31 |
+
Object.freeze(CHIRP_CONFIG.ULTRASONIC);
|
| 32 |
+
Object.freeze(CHIRP_CONFIG);
|
| 33 |
+
|
| 34 |
const WavEncoder = global.FricuitAudioWavEncoder || {
|
| 35 |
+
createWavBlobFromAudioSamples: () => new Blob([], { type: 'audio/wav' }),
|
| 36 |
triggerWavDownload: () => false
|
| 37 |
};
|
| 38 |
|
|
|
|
| 73 |
const cleanStr = (payloadString || '').trim();
|
| 74 |
const symbols = [];
|
| 75 |
|
|
|
|
| 76 |
this.cfg.PREAMBLE.forEach(f => symbols.push(f));
|
| 77 |
|
| 78 |
let checksum = 0;
|
|
|
|
| 85 |
symbols.push(this.cfg.BASE_FREQ + (lowNibble * this.cfg.STEP_FREQ));
|
| 86 |
}
|
| 87 |
|
|
|
|
| 88 |
symbols.push(this.cfg.BASE_FREQ + (checksum * this.cfg.STEP_FREQ));
|
| 89 |
this.cfg.POSTAMBLE.forEach(f => symbols.push(f));
|
| 90 |
|
|
|
|
| 170 |
}
|
| 171 |
}
|
| 172 |
|
| 173 |
+
// Export module to global scope with immutable descriptor
|
| 174 |
const AudioTransmitter = {
|
| 175 |
CHIRP_CONFIG,
|
| 176 |
Transmitter: FricuitAudioTransmitter,
|
| 177 |
getOrCreateAudioContext
|
| 178 |
};
|
| 179 |
|
| 180 |
+
Object.freeze(AudioTransmitter);
|
| 181 |
+
|
| 182 |
+
try {
|
| 183 |
+
Object.defineProperty(global, 'FricuitAudioTransmitter', {
|
| 184 |
+
value: AudioTransmitter,
|
| 185 |
+
writable: false,
|
| 186 |
+
configurable: false
|
| 187 |
+
});
|
| 188 |
+
} catch (e) {
|
| 189 |
global.FricuitAudioTransmitter = AudioTransmitter;
|
| 190 |
}
|
| 191 |
|
fricuit_budget_planner.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Monthly Budget Planner & Spending Limits Guard
|
| 3 |
* File: fricuit_budget_planner.js
|
| 4 |
-
* Responsibility: Dynamic DOM injection of Budget Planner pane, defining currency-specific monthly spending caps, auditing real-time expense ratios, rendering progress bars, and generating threshold warning alerts.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
@@ -151,14 +151,13 @@
|
|
| 151 |
renderBudgetsList();
|
| 152 |
}
|
| 153 |
|
| 154 |
-
// Auto-inject budget planner DOM on startup
|
| 155 |
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| 156 |
ensureBudgetPlannerPaneDOM();
|
| 157 |
} else {
|
| 158 |
document.addEventListener('DOMContentLoaded', ensureBudgetPlannerPaneDOM);
|
| 159 |
}
|
| 160 |
|
| 161 |
-
// Export module to global scope
|
| 162 |
const BudgetPlanner = {
|
| 163 |
renderBudgetsList,
|
| 164 |
saveBudgetLimit,
|
|
@@ -166,11 +165,15 @@
|
|
| 166 |
ensureBudgetPlannerPaneDOM
|
| 167 |
};
|
| 168 |
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
global.FricuitBudgetPlanner = BudgetPlanner;
|
| 175 |
}
|
| 176 |
|
|
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Monthly Budget Planner & Spending Limits Guard
|
| 3 |
* File: fricuit_budget_planner.js
|
| 4 |
+
* Responsibility: Dynamic DOM injection of Budget Planner pane, defining currency-specific monthly spending caps, auditing real-time expense ratios, rendering progress bars, and generating threshold warning alerts with zero console logging.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
|
|
| 151 |
renderBudgetsList();
|
| 152 |
}
|
| 153 |
|
|
|
|
| 154 |
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| 155 |
ensureBudgetPlannerPaneDOM();
|
| 156 |
} else {
|
| 157 |
document.addEventListener('DOMContentLoaded', ensureBudgetPlannerPaneDOM);
|
| 158 |
}
|
| 159 |
|
| 160 |
+
// Export module to global scope with immutable descriptor
|
| 161 |
const BudgetPlanner = {
|
| 162 |
renderBudgetsList,
|
| 163 |
saveBudgetLimit,
|
|
|
|
| 165 |
ensureBudgetPlannerPaneDOM
|
| 166 |
};
|
| 167 |
|
| 168 |
+
Object.freeze(BudgetPlanner);
|
| 169 |
+
|
| 170 |
+
try {
|
| 171 |
+
Object.defineProperty(global, 'FricuitBudgetPlanner', {
|
| 172 |
+
value: BudgetPlanner,
|
| 173 |
+
writable: false,
|
| 174 |
+
configurable: false
|
| 175 |
+
});
|
| 176 |
+
} catch (e) {
|
| 177 |
global.FricuitBudgetPlanner = BudgetPlanner;
|
| 178 |
}
|
| 179 |
|
fricuit_camera_scanner_pipeline.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - WebRTC Camera Scanner & Viewfinder Pipeline
|
| 3 |
* File: fricuit_camera_scanner_pipeline.js
|
| 4 |
-
* Responsibility: Camera constraints negotiation, torch/flashlight control, front/back lens switching, OffscreenCanvas/Worker frame transfer, and live QR detection loop.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
@@ -46,7 +46,6 @@
|
|
| 46 |
*/
|
| 47 |
initWorker() {
|
| 48 |
try {
|
| 49 |
-
// Workers are blocked on file:/// protocol by browser security policies
|
| 50 |
const isSupportedProtocol = typeof location !== 'undefined' &&
|
| 51 |
(location.protocol.startsWith('http') || location.protocol.includes('extension'));
|
| 52 |
|
|
@@ -126,7 +125,7 @@
|
|
| 126 |
}
|
| 127 |
|
| 128 |
if (!activeStream) {
|
| 129 |
-
throw (lastError || new Error("
|
| 130 |
}
|
| 131 |
|
| 132 |
this.stream = activeStream;
|
|
@@ -313,12 +312,16 @@
|
|
| 313 |
}
|
| 314 |
}
|
| 315 |
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 322 |
global.FricuitCameraPipeline = FricuitCameraPipeline;
|
| 323 |
}
|
| 324 |
|
|
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - WebRTC Camera Scanner & Viewfinder Pipeline
|
| 3 |
* File: fricuit_camera_scanner_pipeline.js
|
| 4 |
+
* Responsibility: Camera constraints negotiation, torch/flashlight control, front/back lens switching, OffscreenCanvas/Worker frame transfer, and live QR detection loop with zero console logging.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
|
|
| 46 |
*/
|
| 47 |
initWorker() {
|
| 48 |
try {
|
|
|
|
| 49 |
const isSupportedProtocol = typeof location !== 'undefined' &&
|
| 50 |
(location.protocol.startsWith('http') || location.protocol.includes('extension'));
|
| 51 |
|
|
|
|
| 125 |
}
|
| 126 |
|
| 127 |
if (!activeStream) {
|
| 128 |
+
throw (lastError || new Error("Camera stream access unavailable"));
|
| 129 |
}
|
| 130 |
|
| 131 |
this.stream = activeStream;
|
|
|
|
| 312 |
}
|
| 313 |
}
|
| 314 |
|
| 315 |
+
Object.freeze(FricuitCameraPipeline.prototype);
|
| 316 |
+
Object.freeze(FricuitCameraPipeline);
|
| 317 |
+
|
| 318 |
+
try {
|
| 319 |
+
Object.defineProperty(global, 'FricuitCameraPipeline', {
|
| 320 |
+
value: FricuitCameraPipeline,
|
| 321 |
+
writable: false,
|
| 322 |
+
configurable: false
|
| 323 |
+
});
|
| 324 |
+
} catch (e) {
|
| 325 |
global.FricuitCameraPipeline = FricuitCameraPipeline;
|
| 326 |
}
|
| 327 |
|
fricuit_contacts_address_book.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Address Book & Verified Contacts Manager
|
| 3 |
* File: fricuit_contacts_address_book.js
|
| 4 |
-
* Responsibility: Dynamic DOM injection of Address Book pane and Contact modals, saving 128-character account IDs with human-readable aliases, contact search, editing, modal pickers, and bulk multi-selection deletion.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
@@ -495,14 +495,13 @@
|
|
| 495 |
});
|
| 496 |
}
|
| 497 |
|
| 498 |
-
// Auto-inject address book DOM on startup
|
| 499 |
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| 500 |
ensureAddressBookDOM();
|
| 501 |
} else {
|
| 502 |
document.addEventListener('DOMContentLoaded', ensureAddressBookDOM);
|
| 503 |
}
|
| 504 |
|
| 505 |
-
// Export module to global scope
|
| 506 |
const ContactsManager = {
|
| 507 |
renderAddressBookList,
|
| 508 |
saveNewContact,
|
|
@@ -518,11 +517,15 @@
|
|
| 518 |
ensureAddressBookDOM
|
| 519 |
};
|
| 520 |
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 526 |
global.FricuitContactsManager = ContactsManager;
|
| 527 |
}
|
| 528 |
|
|
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Address Book & Verified Contacts Manager
|
| 3 |
* File: fricuit_contacts_address_book.js
|
| 4 |
+
* Responsibility: Dynamic DOM injection of Address Book pane and Contact modals, saving 128-character account IDs with human-readable aliases, contact search, editing, modal pickers, and bulk multi-selection deletion with zero console logging.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
|
|
| 495 |
});
|
| 496 |
}
|
| 497 |
|
|
|
|
| 498 |
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| 499 |
ensureAddressBookDOM();
|
| 500 |
} else {
|
| 501 |
document.addEventListener('DOMContentLoaded', ensureAddressBookDOM);
|
| 502 |
}
|
| 503 |
|
| 504 |
+
// Export module to global scope with immutable descriptor
|
| 505 |
const ContactsManager = {
|
| 506 |
renderAddressBookList,
|
| 507 |
saveNewContact,
|
|
|
|
| 517 |
ensureAddressBookDOM
|
| 518 |
};
|
| 519 |
|
| 520 |
+
Object.freeze(ContactsManager);
|
| 521 |
+
|
| 522 |
+
try {
|
| 523 |
+
Object.defineProperty(global, 'FricuitContactsManager', {
|
| 524 |
+
value: ContactsManager,
|
| 525 |
+
writable: false,
|
| 526 |
+
configurable: false
|
| 527 |
+
});
|
| 528 |
+
} catch (e) {
|
| 529 |
global.FricuitContactsManager = ContactsManager;
|
| 530 |
}
|
| 531 |
|
fricuit_country_database.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Standalone Global Country & Nationality Registry
|
| 3 |
* File: fricuit_country_database.js
|
| 4 |
-
* Responsibility: Complete, normalized registry of all 249 recognized countries and sovereign territories (ISO 3166-1 alpha-2, dial codes, flags, continents) with zero
|
| 5 |
* Pure Offline - 100% Client-Side.
|
| 6 |
*/
|
| 7 |
|
|
@@ -257,15 +257,16 @@
|
|
| 257 |
{ name: "Zimbabwe", code: "ZW", dialCode: "+263", flag: "🇿🇼", continent: "Africa" }
|
| 258 |
];
|
| 259 |
|
| 260 |
-
// Alphabetical Sorting by Country Name
|
| 261 |
COUNTRY_DATABASE.sort((a, b) => a.name.localeCompare(b.name, 'en'));
|
| 262 |
|
| 263 |
-
// Fast Indexed Map by ISO 3166-1 alpha-2 Code
|
| 264 |
const COUNTRY_MAP_BY_CODE = {};
|
| 265 |
COUNTRY_DATABASE.forEach(item => {
|
| 266 |
COUNTRY_MAP_BY_CODE[item.code.toUpperCase()] = item;
|
| 267 |
});
|
| 268 |
|
|
|
|
|
|
|
|
|
|
| 269 |
/**
|
| 270 |
* Retrieves full metadata for a given country code.
|
| 271 |
* @param {string} isoCode - 2-character ISO code (e.g., "EG", "US")
|
|
@@ -299,7 +300,7 @@
|
|
| 299 |
});
|
| 300 |
}
|
| 301 |
|
| 302 |
-
// Export module to global scope
|
| 303 |
const CountryRegistry = {
|
| 304 |
COUNTRIES: COUNTRY_DATABASE,
|
| 305 |
COUNTRY_MAP: COUNTRY_MAP_BY_CODE,
|
|
@@ -308,11 +309,15 @@
|
|
| 308 |
DEFAULT_COUNTRY: COUNTRY_MAP_BY_CODE["EG"]
|
| 309 |
};
|
| 310 |
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 316 |
global.FricuitCountryDatabase = CountryRegistry;
|
| 317 |
}
|
| 318 |
|
|
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Standalone Global Country & Nationality Registry
|
| 3 |
* File: fricuit_country_database.js
|
| 4 |
+
* Responsibility: Complete, normalized registry of all 249 recognized countries and sovereign territories (ISO 3166-1 alpha-2, dial codes, flags, continents) with zero console logging.
|
| 5 |
* Pure Offline - 100% Client-Side.
|
| 6 |
*/
|
| 7 |
|
|
|
|
| 257 |
{ name: "Zimbabwe", code: "ZW", dialCode: "+263", flag: "🇿🇼", continent: "Africa" }
|
| 258 |
];
|
| 259 |
|
|
|
|
| 260 |
COUNTRY_DATABASE.sort((a, b) => a.name.localeCompare(b.name, 'en'));
|
| 261 |
|
|
|
|
| 262 |
const COUNTRY_MAP_BY_CODE = {};
|
| 263 |
COUNTRY_DATABASE.forEach(item => {
|
| 264 |
COUNTRY_MAP_BY_CODE[item.code.toUpperCase()] = item;
|
| 265 |
});
|
| 266 |
|
| 267 |
+
Object.freeze(COUNTRY_DATABASE);
|
| 268 |
+
Object.freeze(COUNTRY_MAP_BY_CODE);
|
| 269 |
+
|
| 270 |
/**
|
| 271 |
* Retrieves full metadata for a given country code.
|
| 272 |
* @param {string} isoCode - 2-character ISO code (e.g., "EG", "US")
|
|
|
|
| 300 |
});
|
| 301 |
}
|
| 302 |
|
| 303 |
+
// Export module to global scope with immutable descriptor
|
| 304 |
const CountryRegistry = {
|
| 305 |
COUNTRIES: COUNTRY_DATABASE,
|
| 306 |
COUNTRY_MAP: COUNTRY_MAP_BY_CODE,
|
|
|
|
| 309 |
DEFAULT_COUNTRY: COUNTRY_MAP_BY_CODE["EG"]
|
| 310 |
};
|
| 311 |
|
| 312 |
+
Object.freeze(CountryRegistry);
|
| 313 |
+
|
| 314 |
+
try {
|
| 315 |
+
Object.defineProperty(global, 'FricuitCountryDatabase', {
|
| 316 |
+
value: CountryRegistry,
|
| 317 |
+
writable: false,
|
| 318 |
+
configurable: false
|
| 319 |
+
});
|
| 320 |
+
} catch (e) {
|
| 321 |
global.FricuitCountryDatabase = CountryRegistry;
|
| 322 |
}
|
| 323 |
|
fricuit_currency_database.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Global 140+ Currencies Database & Matrix
|
| 3 |
* File: fricuit_currency_database.js
|
| 4 |
-
* Responsibility: Complete registry of all official world currencies, regional currency unions, symbols, classifications, and metadata.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
@@ -155,7 +155,6 @@
|
|
| 155 |
{ code: "FCC", name: "Fricuit Decentralized Currency", continent: "Global (Decentralized)", region: "Fricuit Economic Ecosystem", country: "Fricuit Digital Network", symbol: "FCC", isInternational: true, isShared: true, sharedZoneName: "Fricuit Global Decentralized Network" }
|
| 156 |
];
|
| 157 |
|
| 158 |
-
// Alphabetical sort by English name
|
| 159 |
CURRENCY_DATABASE.sort((a, b) => a.name.localeCompare(b.name, 'en'));
|
| 160 |
|
| 161 |
const CURRENCY_MAP = {};
|
|
@@ -163,6 +162,9 @@
|
|
| 163 |
CURRENCY_MAP[c.code] = c;
|
| 164 |
});
|
| 165 |
|
|
|
|
|
|
|
|
|
|
| 166 |
/**
|
| 167 |
* Retrieves metadata for a specific currency code.
|
| 168 |
* @param {string} code
|
|
@@ -182,18 +184,22 @@
|
|
| 182 |
};
|
| 183 |
}
|
| 184 |
|
| 185 |
-
// Export module to global scope
|
| 186 |
const CurrencyDatabase = {
|
| 187 |
CURRENCIES: CURRENCY_DATABASE,
|
| 188 |
CURRENCY_MAP: CURRENCY_MAP,
|
| 189 |
getCurrencyMeta
|
| 190 |
};
|
| 191 |
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
global.FricuitCurrencyDatabase = CurrencyDatabase;
|
| 198 |
}
|
| 199 |
|
|
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Global 140+ Currencies Database & Matrix
|
| 3 |
* File: fricuit_currency_database.js
|
| 4 |
+
* Responsibility: Complete registry of all official world currencies, regional currency unions, symbols, classifications, and metadata with zero console logging.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
|
|
| 155 |
{ code: "FCC", name: "Fricuit Decentralized Currency", continent: "Global (Decentralized)", region: "Fricuit Economic Ecosystem", country: "Fricuit Digital Network", symbol: "FCC", isInternational: true, isShared: true, sharedZoneName: "Fricuit Global Decentralized Network" }
|
| 156 |
];
|
| 157 |
|
|
|
|
| 158 |
CURRENCY_DATABASE.sort((a, b) => a.name.localeCompare(b.name, 'en'));
|
| 159 |
|
| 160 |
const CURRENCY_MAP = {};
|
|
|
|
| 162 |
CURRENCY_MAP[c.code] = c;
|
| 163 |
});
|
| 164 |
|
| 165 |
+
Object.freeze(CURRENCY_DATABASE);
|
| 166 |
+
Object.freeze(CURRENCY_MAP);
|
| 167 |
+
|
| 168 |
/**
|
| 169 |
* Retrieves metadata for a specific currency code.
|
| 170 |
* @param {string} code
|
|
|
|
| 184 |
};
|
| 185 |
}
|
| 186 |
|
| 187 |
+
// Export module to global scope with immutable descriptor
|
| 188 |
const CurrencyDatabase = {
|
| 189 |
CURRENCIES: CURRENCY_DATABASE,
|
| 190 |
CURRENCY_MAP: CURRENCY_MAP,
|
| 191 |
getCurrencyMeta
|
| 192 |
};
|
| 193 |
|
| 194 |
+
Object.freeze(CurrencyDatabase);
|
| 195 |
+
|
| 196 |
+
try {
|
| 197 |
+
Object.defineProperty(global, 'FricuitCurrencyDatabase', {
|
| 198 |
+
value: CurrencyDatabase,
|
| 199 |
+
writable: false,
|
| 200 |
+
configurable: false
|
| 201 |
+
});
|
| 202 |
+
} catch (e) {
|
| 203 |
global.FricuitCurrencyDatabase = CurrencyDatabase;
|
| 204 |
}
|
| 205 |
|
fricuit_currency_gold_market.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Implied Gold Index & FX Market Quotes Engine
|
| 3 |
* File: fricuit_currency_gold_market.js
|
| 4 |
-
* Responsibility: Computing implied international gold ounce, 24K and 21K gram pricing per currency, bid/ask spread margins, and liquidity quotes.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
@@ -88,7 +88,7 @@
|
|
| 88 |
return MarketState.goldOunceUSD * rate;
|
| 89 |
}
|
| 90 |
|
| 91 |
-
// Export module to global scope
|
| 92 |
const GoldMarket = {
|
| 93 |
MarketState,
|
| 94 |
calculateMarketQuotes,
|
|
@@ -96,11 +96,15 @@
|
|
| 96 |
getGoldOuncePrice
|
| 97 |
};
|
| 98 |
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
global.FricuitCurrencyGoldMarket = GoldMarket;
|
| 105 |
}
|
| 106 |
|
|
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Implied Gold Index & FX Market Quotes Engine
|
| 3 |
* File: fricuit_currency_gold_market.js
|
| 4 |
+
* Responsibility: Computing implied international gold ounce, 24K and 21K gram pricing per currency, bid/ask spread margins, and liquidity quotes with zero console logging.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
|
|
| 88 |
return MarketState.goldOunceUSD * rate;
|
| 89 |
}
|
| 90 |
|
| 91 |
+
// Export module to global scope with immutable descriptor
|
| 92 |
const GoldMarket = {
|
| 93 |
MarketState,
|
| 94 |
calculateMarketQuotes,
|
|
|
|
| 96 |
getGoldOuncePrice
|
| 97 |
};
|
| 98 |
|
| 99 |
+
Object.freeze(GoldMarket);
|
| 100 |
+
|
| 101 |
+
try {
|
| 102 |
+
Object.defineProperty(global, 'FricuitCurrencyGoldMarket', {
|
| 103 |
+
value: GoldMarket,
|
| 104 |
+
writable: false,
|
| 105 |
+
configurable: false
|
| 106 |
+
});
|
| 107 |
+
} catch (e) {
|
| 108 |
global.FricuitCurrencyGoldMarket = GoldMarket;
|
| 109 |
}
|
| 110 |
|
fricuit_currency_rates_fetcher.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Currency Rates Fetcher & Offline Matrix Engine
|
| 3 |
* File: fricuit_currency_rates_fetcher.js
|
| 4 |
-
* Responsibility: Multi-endpoint real-time currency exchange rates fetcher, rate validation, and offline emergency static matrix synchronization.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
@@ -20,6 +20,8 @@
|
|
| 20 |
});
|
| 21 |
}
|
| 22 |
|
|
|
|
|
|
|
| 23 |
const RatesState = {
|
| 24 |
liveRates: { ...ZERO_RATES_MATRIX },
|
| 25 |
isRatesLoaded: false,
|
|
@@ -128,7 +130,7 @@
|
|
| 128 |
return RatesState.liveRates || ZERO_RATES_MATRIX;
|
| 129 |
}
|
| 130 |
|
| 131 |
-
// Export module to global scope
|
| 132 |
const CurrencyRatesFetcher = {
|
| 133 |
RatesState,
|
| 134 |
ZERO_RATES_MATRIX,
|
|
@@ -137,11 +139,15 @@
|
|
| 137 |
getRates
|
| 138 |
};
|
| 139 |
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
global.FricuitCurrencyRatesFetcher = CurrencyRatesFetcher;
|
| 146 |
}
|
| 147 |
|
|
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Currency Rates Fetcher & Offline Matrix Engine
|
| 3 |
* File: fricuit_currency_rates_fetcher.js
|
| 4 |
+
* Responsibility: Multi-endpoint real-time currency exchange rates fetcher, rate validation, and offline emergency static matrix synchronization with zero console logging.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
|
|
| 20 |
});
|
| 21 |
}
|
| 22 |
|
| 23 |
+
Object.freeze(ZERO_RATES_MATRIX);
|
| 24 |
+
|
| 25 |
const RatesState = {
|
| 26 |
liveRates: { ...ZERO_RATES_MATRIX },
|
| 27 |
isRatesLoaded: false,
|
|
|
|
| 130 |
return RatesState.liveRates || ZERO_RATES_MATRIX;
|
| 131 |
}
|
| 132 |
|
| 133 |
+
// Export module to global scope with immutable descriptor
|
| 134 |
const CurrencyRatesFetcher = {
|
| 135 |
RatesState,
|
| 136 |
ZERO_RATES_MATRIX,
|
|
|
|
| 139 |
getRates
|
| 140 |
};
|
| 141 |
|
| 142 |
+
Object.freeze(CurrencyRatesFetcher);
|
| 143 |
+
|
| 144 |
+
try {
|
| 145 |
+
Object.defineProperty(global, 'FricuitCurrencyRatesFetcher', {
|
| 146 |
+
value: CurrencyRatesFetcher,
|
| 147 |
+
writable: false,
|
| 148 |
+
configurable: false
|
| 149 |
+
});
|
| 150 |
+
} catch (e) {
|
| 151 |
global.FricuitCurrencyRatesFetcher = CurrencyRatesFetcher;
|
| 152 |
}
|
| 153 |
|
fricuit_device_telemetry.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Deep Hardware, Device, High-Entropy OS & Context Telemetry Engine
|
| 3 |
* File: fricuit_device_telemetry.js
|
| 4 |
-
* Responsibility: Accurate high-entropy identification of operating systems (exact Windows 11 builds vs Windows 10, macOS versions, exact iOS/iPadOS releases, Android OS, Linux, ChromeOS), device form factors, browser engines, runtime sandbox modes, and silent background persistence of hardware diagnostics
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
@@ -27,7 +27,6 @@
|
|
| 27 |
let osVersion = '';
|
| 28 |
|
| 29 |
if (/Win(dows )?NT 10\.0/i.test(ua)) {
|
| 30 |
-
// Heuristic baseline; will be refined by High-Entropy Client Hints if available
|
| 31 |
osName = 'Windows 10 / 11';
|
| 32 |
osCode = 'windows';
|
| 33 |
osVersion = '10.0';
|
|
@@ -190,7 +189,6 @@
|
|
| 190 |
|
| 191 |
/**
|
| 192 |
* High-Entropy Client Hints Asynchronous OS Version Resolver.
|
| 193 |
-
* Accurately distinguishes Windows 11 build releases from Windows 10, exact device models, and CPU architectures.
|
| 194 |
* @returns {Promise<Object>}
|
| 195 |
*/
|
| 196 |
async function detectEnvironmentAsync() {
|
|
@@ -216,8 +214,6 @@
|
|
| 216 |
baseEnv.deviceModel = hints.model;
|
| 217 |
}
|
| 218 |
|
| 219 |
-
// Refine Windows 11 vs Windows 10
|
| 220 |
-
// On Windows, Chromium returns platformVersion major >= 13 for Windows 11
|
| 221 |
if (hints.platform === 'Windows' || baseEnv.osCode === 'windows') {
|
| 222 |
if (hints.platformVersion) {
|
| 223 |
const majorVersion = parseInt(hints.platformVersion.split('.')[0], 10);
|
|
@@ -234,9 +230,7 @@
|
|
| 234 |
baseEnv.osVersion = hints.platformVersion || baseEnv.osVersion;
|
| 235 |
}
|
| 236 |
}
|
| 237 |
-
} catch (hintsErr) {
|
| 238 |
-
// High entropy query restricted or rejected; retain regex parsed baseline
|
| 239 |
-
}
|
| 240 |
}
|
| 241 |
|
| 242 |
return baseEnv;
|
|
@@ -320,7 +314,6 @@
|
|
| 320 |
timestamp: new Date().toISOString()
|
| 321 |
};
|
| 322 |
|
| 323 |
-
// Silently record telemetry snapshot into Fricuit local database
|
| 324 |
try {
|
| 325 |
if (global.FricuitStorage && typeof global.FricuitStorage.set === 'function') {
|
| 326 |
await global.FricuitStorage.set('fricuit_device_telemetry_snapshot', report);
|
|
@@ -345,7 +338,7 @@
|
|
| 345 |
document.addEventListener('DOMContentLoaded', autoCollectAndPersistTelemetry);
|
| 346 |
}
|
| 347 |
|
| 348 |
-
// Export module to global scope
|
| 349 |
const DeviceTelemetry = {
|
| 350 |
detectEnvironment: detectEnvironmentSync,
|
| 351 |
detectEnvironmentAsync,
|
|
@@ -355,11 +348,15 @@
|
|
| 355 |
autoCollectAndPersistTelemetry
|
| 356 |
};
|
| 357 |
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 363 |
global.FricuitDeviceTelemetry = DeviceTelemetry;
|
| 364 |
}
|
| 365 |
|
|
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Deep Hardware, Device, High-Entropy OS & Context Telemetry Engine
|
| 3 |
* File: fricuit_device_telemetry.js
|
| 4 |
+
* Responsibility: Accurate high-entropy identification of operating systems (exact Windows 11 builds vs Windows 10, macOS versions, exact iOS/iPadOS releases, Android OS, Linux, ChromeOS), device form factors, browser engines, runtime sandbox modes, and silent background persistence of hardware diagnostics with zero console logging.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
|
|
| 27 |
let osVersion = '';
|
| 28 |
|
| 29 |
if (/Win(dows )?NT 10\.0/i.test(ua)) {
|
|
|
|
| 30 |
osName = 'Windows 10 / 11';
|
| 31 |
osCode = 'windows';
|
| 32 |
osVersion = '10.0';
|
|
|
|
| 189 |
|
| 190 |
/**
|
| 191 |
* High-Entropy Client Hints Asynchronous OS Version Resolver.
|
|
|
|
| 192 |
* @returns {Promise<Object>}
|
| 193 |
*/
|
| 194 |
async function detectEnvironmentAsync() {
|
|
|
|
| 214 |
baseEnv.deviceModel = hints.model;
|
| 215 |
}
|
| 216 |
|
|
|
|
|
|
|
| 217 |
if (hints.platform === 'Windows' || baseEnv.osCode === 'windows') {
|
| 218 |
if (hints.platformVersion) {
|
| 219 |
const majorVersion = parseInt(hints.platformVersion.split('.')[0], 10);
|
|
|
|
| 230 |
baseEnv.osVersion = hints.platformVersion || baseEnv.osVersion;
|
| 231 |
}
|
| 232 |
}
|
| 233 |
+
} catch (hintsErr) {}
|
|
|
|
|
|
|
| 234 |
}
|
| 235 |
|
| 236 |
return baseEnv;
|
|
|
|
| 314 |
timestamp: new Date().toISOString()
|
| 315 |
};
|
| 316 |
|
|
|
|
| 317 |
try {
|
| 318 |
if (global.FricuitStorage && typeof global.FricuitStorage.set === 'function') {
|
| 319 |
await global.FricuitStorage.set('fricuit_device_telemetry_snapshot', report);
|
|
|
|
| 338 |
document.addEventListener('DOMContentLoaded', autoCollectAndPersistTelemetry);
|
| 339 |
}
|
| 340 |
|
| 341 |
+
// Export module to global scope with immutable descriptor
|
| 342 |
const DeviceTelemetry = {
|
| 343 |
detectEnvironment: detectEnvironmentSync,
|
| 344 |
detectEnvironmentAsync,
|
|
|
|
| 348 |
autoCollectAndPersistTelemetry
|
| 349 |
};
|
| 350 |
|
| 351 |
+
Object.freeze(DeviceTelemetry);
|
| 352 |
+
|
| 353 |
+
try {
|
| 354 |
+
Object.defineProperty(global, 'FricuitDeviceTelemetry', {
|
| 355 |
+
value: DeviceTelemetry,
|
| 356 |
+
writable: false,
|
| 357 |
+
configurable: false
|
| 358 |
+
});
|
| 359 |
+
} catch (e) {
|
| 360 |
global.FricuitDeviceTelemetry = DeviceTelemetry;
|
| 361 |
}
|
| 362 |
|
fricuit_image_qr_extractor.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Multi-Scale Image QR Extraction Engine
|
| 3 |
* File: fricuit_image_qr_extractor.js
|
| 4 |
-
* Responsibility: Multi-pass image extraction, hardware BarcodeDetector acceleration,
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
@@ -28,7 +28,7 @@
|
|
| 28 |
}
|
| 29 |
|
| 30 |
/**
|
| 31 |
-
* Cleans, sanitizes, and strips zero-width non-printing characters and
|
| 32 |
* @param {string} rawText
|
| 33 |
* @returns {string|null}
|
| 34 |
*/
|
|
@@ -36,11 +36,59 @@
|
|
| 36 |
if (!rawText || typeof rawText !== 'string') return null;
|
| 37 |
let str = rawText.trim().replace(/^[\uFEFF\x00-\x1F\x7F-\x9F]+|[\uFEFF\x00-\x1F\x7F-\x9F]+$/g, '');
|
| 38 |
str = str.replace(/^["']|["']$/g, '').trim();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
return str.length > 0 ? str : null;
|
| 40 |
}
|
| 41 |
|
| 42 |
/**
|
| 43 |
-
*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
* @param {ImageData} imgData
|
| 45 |
* @param {number} width
|
| 46 |
* @param {number} height
|
|
@@ -61,6 +109,25 @@
|
|
| 61 |
return null;
|
| 62 |
}
|
| 63 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
/**
|
| 65 |
* Converts a File or Blob into an ImageBitmap or HTMLImageElement.
|
| 66 |
* @param {Blob|File} fileBlob
|
|
@@ -69,7 +136,7 @@
|
|
| 69 |
async function loadSourceImage(fileBlob) {
|
| 70 |
if (typeof createImageBitmap === 'function') {
|
| 71 |
try {
|
| 72 |
-
const bitmap = await createImageBitmap(fileBlob);
|
| 73 |
if (bitmap && bitmap.width > 0 && bitmap.height > 0) {
|
| 74 |
return bitmap;
|
| 75 |
}
|
|
@@ -108,27 +175,19 @@
|
|
| 108 |
const natH = sourceImg.height || sourceImg.naturalHeight;
|
| 109 |
if (!natW || !natH) return null;
|
| 110 |
|
| 111 |
-
// 1.
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
const detector = new window.BarcodeDetector({ formats: ['qr_code'] });
|
| 115 |
-
const barcodes = await detector.detect(sourceImg);
|
| 116 |
-
if (barcodes && barcodes.length > 0 && barcodes[0].rawValue) {
|
| 117 |
-
const detectedText = cleanExtractedQRPayload(barcodes[0].rawValue);
|
| 118 |
-
if (detectedText) return detectedText;
|
| 119 |
-
}
|
| 120 |
-
} catch (hwErr) {}
|
| 121 |
-
}
|
| 122 |
|
| 123 |
const canvas = document.createElement('canvas');
|
| 124 |
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
| 125 |
if (!ctx) return null;
|
| 126 |
|
| 127 |
-
//
|
| 128 |
-
const
|
| 129 |
-
const
|
| 130 |
|
| 131 |
-
for (const maxDim of
|
| 132 |
if (natW > maxDim || natH > maxDim) {
|
| 133 |
if (natW >= natH) {
|
| 134 |
const w = maxDim;
|
|
@@ -141,23 +200,32 @@
|
|
| 141 |
}
|
| 142 |
}
|
| 143 |
}
|
|
|
|
| 144 |
|
| 145 |
for (const scale of targetScales) {
|
| 146 |
const w = scale.w;
|
| 147 |
const h = scale.h;
|
|
|
|
|
|
|
| 148 |
canvas.width = w;
|
| 149 |
canvas.height = h;
|
| 150 |
-
ctx.
|
|
|
|
| 151 |
ctx.drawImage(sourceImg, 0, 0, w, h);
|
| 152 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
let rawImgData = null;
|
| 154 |
try {
|
| 155 |
rawImgData = ctx.getImageData(0, 0, w, h);
|
|
|
|
| 156 |
} catch (ctxErr) {
|
| 157 |
continue;
|
| 158 |
}
|
| 159 |
|
| 160 |
-
// Pass 1:
|
| 161 |
let result = tryDecodeImageData(rawImgData, w, h);
|
| 162 |
if (result) return result;
|
| 163 |
|
|
@@ -169,7 +237,15 @@
|
|
| 169 |
if (result) return result;
|
| 170 |
} catch (e) {}
|
| 171 |
|
| 172 |
-
// Pass 3:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
try {
|
| 174 |
const otsuData = VisionFilters.applyOtsuThreshold(rawImgData.data, w, h);
|
| 175 |
const otsuImgData = new ImageData(otsuData, w, h);
|
|
@@ -177,7 +253,7 @@
|
|
| 177 |
if (result) return result;
|
| 178 |
} catch (e) {}
|
| 179 |
|
| 180 |
-
// Pass
|
| 181 |
try {
|
| 182 |
const adaptiveData = VisionFilters.applyAdaptiveThreshold(rawImgData.data, w, h, 0.15);
|
| 183 |
const adaptiveImgData = new ImageData(adaptiveData, w, h);
|
|
@@ -185,44 +261,90 @@
|
|
| 185 |
if (result) return result;
|
| 186 |
} catch (e) {}
|
| 187 |
|
| 188 |
-
// Pass
|
| 189 |
try {
|
| 190 |
const sharpData = VisionFilters.applySharpeningKernel(rawImgData.data, w, h);
|
| 191 |
const sharpImgData = new ImageData(sharpData, w, h);
|
| 192 |
result = tryDecodeImageData(sharpImgData, w, h);
|
| 193 |
if (result) return result;
|
| 194 |
} catch (e) {}
|
|
|
|
| 195 |
|
| 196 |
-
|
|
|
|
|
|
|
| 197 |
try {
|
| 198 |
-
const cropW = Math.floor(
|
| 199 |
-
const cropH = Math.floor(
|
| 200 |
-
const cropX = Math.floor((
|
| 201 |
-
const cropY = Math.floor((
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
const cropCanvas = document.createElement('canvas');
|
| 203 |
-
cropCanvas.width =
|
| 204 |
-
cropCanvas.height =
|
| 205 |
const cropCtx = cropCanvas.getContext('2d', { willReadFrequently: true });
|
| 206 |
if (cropCtx) {
|
| 207 |
-
cropCtx.
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 211 |
}
|
| 212 |
} catch (e) {}
|
| 213 |
}
|
| 214 |
|
| 215 |
-
// Pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
const rotations = [90, 180, 270];
|
| 217 |
for (const angle of rotations) {
|
| 218 |
try {
|
| 219 |
const rotW = (angle === 90 || angle === 270) ? natH : natW;
|
| 220 |
const rotH = (angle === 90 || angle === 270) ? natW : natH;
|
| 221 |
-
const scaledW = Math.min(
|
| 222 |
const scaledH = Math.round(scaledW * (rotH / rotW));
|
|
|
|
| 223 |
canvas.width = scaledW;
|
| 224 |
canvas.height = scaledH;
|
| 225 |
-
ctx.
|
|
|
|
| 226 |
ctx.save();
|
| 227 |
ctx.translate(scaledW / 2, scaledH / 2);
|
| 228 |
ctx.rotate((angle * Math.PI) / 180);
|
|
@@ -232,7 +354,12 @@
|
|
| 232 |
ctx.drawImage(sourceImg, -scaledW / 2, -scaledH / 2, scaledW, scaledH);
|
| 233 |
}
|
| 234 |
ctx.restore();
|
|
|
|
|
|
|
|
|
|
|
|
|
| 235 |
const rotData = ctx.getImageData(0, 0, scaledW, scaledH);
|
|
|
|
| 236 |
const result = tryDecodeImageData(rotData, scaledW, scaledH);
|
| 237 |
if (result) return result;
|
| 238 |
} catch (e) {}
|
|
@@ -241,7 +368,7 @@
|
|
| 241 |
return null;
|
| 242 |
}
|
| 243 |
|
| 244 |
-
// Export module to global scope
|
| 245 |
const OmniTransfer = {
|
| 246 |
cleanExtractedQRPayload,
|
| 247 |
extractQRFromImageBlob,
|
|
@@ -249,13 +376,20 @@
|
|
| 249 |
OmniVision: VisionFilters
|
| 250 |
};
|
| 251 |
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
global.FricuitImageQRExtractor = OmniTransfer;
|
| 260 |
global.FricuitOmniTransfer = OmniTransfer;
|
| 261 |
}
|
|
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Multi-Scale Image QR Extraction Engine
|
| 3 |
* File: fricuit_image_qr_extractor.js
|
| 4 |
+
* Responsibility: Multi-pass image extraction, hardware BarcodeDetector acceleration, white-fill alpha blending, optimal scale ladder, aspect ratio crops, quiet-zone padding, 90/180/270 rotations, and payload sanitization with zero console logging.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
|
|
| 28 |
}
|
| 29 |
|
| 30 |
/**
|
| 31 |
+
* Cleans, sanitizes, and strips zero-width non-printing characters, quotes, and URI wrappers from extracted QR payload.
|
| 32 |
* @param {string} rawText
|
| 33 |
* @returns {string|null}
|
| 34 |
*/
|
|
|
|
| 36 |
if (!rawText || typeof rawText !== 'string') return null;
|
| 37 |
let str = rawText.trim().replace(/^[\uFEFF\x00-\x1F\x7F-\x9F]+|[\uFEFF\x00-\x1F\x7F-\x9F]+$/g, '');
|
| 38 |
str = str.replace(/^["']|["']$/g, '').trim();
|
| 39 |
+
|
| 40 |
+
// Extract nested pay_uri or token parameters if wrapped inside URL query parameters
|
| 41 |
+
if (str.includes('pay_uri=')) {
|
| 42 |
+
const match = str.match(/pay_uri=([^&]+)/);
|
| 43 |
+
if (match && match[1]) {
|
| 44 |
+
try {
|
| 45 |
+
str = decodeURIComponent(match[1]).trim();
|
| 46 |
+
} catch (e) {}
|
| 47 |
+
}
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
return str.length > 0 ? str : null;
|
| 51 |
}
|
| 52 |
|
| 53 |
/**
|
| 54 |
+
* Normalizes RGBA buffer by blending alpha channel over pure white background.
|
| 55 |
+
* @param {Uint8ClampedArray} data
|
| 56 |
+
* @param {number} width
|
| 57 |
+
* @param {number} height
|
| 58 |
+
*/
|
| 59 |
+
function blendAlphaOverWhite(data, width, height) {
|
| 60 |
+
const len = width * height * 4;
|
| 61 |
+
for (let i = 0; i < len; i += 4) {
|
| 62 |
+
const a = data[i + 3] / 255;
|
| 63 |
+
if (a < 1) {
|
| 64 |
+
data[i] = Math.round(data[i] * a + 255 * (1 - a));
|
| 65 |
+
data[i + 1] = Math.round(data[i + 1] * a + 255 * (1 - a));
|
| 66 |
+
data[i + 2] = Math.round(data[i + 2] * a + 255 * (1 - a));
|
| 67 |
+
data[i + 3] = 255;
|
| 68 |
+
}
|
| 69 |
+
}
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
/**
|
| 73 |
+
* Inverts colors (for Dark Mode screenshots: white QR on dark background).
|
| 74 |
+
* @param {Uint8ClampedArray} data
|
| 75 |
+
* @param {number} width
|
| 76 |
+
* @param {number} height
|
| 77 |
+
* @returns {Uint8ClampedArray}
|
| 78 |
+
*/
|
| 79 |
+
function invertColors(data, width, height) {
|
| 80 |
+
const output = new Uint8ClampedArray(data.length);
|
| 81 |
+
for (let i = 0; i < data.length; i += 4) {
|
| 82 |
+
output[i] = 255 - data[i];
|
| 83 |
+
output[i + 1] = 255 - data[i + 1];
|
| 84 |
+
output[i + 2] = 255 - data[i + 2];
|
| 85 |
+
output[i + 3] = 255;
|
| 86 |
+
}
|
| 87 |
+
return output;
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
/**
|
| 91 |
+
* Attempts decoding on raw ImageData using jsQR with inversion fallback.
|
| 92 |
* @param {ImageData} imgData
|
| 93 |
* @param {number} width
|
| 94 |
* @param {number} height
|
|
|
|
| 109 |
return null;
|
| 110 |
}
|
| 111 |
|
| 112 |
+
/**
|
| 113 |
+
* Attempts native BarcodeDetector scan if available on device browser.
|
| 114 |
+
* @param {CanvasImageSource} source
|
| 115 |
+
* @returns {Promise<string|null>}
|
| 116 |
+
*/
|
| 117 |
+
async function tryNativeBarcodeDetector(source) {
|
| 118 |
+
if (typeof window !== 'undefined' && typeof window.BarcodeDetector !== 'undefined') {
|
| 119 |
+
try {
|
| 120 |
+
const detector = new window.BarcodeDetector({ formats: ['qr_code'] });
|
| 121 |
+
const barcodes = await detector.detect(source);
|
| 122 |
+
if (barcodes && barcodes.length > 0 && barcodes[0].rawValue) {
|
| 123 |
+
const detectedText = cleanExtractedQRPayload(barcodes[0].rawValue);
|
| 124 |
+
if (detectedText) return detectedText;
|
| 125 |
+
}
|
| 126 |
+
} catch (hwErr) {}
|
| 127 |
+
}
|
| 128 |
+
return null;
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
/**
|
| 132 |
* Converts a File or Blob into an ImageBitmap or HTMLImageElement.
|
| 133 |
* @param {Blob|File} fileBlob
|
|
|
|
| 136 |
async function loadSourceImage(fileBlob) {
|
| 137 |
if (typeof createImageBitmap === 'function') {
|
| 138 |
try {
|
| 139 |
+
const bitmap = await createImageBitmap(fileBlob, { imageOrientation: "from-image" });
|
| 140 |
if (bitmap && bitmap.width > 0 && bitmap.height > 0) {
|
| 141 |
return bitmap;
|
| 142 |
}
|
|
|
|
| 175 |
const natH = sourceImg.height || sourceImg.naturalHeight;
|
| 176 |
if (!natW || !natH) return null;
|
| 177 |
|
| 178 |
+
// 1. Direct Native BarcodeDetector Pass (Hardware Accelerated)
|
| 179 |
+
const nativeDirect = await tryNativeBarcodeDetector(sourceImg);
|
| 180 |
+
if (nativeDirect) return nativeDirect;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
|
| 182 |
const canvas = document.createElement('canvas');
|
| 183 |
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
| 184 |
if (!ctx) return null;
|
| 185 |
|
| 186 |
+
// Scale Ladder: Fast optimal resolution first, progressive down/upscaling, then native
|
| 187 |
+
const desiredMaxDimensions = [800, 600, 1000, 450, 1200, 320, 1600];
|
| 188 |
+
const targetScales = [];
|
| 189 |
|
| 190 |
+
for (const maxDim of desiredMaxDimensions) {
|
| 191 |
if (natW > maxDim || natH > maxDim) {
|
| 192 |
if (natW >= natH) {
|
| 193 |
const w = maxDim;
|
|
|
|
| 200 |
}
|
| 201 |
}
|
| 202 |
}
|
| 203 |
+
targetScales.push({ w: natW, h: natH });
|
| 204 |
|
| 205 |
for (const scale of targetScales) {
|
| 206 |
const w = scale.w;
|
| 207 |
const h = scale.h;
|
| 208 |
+
|
| 209 |
+
// Fill solid white background before drawing (solves transparent PNG issue)
|
| 210 |
canvas.width = w;
|
| 211 |
canvas.height = h;
|
| 212 |
+
ctx.fillStyle = '#FFFFFF';
|
| 213 |
+
ctx.fillRect(0, 0, w, h);
|
| 214 |
ctx.drawImage(sourceImg, 0, 0, w, h);
|
| 215 |
|
| 216 |
+
// Native BarcodeDetector on scaled Canvas
|
| 217 |
+
const nativeCanvasResult = await tryNativeBarcodeDetector(canvas);
|
| 218 |
+
if (nativeCanvasResult) return nativeCanvasResult;
|
| 219 |
+
|
| 220 |
let rawImgData = null;
|
| 221 |
try {
|
| 222 |
rawImgData = ctx.getImageData(0, 0, w, h);
|
| 223 |
+
blendAlphaOverWhite(rawImgData.data, w, h);
|
| 224 |
} catch (ctxErr) {
|
| 225 |
continue;
|
| 226 |
}
|
| 227 |
|
| 228 |
+
// Pass 1: Alpha-blended clean image
|
| 229 |
let result = tryDecodeImageData(rawImgData, w, h);
|
| 230 |
if (result) return result;
|
| 231 |
|
|
|
|
| 237 |
if (result) return result;
|
| 238 |
} catch (e) {}
|
| 239 |
|
| 240 |
+
// Pass 3: Inverted Colors (For dark-mode screenshots & inverted QRs)
|
| 241 |
+
try {
|
| 242 |
+
const invertedData = invertColors(rawImgData.data, w, h);
|
| 243 |
+
const invertedImgData = new ImageData(invertedData, w, h);
|
| 244 |
+
result = tryDecodeImageData(invertedImgData, w, h);
|
| 245 |
+
if (result) return result;
|
| 246 |
+
} catch (e) {}
|
| 247 |
+
|
| 248 |
+
// Pass 4: Otsu Optimal Global Binarization
|
| 249 |
try {
|
| 250 |
const otsuData = VisionFilters.applyOtsuThreshold(rawImgData.data, w, h);
|
| 251 |
const otsuImgData = new ImageData(otsuData, w, h);
|
|
|
|
| 253 |
if (result) return result;
|
| 254 |
} catch (e) {}
|
| 255 |
|
| 256 |
+
// Pass 5: Bradley-Roth Adaptive Thresholding
|
| 257 |
try {
|
| 258 |
const adaptiveData = VisionFilters.applyAdaptiveThreshold(rawImgData.data, w, h, 0.15);
|
| 259 |
const adaptiveImgData = new ImageData(adaptiveData, w, h);
|
|
|
|
| 261 |
if (result) return result;
|
| 262 |
} catch (e) {}
|
| 263 |
|
| 264 |
+
// Pass 6: Sharpening Kernel
|
| 265 |
try {
|
| 266 |
const sharpData = VisionFilters.applySharpeningKernel(rawImgData.data, w, h);
|
| 267 |
const sharpImgData = new ImageData(sharpData, w, h);
|
| 268 |
result = tryDecodeImageData(sharpImgData, w, h);
|
| 269 |
if (result) return result;
|
| 270 |
} catch (e) {}
|
| 271 |
+
}
|
| 272 |
|
| 273 |
+
// Pass 7: Multi-Region Crops (Centers 85%, 70%, 55% for screenshots)
|
| 274 |
+
const cropRatios = [0.85, 0.70, 0.55];
|
| 275 |
+
for (const ratio of cropRatios) {
|
| 276 |
try {
|
| 277 |
+
const cropW = Math.floor(natW * ratio);
|
| 278 |
+
const cropH = Math.floor(natH * ratio);
|
| 279 |
+
const cropX = Math.floor((natW - cropW) / 2);
|
| 280 |
+
const cropY = Math.floor((natH - cropH) / 2);
|
| 281 |
+
|
| 282 |
+
const scaledCropW = Math.min(800, cropW);
|
| 283 |
+
const scaledCropH = Math.round(scaledCropW * (cropH / cropW));
|
| 284 |
+
|
| 285 |
const cropCanvas = document.createElement('canvas');
|
| 286 |
+
cropCanvas.width = scaledCropW;
|
| 287 |
+
cropCanvas.height = scaledCropH;
|
| 288 |
const cropCtx = cropCanvas.getContext('2d', { willReadFrequently: true });
|
| 289 |
if (cropCtx) {
|
| 290 |
+
cropCtx.fillStyle = '#FFFFFF';
|
| 291 |
+
cropCtx.fillRect(0, 0, scaledCropW, scaledCropH);
|
| 292 |
+
cropCtx.drawImage(sourceImg, cropX, cropY, cropW, cropH, 0, 0, scaledCropW, scaledCropH);
|
| 293 |
+
|
| 294 |
+
const nativeCropResult = await tryNativeBarcodeDetector(cropCanvas);
|
| 295 |
+
if (nativeCropResult) return nativeCropResult;
|
| 296 |
+
|
| 297 |
+
const cropImgData = cropCtx.getImageData(0, 0, scaledCropW, scaledCropH);
|
| 298 |
+
blendAlphaOverWhite(cropImgData.data, scaledCropW, scaledCropH);
|
| 299 |
+
let cropResult = tryDecodeImageData(cropImgData, scaledCropW, scaledCropH);
|
| 300 |
+
if (cropResult) return cropResult;
|
| 301 |
+
|
| 302 |
+
const cropContrast = VisionFilters.applyContrastStretching(cropImgData.data, scaledCropW, scaledCropH);
|
| 303 |
+
cropResult = tryDecodeImageData(new ImageData(cropContrast, scaledCropW, scaledCropH), scaledCropW, scaledCropH);
|
| 304 |
+
if (cropResult) return cropResult;
|
| 305 |
}
|
| 306 |
} catch (e) {}
|
| 307 |
}
|
| 308 |
|
| 309 |
+
// Pass 8: Quiet-Zone Padding Injection (For tight/borderless QR screenshots)
|
| 310 |
+
try {
|
| 311 |
+
const padW = Math.min(800, natW);
|
| 312 |
+
const padH = Math.round(padW * (natH / natW));
|
| 313 |
+
const padding = Math.max(20, Math.floor(padW * 0.1));
|
| 314 |
+
|
| 315 |
+
const padCanvas = document.createElement('canvas');
|
| 316 |
+
padCanvas.width = padW + (padding * 2);
|
| 317 |
+
padCanvas.height = padH + (padding * 2);
|
| 318 |
+
const padCtx = padCanvas.getContext('2d', { willReadFrequently: true });
|
| 319 |
+
if (padCtx) {
|
| 320 |
+
padCtx.fillStyle = '#FFFFFF';
|
| 321 |
+
padCtx.fillRect(0, 0, padCanvas.width, padCanvas.height);
|
| 322 |
+
padCtx.drawImage(sourceImg, padding, padding, padW, padH);
|
| 323 |
+
|
| 324 |
+
const padImgData = padCtx.getImageData(0, 0, padCanvas.width, padCanvas.height);
|
| 325 |
+
blendAlphaOverWhite(padImgData.data, padCanvas.width, padCanvas.height);
|
| 326 |
+
let padResult = tryDecodeImageData(padImgData, padCanvas.width, padCanvas.height);
|
| 327 |
+
if (padResult) return padResult;
|
| 328 |
+
|
| 329 |
+
const padContrast = VisionFilters.applyContrastStretching(padImgData.data, padCanvas.width, padCanvas.height);
|
| 330 |
+
padResult = tryDecodeImageData(new ImageData(padContrast, padCanvas.width, padCanvas.height), padCanvas.width, padCanvas.height);
|
| 331 |
+
if (padResult) return padResult;
|
| 332 |
+
}
|
| 333 |
+
} catch (e) {}
|
| 334 |
+
|
| 335 |
+
// Pass 9: Rotations (90°, 180°, 270°)
|
| 336 |
const rotations = [90, 180, 270];
|
| 337 |
for (const angle of rotations) {
|
| 338 |
try {
|
| 339 |
const rotW = (angle === 90 || angle === 270) ? natH : natW;
|
| 340 |
const rotH = (angle === 90 || angle === 270) ? natW : natH;
|
| 341 |
+
const scaledW = Math.min(800, rotW);
|
| 342 |
const scaledH = Math.round(scaledW * (rotH / rotW));
|
| 343 |
+
|
| 344 |
canvas.width = scaledW;
|
| 345 |
canvas.height = scaledH;
|
| 346 |
+
ctx.fillStyle = '#FFFFFF';
|
| 347 |
+
ctx.fillRect(0, 0, scaledW, scaledH);
|
| 348 |
ctx.save();
|
| 349 |
ctx.translate(scaledW / 2, scaledH / 2);
|
| 350 |
ctx.rotate((angle * Math.PI) / 180);
|
|
|
|
| 354 |
ctx.drawImage(sourceImg, -scaledW / 2, -scaledH / 2, scaledW, scaledH);
|
| 355 |
}
|
| 356 |
ctx.restore();
|
| 357 |
+
|
| 358 |
+
const nativeRotResult = await tryNativeBarcodeDetector(canvas);
|
| 359 |
+
if (nativeRotResult) return nativeRotResult;
|
| 360 |
+
|
| 361 |
const rotData = ctx.getImageData(0, 0, scaledW, scaledH);
|
| 362 |
+
blendAlphaOverWhite(rotData.data, scaledW, scaledH);
|
| 363 |
const result = tryDecodeImageData(rotData, scaledW, scaledH);
|
| 364 |
if (result) return result;
|
| 365 |
} catch (e) {}
|
|
|
|
| 368 |
return null;
|
| 369 |
}
|
| 370 |
|
| 371 |
+
// Export module to global scope with immutable descriptor
|
| 372 |
const OmniTransfer = {
|
| 373 |
cleanExtractedQRPayload,
|
| 374 |
extractQRFromImageBlob,
|
|
|
|
| 376 |
OmniVision: VisionFilters
|
| 377 |
};
|
| 378 |
|
| 379 |
+
Object.freeze(OmniTransfer);
|
| 380 |
+
|
| 381 |
+
try {
|
| 382 |
+
Object.defineProperty(global, 'FricuitImageQRExtractor', {
|
| 383 |
+
value: OmniTransfer,
|
| 384 |
+
writable: false,
|
| 385 |
+
configurable: false
|
| 386 |
+
});
|
| 387 |
+
Object.defineProperty(global, 'FricuitOmniTransfer', {
|
| 388 |
+
value: OmniTransfer,
|
| 389 |
+
writable: false,
|
| 390 |
+
configurable: false
|
| 391 |
+
});
|
| 392 |
+
} catch (e) {
|
| 393 |
global.FricuitImageQRExtractor = OmniTransfer;
|
| 394 |
global.FricuitOmniTransfer = OmniTransfer;
|
| 395 |
}
|
fricuit_ledger_ui.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Ledger Audit Table, Filtering, CSV Export & Receipt Printing Engine
|
| 3 |
* File: fricuit_ledger_ui.js
|
| 4 |
-
* Responsibility: Dynamic DOM injection of Statement & Audit Ledger pane, rendering the cryptographic audit transaction ledger, multi-parameter search/filtering (currency, flow direction, date ranges), exporting UTF-8 BOM CSV files, and triggering receipt print jobs.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
@@ -313,8 +313,10 @@
|
|
| 313 |
const a = document.createElement('a');
|
| 314 |
a.href = url;
|
| 315 |
a.download = `Fricuit_Statement_${new Date().toISOString().slice(0, 10)}.csv`;
|
|
|
|
| 316 |
a.click();
|
| 317 |
-
|
|
|
|
| 318 |
};
|
| 319 |
}
|
| 320 |
|
|
@@ -325,14 +327,13 @@
|
|
| 325 |
}
|
| 326 |
}
|
| 327 |
|
| 328 |
-
// Auto-inject ledger DOM on startup
|
| 329 |
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| 330 |
ensureLedgerPaneDOM();
|
| 331 |
} else {
|
| 332 |
document.addEventListener('DOMContentLoaded', ensureLedgerPaneDOM);
|
| 333 |
}
|
| 334 |
|
| 335 |
-
// Export module to global scope
|
| 336 |
const LedgerUI = {
|
| 337 |
populateLedgerCurrencyFilter,
|
| 338 |
renderLedgerTable,
|
|
@@ -340,11 +341,15 @@
|
|
| 340 |
ensureLedgerPaneDOM
|
| 341 |
};
|
| 342 |
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 348 |
global.FricuitLedgerUI = LedgerUI;
|
| 349 |
}
|
| 350 |
|
|
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Ledger Audit Table, Filtering, CSV Export & Receipt Printing Engine
|
| 3 |
* File: fricuit_ledger_ui.js
|
| 4 |
+
* Responsibility: Dynamic DOM injection of Statement & Audit Ledger pane, rendering the cryptographic audit transaction ledger, multi-parameter search/filtering (currency, flow direction, date ranges), exporting UTF-8 BOM CSV files, and triggering receipt print jobs with zero console logging.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
|
|
| 313 |
const a = document.createElement('a');
|
| 314 |
a.href = url;
|
| 315 |
a.download = `Fricuit_Statement_${new Date().toISOString().slice(0, 10)}.csv`;
|
| 316 |
+
document.body.appendChild(a);
|
| 317 |
a.click();
|
| 318 |
+
document.body.removeChild(a);
|
| 319 |
+
setTimeout(() => URL.revokeObjectURL(url), 6000);
|
| 320 |
};
|
| 321 |
}
|
| 322 |
|
|
|
|
| 327 |
}
|
| 328 |
}
|
| 329 |
|
|
|
|
| 330 |
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| 331 |
ensureLedgerPaneDOM();
|
| 332 |
} else {
|
| 333 |
document.addEventListener('DOMContentLoaded', ensureLedgerPaneDOM);
|
| 334 |
}
|
| 335 |
|
| 336 |
+
// Export module to global scope with immutable descriptor
|
| 337 |
const LedgerUI = {
|
| 338 |
populateLedgerCurrencyFilter,
|
| 339 |
renderLedgerTable,
|
|
|
|
| 341 |
ensureLedgerPaneDOM
|
| 342 |
};
|
| 343 |
|
| 344 |
+
Object.freeze(LedgerUI);
|
| 345 |
+
|
| 346 |
+
try {
|
| 347 |
+
Object.defineProperty(global, 'FricuitLedgerUI', {
|
| 348 |
+
value: LedgerUI,
|
| 349 |
+
writable: false,
|
| 350 |
+
configurable: false
|
| 351 |
+
});
|
| 352 |
+
} catch (e) {
|
| 353 |
global.FricuitLedgerUI = LedgerUI;
|
| 354 |
}
|
| 355 |
|
fricuit_merchant_invoices.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Digital Invoices & Official Merchant Receipts Engine
|
| 3 |
* File: fricuit_merchant_invoices.js
|
| 4 |
-
* Responsibility: Dynamic DOM injection of Merchant Invoices pane and Invoice Generator modal, creating, signing, rendering, storing, and printing official digital merchant invoices with embedded offline QR payment tokens.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
@@ -229,14 +229,13 @@
|
|
| 229 |
renderSavedInvoicesList();
|
| 230 |
}
|
| 231 |
|
| 232 |
-
// Auto-inject merchant invoices DOM on startup
|
| 233 |
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| 234 |
ensureMerchantInvoicesDOM();
|
| 235 |
} else {
|
| 236 |
document.addEventListener('DOMContentLoaded', ensureMerchantInvoicesDOM);
|
| 237 |
}
|
| 238 |
|
| 239 |
-
// Export module to global scope
|
| 240 |
const MerchantInvoices = {
|
| 241 |
compileAndSaveInvoice,
|
| 242 |
renderSavedInvoicesList,
|
|
@@ -244,11 +243,15 @@
|
|
| 244 |
ensureMerchantInvoicesDOM
|
| 245 |
};
|
| 246 |
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 252 |
global.FricuitMerchantInvoices = MerchantInvoices;
|
| 253 |
}
|
| 254 |
|
|
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Digital Invoices & Official Merchant Receipts Engine
|
| 3 |
* File: fricuit_merchant_invoices.js
|
| 4 |
+
* Responsibility: Dynamic DOM injection of Merchant Invoices pane and Invoice Generator modal, creating, signing, rendering, storing, and printing official digital merchant invoices with embedded offline QR payment tokens with zero console logging.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
|
|
| 229 |
renderSavedInvoicesList();
|
| 230 |
}
|
| 231 |
|
|
|
|
| 232 |
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| 233 |
ensureMerchantInvoicesDOM();
|
| 234 |
} else {
|
| 235 |
document.addEventListener('DOMContentLoaded', ensureMerchantInvoicesDOM);
|
| 236 |
}
|
| 237 |
|
| 238 |
+
// Export module to global scope with immutable descriptor
|
| 239 |
const MerchantInvoices = {
|
| 240 |
compileAndSaveInvoice,
|
| 241 |
renderSavedInvoicesList,
|
|
|
|
| 243 |
ensureMerchantInvoicesDOM
|
| 244 |
};
|
| 245 |
|
| 246 |
+
Object.freeze(MerchantInvoices);
|
| 247 |
+
|
| 248 |
+
try {
|
| 249 |
+
Object.defineProperty(global, 'FricuitMerchantInvoices', {
|
| 250 |
+
value: MerchantInvoices,
|
| 251 |
+
writable: false,
|
| 252 |
+
configurable: false
|
| 253 |
+
});
|
| 254 |
+
} catch (e) {
|
| 255 |
global.FricuitMerchantInvoices = MerchantInvoices;
|
| 256 |
}
|
| 257 |
|
fricuit_merchant_store.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Merchant Store & Mini Product Catalog
|
| 3 |
* File: fricuit_merchant_store.js
|
| 4 |
-
* Responsibility: Dynamic DOM injection of Merchant Store pane and Add Product modal, merchant inventory catalog management, item pricing, product cards rendering, and generating one-tap checkout QR codes.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
@@ -216,14 +216,13 @@
|
|
| 216 |
renderStoreProductsList();
|
| 217 |
}
|
| 218 |
|
| 219 |
-
// Auto-inject merchant store DOM on startup
|
| 220 |
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| 221 |
ensureMerchantStoreDOM();
|
| 222 |
} else {
|
| 223 |
document.addEventListener('DOMContentLoaded', ensureMerchantStoreDOM);
|
| 224 |
}
|
| 225 |
|
| 226 |
-
// Export module to global scope
|
| 227 |
const MerchantStore = {
|
| 228 |
renderStoreProductsList,
|
| 229 |
saveNewStoreProduct,
|
|
@@ -231,11 +230,15 @@
|
|
| 231 |
ensureMerchantStoreDOM
|
| 232 |
};
|
| 233 |
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 239 |
global.FricuitMerchantStore = MerchantStore;
|
| 240 |
}
|
| 241 |
|
|
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Merchant Store & Mini Product Catalog
|
| 3 |
* File: fricuit_merchant_store.js
|
| 4 |
+
* Responsibility: Dynamic DOM injection of Merchant Store pane and Add Product modal, merchant inventory catalog management, item pricing, product cards rendering, and generating one-tap checkout QR codes with zero console logging.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
|
|
| 216 |
renderStoreProductsList();
|
| 217 |
}
|
| 218 |
|
|
|
|
| 219 |
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| 220 |
ensureMerchantStoreDOM();
|
| 221 |
} else {
|
| 222 |
document.addEventListener('DOMContentLoaded', ensureMerchantStoreDOM);
|
| 223 |
}
|
| 224 |
|
| 225 |
+
// Export module to global scope with immutable descriptor
|
| 226 |
const MerchantStore = {
|
| 227 |
renderStoreProductsList,
|
| 228 |
saveNewStoreProduct,
|
|
|
|
| 230 |
ensureMerchantStoreDOM
|
| 231 |
};
|
| 232 |
|
| 233 |
+
Object.freeze(MerchantStore);
|
| 234 |
+
|
| 235 |
+
try {
|
| 236 |
+
Object.defineProperty(global, 'FricuitMerchantStore', {
|
| 237 |
+
value: MerchantStore,
|
| 238 |
+
writable: false,
|
| 239 |
+
configurable: false
|
| 240 |
+
});
|
| 241 |
+
} catch (e) {
|
| 242 |
global.FricuitMerchantStore = MerchantStore;
|
| 243 |
}
|
| 244 |
|
fricuit_p2p_receive_controller.js
CHANGED
|
@@ -54,7 +54,7 @@
|
|
| 54 |
<i class="fa-solid fa-file-audio"></i>
|
| 55 |
<span class="fricuit_truncate">Audio File</span>
|
| 56 |
</button>
|
| 57 |
-
<input type="file" id="input_unified_image_uploader" accept="image/png,
|
| 58 |
<input type="file" id="input_audio_voucher_uploader" accept="audio/*,.wav,.mp3" style="display: none;">
|
| 59 |
</div>
|
| 60 |
<div id="box_batch_images_processing_report" class="batch_images_report_box" style="display: none;">
|
|
@@ -189,7 +189,9 @@
|
|
| 189 |
if (str.includes('pay_uri=')) {
|
| 190 |
const match = str.match(/pay_uri=([^&]+)/);
|
| 191 |
if (match && match[1]) {
|
| 192 |
-
|
|
|
|
|
|
|
| 193 |
}
|
| 194 |
}
|
| 195 |
|
|
@@ -962,14 +964,13 @@
|
|
| 962 |
return true;
|
| 963 |
}
|
| 964 |
|
| 965 |
-
// Auto-inject receive DOM on startup
|
| 966 |
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| 967 |
ensureReceiveDOM();
|
| 968 |
} else {
|
| 969 |
document.addEventListener('DOMContentLoaded', ensureReceiveDOM);
|
| 970 |
}
|
| 971 |
|
| 972 |
-
// Export module to global scope
|
| 973 |
const P2PReceiveController = {
|
| 974 |
init: initP2PReceiveModule,
|
| 975 |
executeReceive: executeP2PReceive,
|
|
@@ -985,12 +986,16 @@
|
|
| 985 |
ensureReceiveDOM
|
| 986 |
};
|
| 987 |
|
| 988 |
-
|
| 989 |
-
|
| 990 |
-
|
| 991 |
-
|
| 992 |
-
|
| 993 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 994 |
}
|
| 995 |
|
| 996 |
})(typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : this);
|
|
|
|
| 54 |
<i class="fa-solid fa-file-audio"></i>
|
| 55 |
<span class="fricuit_truncate">Audio File</span>
|
| 56 |
</button>
|
| 57 |
+
<input type="file" id="input_unified_image_uploader" accept="image/*,.png,.jpg,.jpeg,.webp,.svg" style="display: none;">
|
| 58 |
<input type="file" id="input_audio_voucher_uploader" accept="audio/*,.wav,.mp3" style="display: none;">
|
| 59 |
</div>
|
| 60 |
<div id="box_batch_images_processing_report" class="batch_images_report_box" style="display: none;">
|
|
|
|
| 189 |
if (str.includes('pay_uri=')) {
|
| 190 |
const match = str.match(/pay_uri=([^&]+)/);
|
| 191 |
if (match && match[1]) {
|
| 192 |
+
try {
|
| 193 |
+
str = decodeURIComponent(match[1]).trim();
|
| 194 |
+
} catch (e) {}
|
| 195 |
}
|
| 196 |
}
|
| 197 |
|
|
|
|
| 964 |
return true;
|
| 965 |
}
|
| 966 |
|
|
|
|
| 967 |
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| 968 |
ensureReceiveDOM();
|
| 969 |
} else {
|
| 970 |
document.addEventListener('DOMContentLoaded', ensureReceiveDOM);
|
| 971 |
}
|
| 972 |
|
| 973 |
+
// Export module to global scope with immutable descriptor
|
| 974 |
const P2PReceiveController = {
|
| 975 |
init: initP2PReceiveModule,
|
| 976 |
executeReceive: executeP2PReceive,
|
|
|
|
| 986 |
ensureReceiveDOM
|
| 987 |
};
|
| 988 |
|
| 989 |
+
Object.freeze(P2PReceiveController);
|
| 990 |
+
|
| 991 |
+
try {
|
| 992 |
+
Object.defineProperty(global, 'FricuitP2PReceive', {
|
| 993 |
+
value: P2PReceiveController,
|
| 994 |
+
writable: false,
|
| 995 |
+
configurable: false
|
| 996 |
+
});
|
| 997 |
+
} catch (e) {
|
| 998 |
+
global.FricuitP2PReceive = P2PReceiveController;
|
| 999 |
}
|
| 1000 |
|
| 1001 |
})(typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : this);
|
fricuit_p2p_send_controller.js
CHANGED
|
@@ -173,7 +173,7 @@
|
|
| 173 |
</button>
|
| 174 |
|
| 175 |
<div class="q6r7s8t9u0v1w2x3y4z5a6b7c8d9e0f1 q5r6s7t8u9v0w1x2y3z4a5b6c7d8e9f0" id="r6s7t8u9v0w1x2y3z4a5b6c7d8e9f0g1" style="display: none;">
|
| 176 |
-
<label class="fricuit_truncate"><i class="fa-solid fa-code"></i>
|
| 177 |
<div class="m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8 s6t7u8v9w0x1y2z3a4b5c6d7e8f9g0h1">
|
| 178 |
<input type="text" id="t6u7v8w9x0y1z2a3b4c5d6e7f8g9h0i1" readonly>
|
| 179 |
<button type="button" class="o3p4q5r6s7t8u9v0w1x2y3z4a5b6c7d8" id="u6v7w8x9y0z1a2b3c4d5e6f7g8h9i0j1">
|
|
|
|
| 173 |
</button>
|
| 174 |
|
| 175 |
<div class="q6r7s8t9u0v1w2x3y4z5a6b7c8d9e0f1 q5r6s7t8u9v0w1x2y3z4a5b6c7d8e9f0" id="r6s7t8u9v0w1x2y3z4a5b6c7d8e9f0g1" style="display: none;">
|
| 176 |
+
<label class="fricuit_truncate"><i class="fa-solid fa-code"></i> Transfer Package:</label>
|
| 177 |
<div class="m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8 s6t7u8v9w0x1y2z3a4b5c6d7e8f9g0h1">
|
| 178 |
<input type="text" id="t6u7v8w9x0y1z2a3b4c5d6e7f8g9h0i1" readonly>
|
| 179 |
<button type="button" class="o3p4q5r6s7t8u9v0w1x2y3z4a5b6c7d8" id="u6v7w8x9y0z1a2b3c4d5e6f7g8h9i0j1">
|
fricuit_permissions_manager.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Automated System Capabilities & Silent Permissions Manager
|
| 3 |
* File: fricuit_permissions_manager.js
|
| 4 |
-
* Responsibility: Automatic capability acquisition on initial launch (Geolocation, Notifications, Smart Clipboard, Persistent Storage, Screen Wake Lock, Media Sensors, etc.), silent persistence of capabilities matrix to encrypted storage, and background permission state management.
|
| 5 |
* Pure Offline - 100% Client-Side.
|
| 6 |
*/
|
| 7 |
|
|
@@ -481,7 +481,6 @@
|
|
| 481 |
await collectAndPersistPermissionsMatrix();
|
| 482 |
}
|
| 483 |
|
| 484 |
-
// Auto-execute permission acquisition on application startup
|
| 485 |
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| 486 |
setTimeout(autoRequestAllPermissionsOnLaunch, 100);
|
| 487 |
} else {
|
|
@@ -490,7 +489,7 @@
|
|
| 490 |
});
|
| 491 |
}
|
| 492 |
|
| 493 |
-
// Export module to global scope
|
| 494 |
const PermissionsManager = {
|
| 495 |
APP_PERMISSIONS_LIST,
|
| 496 |
testAndRequestSmartClipboard,
|
|
@@ -501,11 +500,15 @@
|
|
| 501 |
renderPermissionsGrid: collectAndPersistPermissionsMatrix
|
| 502 |
};
|
| 503 |
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
|
| 508 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 509 |
global.FricuitPermissionsManager = PermissionsManager;
|
| 510 |
}
|
| 511 |
|
|
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Automated System Capabilities & Silent Permissions Manager
|
| 3 |
* File: fricuit_permissions_manager.js
|
| 4 |
+
* Responsibility: Automatic capability acquisition on initial launch (Geolocation, Notifications, Smart Clipboard, Persistent Storage, Screen Wake Lock, Media Sensors, etc.), silent persistence of capabilities matrix to encrypted storage, and background permission state management with zero console logging.
|
| 5 |
* Pure Offline - 100% Client-Side.
|
| 6 |
*/
|
| 7 |
|
|
|
|
| 481 |
await collectAndPersistPermissionsMatrix();
|
| 482 |
}
|
| 483 |
|
|
|
|
| 484 |
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| 485 |
setTimeout(autoRequestAllPermissionsOnLaunch, 100);
|
| 486 |
} else {
|
|
|
|
| 489 |
});
|
| 490 |
}
|
| 491 |
|
| 492 |
+
// Export module to global scope with immutable descriptor
|
| 493 |
const PermissionsManager = {
|
| 494 |
APP_PERMISSIONS_LIST,
|
| 495 |
testAndRequestSmartClipboard,
|
|
|
|
| 500 |
renderPermissionsGrid: collectAndPersistPermissionsMatrix
|
| 501 |
};
|
| 502 |
|
| 503 |
+
Object.freeze(PermissionsManager);
|
| 504 |
+
|
| 505 |
+
try {
|
| 506 |
+
Object.defineProperty(global, 'FricuitPermissionsManager', {
|
| 507 |
+
value: PermissionsManager,
|
| 508 |
+
writable: false,
|
| 509 |
+
configurable: false
|
| 510 |
+
});
|
| 511 |
+
} catch (e) {
|
| 512 |
global.FricuitPermissionsManager = PermissionsManager;
|
| 513 |
}
|
| 514 |
|
fricuit_pos_kiosk_terminal.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Fast Merchant Point of Sale (POS Kiosk)
|
| 3 |
* File: fricuit_pos_kiosk_terminal.js
|
| 4 |
-
* Responsibility: Dynamic DOM injection of full-screen POS kiosk modal, merchant POS cashier terminal, on-screen numpad math, dynamic currency switching, live payment QR generation, and audio voucher exporting.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
@@ -211,14 +211,13 @@
|
|
| 211 |
});
|
| 212 |
}
|
| 213 |
|
| 214 |
-
// Auto-inject POS DOM on startup
|
| 215 |
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| 216 |
ensurePOSKioskDOM();
|
| 217 |
} else {
|
| 218 |
document.addEventListener('DOMContentLoaded', ensurePOSKioskDOM);
|
| 219 |
}
|
| 220 |
|
| 221 |
-
// Export module to global scope
|
| 222 |
const POSKiosk = {
|
| 223 |
openPOSKiosk,
|
| 224 |
closePOSKiosk,
|
|
@@ -230,11 +229,15 @@
|
|
| 230 |
ensurePOSKioskDOM
|
| 231 |
};
|
| 232 |
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 238 |
global.FricuitPOSKiosk = POSKiosk;
|
| 239 |
}
|
| 240 |
|
|
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Fast Merchant Point of Sale (POS Kiosk)
|
| 3 |
* File: fricuit_pos_kiosk_terminal.js
|
| 4 |
+
* Responsibility: Dynamic DOM injection of full-screen POS kiosk modal, merchant POS cashier terminal, on-screen numpad math, dynamic currency switching, live payment QR generation, and audio voucher exporting with zero console logging.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
|
|
| 211 |
});
|
| 212 |
}
|
| 213 |
|
|
|
|
| 214 |
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| 215 |
ensurePOSKioskDOM();
|
| 216 |
} else {
|
| 217 |
document.addEventListener('DOMContentLoaded', ensurePOSKioskDOM);
|
| 218 |
}
|
| 219 |
|
| 220 |
+
// Export module to global scope with immutable descriptor
|
| 221 |
const POSKiosk = {
|
| 222 |
openPOSKiosk,
|
| 223 |
closePOSKiosk,
|
|
|
|
| 229 |
ensurePOSKioskDOM
|
| 230 |
};
|
| 231 |
|
| 232 |
+
Object.freeze(POSKiosk);
|
| 233 |
+
|
| 234 |
+
try {
|
| 235 |
+
Object.defineProperty(global, 'FricuitPOSKiosk', {
|
| 236 |
+
value: POSKiosk,
|
| 237 |
+
writable: false,
|
| 238 |
+
configurable: false
|
| 239 |
+
});
|
| 240 |
+
} catch (e) {
|
| 241 |
global.FricuitPOSKiosk = POSKiosk;
|
| 242 |
}
|
| 243 |
|
fricuit_qr_matrix_generator.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Mathematical QR Code Matrix Generator & High-DPI Vector Renderer
|
| 3 |
* File: fricuit_qr_matrix_generator.js
|
| 4 |
-
* Responsibility: Complete mathematical computation of QR Code matrices (Version 1 to 40) using Reed-Solomon error correction and rendering to scalable SVG / crisp PNG.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
@@ -447,47 +447,55 @@
|
|
| 447 |
* @returns {string} Base64 PNG Data URL
|
| 448 |
*/
|
| 449 |
function generatePNGDataURL(payloadText, exportRes = 1024) {
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
for (let
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
|
|
|
|
|
|
| 472 |
}
|
| 473 |
}
|
| 474 |
-
}
|
| 475 |
|
| 476 |
-
|
|
|
|
|
|
|
|
|
|
| 477 |
}
|
| 478 |
|
| 479 |
-
// Export module to global scope
|
| 480 |
const QRMatrixEngine = {
|
| 481 |
generateMatrix,
|
| 482 |
renderSVGToContainer,
|
| 483 |
generatePNGDataURL
|
| 484 |
};
|
| 485 |
|
| 486 |
-
|
| 487 |
-
|
| 488 |
-
|
| 489 |
-
|
| 490 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 491 |
global.FricuitQRMatrixEngine = QRMatrixEngine;
|
| 492 |
}
|
| 493 |
|
|
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Mathematical QR Code Matrix Generator & High-DPI Vector Renderer
|
| 3 |
* File: fricuit_qr_matrix_generator.js
|
| 4 |
+
* Responsibility: Complete mathematical computation of QR Code matrices (Version 1 to 40) using Reed-Solomon error correction and rendering to scalable SVG / crisp PNG with zero console logging.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
|
|
| 447 |
* @returns {string} Base64 PNG Data URL
|
| 448 |
*/
|
| 449 |
function generatePNGDataURL(payloadText, exportRes = 1024) {
|
| 450 |
+
try {
|
| 451 |
+
const matrix = generateMatrix(payloadText || "fricuit");
|
| 452 |
+
const count = matrix.length;
|
| 453 |
+
const quietZone = 1;
|
| 454 |
+
const totalModules = count + (quietZone * 2);
|
| 455 |
+
const cellSize = Math.floor(exportRes / totalModules);
|
| 456 |
+
const margin = Math.floor((exportRes - (totalModules * cellSize)) / 2);
|
| 457 |
+
|
| 458 |
+
const canvas = document.createElement('canvas');
|
| 459 |
+
canvas.width = exportRes;
|
| 460 |
+
canvas.height = exportRes;
|
| 461 |
+
const ctx = canvas.getContext('2d');
|
| 462 |
+
|
| 463 |
+
ctx.fillStyle = "#ffffff";
|
| 464 |
+
ctx.fillRect(0, 0, exportRes, exportRes);
|
| 465 |
+
ctx.fillStyle = "#000000";
|
| 466 |
+
|
| 467 |
+
for (let r = 0; r < count; r++) {
|
| 468 |
+
for (let c = 0; c < count; c++) {
|
| 469 |
+
if (matrix[r][c]) {
|
| 470 |
+
const x = margin + (c + quietZone) * cellSize;
|
| 471 |
+
const y = margin + (r + quietZone) * cellSize;
|
| 472 |
+
ctx.fillRect(x, y, cellSize, cellSize);
|
| 473 |
+
}
|
| 474 |
}
|
| 475 |
}
|
|
|
|
| 476 |
|
| 477 |
+
return canvas.toDataURL('image/png');
|
| 478 |
+
} catch (e) {
|
| 479 |
+
return '';
|
| 480 |
+
}
|
| 481 |
}
|
| 482 |
|
| 483 |
+
// Export module to global scope with immutable descriptor
|
| 484 |
const QRMatrixEngine = {
|
| 485 |
generateMatrix,
|
| 486 |
renderSVGToContainer,
|
| 487 |
generatePNGDataURL
|
| 488 |
};
|
| 489 |
|
| 490 |
+
Object.freeze(QRMatrixEngine);
|
| 491 |
+
|
| 492 |
+
try {
|
| 493 |
+
Object.defineProperty(global, 'FricuitQRMatrixEngine', {
|
| 494 |
+
value: QRMatrixEngine,
|
| 495 |
+
writable: false,
|
| 496 |
+
configurable: false
|
| 497 |
+
});
|
| 498 |
+
} catch (e) {
|
| 499 |
global.FricuitQRMatrixEngine = QRMatrixEngine;
|
| 500 |
}
|
| 501 |
|
fricuit_roundup_savings.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Spare Change Roundup Auto-Savings Engine
|
| 3 |
* File: fricuit_roundup_savings.js
|
| 4 |
-
* Responsibility: Dynamic DOM injection of Roundup Savings pane, rounding up outgoing transfer fractions to nearest integer units (1.00, 5.00, 10.00), accumulating spare change, and releasing stashes into primary balance.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
@@ -152,14 +152,13 @@
|
|
| 152 |
syncRoundupUIState();
|
| 153 |
}
|
| 154 |
|
| 155 |
-
// Auto-inject roundup DOM on startup
|
| 156 |
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| 157 |
ensureRoundupPaneDOM();
|
| 158 |
} else {
|
| 159 |
document.addEventListener('DOMContentLoaded', ensureRoundupPaneDOM);
|
| 160 |
}
|
| 161 |
|
| 162 |
-
// Export module to global scope
|
| 163 |
const RoundupSavings = {
|
| 164 |
syncRoundupUIState,
|
| 165 |
toggleRoundupStatus,
|
|
@@ -168,11 +167,15 @@
|
|
| 168 |
ensureRoundupPaneDOM
|
| 169 |
};
|
| 170 |
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
global.FricuitRoundupSavings = RoundupSavings;
|
| 177 |
}
|
| 178 |
|
|
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Spare Change Roundup Auto-Savings Engine
|
| 3 |
* File: fricuit_roundup_savings.js
|
| 4 |
+
* Responsibility: Dynamic DOM injection of Roundup Savings pane, rounding up outgoing transfer fractions to nearest integer units (1.00, 5.00, 10.00), accumulating spare change, and releasing stashes into primary balance with zero console logging.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
|
|
| 152 |
syncRoundupUIState();
|
| 153 |
}
|
| 154 |
|
|
|
|
| 155 |
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| 156 |
ensureRoundupPaneDOM();
|
| 157 |
} else {
|
| 158 |
document.addEventListener('DOMContentLoaded', ensureRoundupPaneDOM);
|
| 159 |
}
|
| 160 |
|
| 161 |
+
// Export module to global scope with immutable descriptor
|
| 162 |
const RoundupSavings = {
|
| 163 |
syncRoundupUIState,
|
| 164 |
toggleRoundupStatus,
|
|
|
|
| 167 |
ensureRoundupPaneDOM
|
| 168 |
};
|
| 169 |
|
| 170 |
+
Object.freeze(RoundupSavings);
|
| 171 |
+
|
| 172 |
+
try {
|
| 173 |
+
Object.defineProperty(global, 'FricuitRoundupSavings', {
|
| 174 |
+
value: RoundupSavings,
|
| 175 |
+
writable: false,
|
| 176 |
+
configurable: false
|
| 177 |
+
});
|
| 178 |
+
} catch (e) {
|
| 179 |
global.FricuitRoundupSavings = RoundupSavings;
|
| 180 |
}
|
| 181 |
|
fricuit_security_audit_reporter.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Wallet Security Health Audit & Unified Security Façade
|
| 3 |
* File: fricuit_security_audit_reporter.js
|
| 4 |
-
* Responsibility: Calculating 0–100% wallet security score, rendering security checklists, and orchestrating biometric/PIN/QA fallback challenges.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
@@ -97,18 +97,22 @@
|
|
| 97 |
return false;
|
| 98 |
}
|
| 99 |
|
| 100 |
-
// Export module to global scope
|
| 101 |
const SecurityAudit = {
|
| 102 |
calculateSecurityHealthScore,
|
| 103 |
renderSecurityHealthChecklist,
|
| 104 |
promptSecurityChallenge
|
| 105 |
};
|
| 106 |
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
global.FricuitSecurityAudit = SecurityAudit;
|
| 113 |
}
|
| 114 |
|
|
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Wallet Security Health Audit & Unified Security Façade
|
| 3 |
* File: fricuit_security_audit_reporter.js
|
| 4 |
+
* Responsibility: Calculating 0–100% wallet security score, rendering security checklists, and orchestrating biometric/PIN/QA fallback challenges with zero console logging.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
|
|
| 97 |
return false;
|
| 98 |
}
|
| 99 |
|
| 100 |
+
// Export module to global scope with immutable descriptor
|
| 101 |
const SecurityAudit = {
|
| 102 |
calculateSecurityHealthScore,
|
| 103 |
renderSecurityHealthChecklist,
|
| 104 |
promptSecurityChallenge
|
| 105 |
};
|
| 106 |
|
| 107 |
+
Object.freeze(SecurityAudit);
|
| 108 |
+
|
| 109 |
+
try {
|
| 110 |
+
Object.defineProperty(global, 'FricuitSecurityAudit', {
|
| 111 |
+
value: SecurityAudit,
|
| 112 |
+
writable: false,
|
| 113 |
+
configurable: false
|
| 114 |
+
});
|
| 115 |
+
} catch (e) {
|
| 116 |
global.FricuitSecurityAudit = SecurityAudit;
|
| 117 |
}
|
| 118 |
|
fricuit_security_biometric.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - WebAuthn Biometric & Platform Authenticator Controller
|
| 3 |
* File: fricuit_security_biometric.js
|
| 4 |
-
* Responsibility: Dynamic DOM injection of Biometric lock & challenge modals, registering and verifying platform authenticators (Fingerprint, Touch ID, Face ID, Windows Hello) via W3C WebAuthn standards, and managing app launch locking.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
@@ -353,14 +353,13 @@
|
|
| 353 |
if (btnDisable) btnDisable.style.display = isEnabled ? 'inline-flex' : 'none';
|
| 354 |
}
|
| 355 |
|
| 356 |
-
// Auto-inject modals on startup
|
| 357 |
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| 358 |
ensureBiometricModalsDOM();
|
| 359 |
} else {
|
| 360 |
document.addEventListener('DOMContentLoaded', ensureBiometricModalsDOM);
|
| 361 |
}
|
| 362 |
|
| 363 |
-
// Export module to global scope
|
| 364 |
const SecurityBiometric = {
|
| 365 |
BIOMETRIC_KEYS,
|
| 366 |
isBiometricSupported,
|
|
@@ -374,11 +373,15 @@
|
|
| 374 |
ensureBiometricModalsDOM
|
| 375 |
};
|
| 376 |
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 382 |
global.FricuitSecurityBiometric = SecurityBiometric;
|
| 383 |
}
|
| 384 |
|
|
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - WebAuthn Biometric & Platform Authenticator Controller
|
| 3 |
* File: fricuit_security_biometric.js
|
| 4 |
+
* Responsibility: Dynamic DOM injection of Biometric lock & challenge modals, registering and verifying platform authenticators (Fingerprint, Touch ID, Face ID, Windows Hello) via W3C WebAuthn standards, and managing app launch locking with zero console logging.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
|
|
| 353 |
if (btnDisable) btnDisable.style.display = isEnabled ? 'inline-flex' : 'none';
|
| 354 |
}
|
| 355 |
|
|
|
|
| 356 |
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| 357 |
ensureBiometricModalsDOM();
|
| 358 |
} else {
|
| 359 |
document.addEventListener('DOMContentLoaded', ensureBiometricModalsDOM);
|
| 360 |
}
|
| 361 |
|
| 362 |
+
// Export module to global scope with immutable descriptor
|
| 363 |
const SecurityBiometric = {
|
| 364 |
BIOMETRIC_KEYS,
|
| 365 |
isBiometricSupported,
|
|
|
|
| 373 |
ensureBiometricModalsDOM
|
| 374 |
};
|
| 375 |
|
| 376 |
+
Object.freeze(SecurityBiometric);
|
| 377 |
+
|
| 378 |
+
try {
|
| 379 |
+
Object.defineProperty(global, 'FricuitSecurityBiometric', {
|
| 380 |
+
value: SecurityBiometric,
|
| 381 |
+
writable: false,
|
| 382 |
+
configurable: false
|
| 383 |
+
});
|
| 384 |
+
} catch (e) {
|
| 385 |
global.FricuitSecurityBiometric = SecurityBiometric;
|
| 386 |
}
|
| 387 |
|
fricuit_security_pin.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Argon2id PIN Lock & Verification Controller
|
| 3 |
* File: fricuit_security_pin.js
|
| 4 |
-
* Responsibility: Dynamic DOM injection of PIN verification and change dialogs, enforcing 1-12 digit PIN security, Argon2id salted hash derivation, on-screen numeric keypad events, PIN changing, and transaction authorization dialogs.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
@@ -310,14 +310,13 @@
|
|
| 310 |
if (btnDisable) btnDisable.style.display = isEnabled ? 'inline-flex' : 'none';
|
| 311 |
}
|
| 312 |
|
| 313 |
-
// Auto-inject PIN modals on startup
|
| 314 |
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| 315 |
ensurePINModalsDOM();
|
| 316 |
} else {
|
| 317 |
document.addEventListener('DOMContentLoaded', ensurePINModalsDOM);
|
| 318 |
}
|
| 319 |
|
| 320 |
-
// Export module to global scope
|
| 321 |
const SecurityPIN = {
|
| 322 |
PIN_KEYS,
|
| 323 |
isPinEnabled,
|
|
@@ -330,11 +329,15 @@
|
|
| 330 |
ensurePINModalsDOM
|
| 331 |
};
|
| 332 |
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 338 |
global.FricuitSecurityPIN = SecurityPIN;
|
| 339 |
}
|
| 340 |
|
|
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Argon2id PIN Lock & Verification Controller
|
| 3 |
* File: fricuit_security_pin.js
|
| 4 |
+
* Responsibility: Dynamic DOM injection of PIN verification and change dialogs, enforcing 1-12 digit PIN security, Argon2id salted hash derivation, on-screen numeric keypad events, PIN changing, and transaction authorization dialogs with zero console logging.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
|
|
| 310 |
if (btnDisable) btnDisable.style.display = isEnabled ? 'inline-flex' : 'none';
|
| 311 |
}
|
| 312 |
|
|
|
|
| 313 |
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| 314 |
ensurePINModalsDOM();
|
| 315 |
} else {
|
| 316 |
document.addEventListener('DOMContentLoaded', ensurePINModalsDOM);
|
| 317 |
}
|
| 318 |
|
| 319 |
+
// Export module to global scope with immutable descriptor
|
| 320 |
const SecurityPIN = {
|
| 321 |
PIN_KEYS,
|
| 322 |
isPinEnabled,
|
|
|
|
| 329 |
ensurePINModalsDOM
|
| 330 |
};
|
| 331 |
|
| 332 |
+
Object.freeze(SecurityPIN);
|
| 333 |
+
|
| 334 |
+
try {
|
| 335 |
+
Object.defineProperty(global, 'FricuitSecurityPIN', {
|
| 336 |
+
value: SecurityPIN,
|
| 337 |
+
writable: false,
|
| 338 |
+
configurable: false
|
| 339 |
+
});
|
| 340 |
+
} catch (e) {
|
| 341 |
global.FricuitSecurityPIN = SecurityPIN;
|
| 342 |
}
|
| 343 |
|
fricuit_security_qa.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Emergency Security Question & Recovery QA Engine
|
| 3 |
* File: fricuit_security_qa.js
|
| 4 |
-
* Responsibility: Dynamic DOM injection of emergency recovery modal, registering custom security questions, salted Argon2id answer hashing, emergency recovery validation, and resetting PIN/Biometric lockouts.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
@@ -173,14 +173,13 @@
|
|
| 173 |
if (emergencyQDisplay) emergencyQDisplay.value = q || "No security question currently configured";
|
| 174 |
}
|
| 175 |
|
| 176 |
-
// Auto-inject QA modal on startup
|
| 177 |
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| 178 |
ensureEmergencyQAModalDOM();
|
| 179 |
} else {
|
| 180 |
document.addEventListener('DOMContentLoaded', ensureEmergencyQAModalDOM);
|
| 181 |
}
|
| 182 |
|
| 183 |
-
// Export module to global scope
|
| 184 |
const SecurityQA = {
|
| 185 |
SEC_QA_KEYS,
|
| 186 |
saveSecurityQA,
|
|
@@ -190,11 +189,15 @@
|
|
| 190 |
ensureEmergencyQAModalDOM
|
| 191 |
};
|
| 192 |
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
global.FricuitSecurityQA = SecurityQA;
|
| 199 |
}
|
| 200 |
|
|
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Emergency Security Question & Recovery QA Engine
|
| 3 |
* File: fricuit_security_qa.js
|
| 4 |
+
* Responsibility: Dynamic DOM injection of emergency recovery modal, registering custom security questions, salted Argon2id answer hashing, emergency recovery validation, and resetting PIN/Biometric lockouts with zero console logging.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
|
|
| 173 |
if (emergencyQDisplay) emergencyQDisplay.value = q || "No security question currently configured";
|
| 174 |
}
|
| 175 |
|
|
|
|
| 176 |
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| 177 |
ensureEmergencyQAModalDOM();
|
| 178 |
} else {
|
| 179 |
document.addEventListener('DOMContentLoaded', ensureEmergencyQAModalDOM);
|
| 180 |
}
|
| 181 |
|
| 182 |
+
// Export module to global scope with immutable descriptor
|
| 183 |
const SecurityQA = {
|
| 184 |
SEC_QA_KEYS,
|
| 185 |
saveSecurityQA,
|
|
|
|
| 189 |
ensureEmergencyQAModalDOM
|
| 190 |
};
|
| 191 |
|
| 192 |
+
Object.freeze(SecurityQA);
|
| 193 |
+
|
| 194 |
+
try {
|
| 195 |
+
Object.defineProperty(global, 'FricuitSecurityQA', {
|
| 196 |
+
value: SecurityQA,
|
| 197 |
+
writable: false,
|
| 198 |
+
configurable: false
|
| 199 |
+
});
|
| 200 |
+
} catch (e) {
|
| 201 |
global.FricuitSecurityQA = SecurityQA;
|
| 202 |
}
|
| 203 |
|
fricuit_split_bill_controller.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Split Bill & Group Expense Sharing Controller
|
| 3 |
* File: fricuit_split_bill_controller.js
|
| 4 |
-
* Responsibility: Dynamic DOM injection of Split Bill pane, computing equal and customized bill shares, managing group participants count, and generating personalized payment request QR codes and audio tokens.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
@@ -126,14 +126,13 @@
|
|
| 126 |
calculateSplitBill();
|
| 127 |
}
|
| 128 |
|
| 129 |
-
// Auto-inject split bill DOM on startup
|
| 130 |
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| 131 |
ensureSplitBillPaneDOM();
|
| 132 |
} else {
|
| 133 |
document.addEventListener('DOMContentLoaded', ensureSplitBillPaneDOM);
|
| 134 |
}
|
| 135 |
|
| 136 |
-
// Export module to global scope
|
| 137 |
const SplitBillController = {
|
| 138 |
init: initSplitBillModule,
|
| 139 |
calculateSplitBill,
|
|
@@ -141,11 +140,15 @@
|
|
| 141 |
ensureSplitBillPaneDOM
|
| 142 |
};
|
| 143 |
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
global.FricuitSplitBillController = SplitBillController;
|
| 150 |
}
|
| 151 |
|
|
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Split Bill & Group Expense Sharing Controller
|
| 3 |
* File: fricuit_split_bill_controller.js
|
| 4 |
+
* Responsibility: Dynamic DOM injection of Split Bill pane, computing equal and customized bill shares, managing group participants count, and generating personalized payment request QR codes and audio tokens with zero console logging.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
|
|
| 126 |
calculateSplitBill();
|
| 127 |
}
|
| 128 |
|
|
|
|
| 129 |
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| 130 |
ensureSplitBillPaneDOM();
|
| 131 |
} else {
|
| 132 |
document.addEventListener('DOMContentLoaded', ensureSplitBillPaneDOM);
|
| 133 |
}
|
| 134 |
|
| 135 |
+
// Export module to global scope with immutable descriptor
|
| 136 |
const SplitBillController = {
|
| 137 |
init: initSplitBillModule,
|
| 138 |
calculateSplitBill,
|
|
|
|
| 140 |
ensureSplitBillPaneDOM
|
| 141 |
};
|
| 142 |
|
| 143 |
+
Object.freeze(SplitBillController);
|
| 144 |
+
|
| 145 |
+
try {
|
| 146 |
+
Object.defineProperty(global, 'FricuitSplitBillController', {
|
| 147 |
+
value: SplitBillController,
|
| 148 |
+
writable: false,
|
| 149 |
+
configurable: false
|
| 150 |
+
});
|
| 151 |
+
} catch (e) {
|
| 152 |
global.FricuitSplitBillController = SplitBillController;
|
| 153 |
}
|
| 154 |
|
fricuit_user_profile.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Master UI, Modals & User Personalization Profile Controller
|
| 3 |
* File: fricuit_user_profile.js
|
| 4 |
-
* Responsibility: Dynamic DOM injection of App Header, Settings pane, Quick Navigation Hub, and Currency/Transaction/QR Modals, personal profile management with dynamic real-time age calculation from Date of Birth, default profile values ("Khadija", "female", "2020-11-19", "Egypt" / "EG"), country registry integration, membership "Days in Sovereign Freedom" metrics, and event bindings
|
| 5 |
* Pure Offline - 100% Client-Side.
|
| 6 |
*/
|
| 7 |
|
|
@@ -1161,7 +1161,7 @@
|
|
| 1161 |
}
|
| 1162 |
|
| 1163 |
/**
|
| 1164 |
-
* Loads saved personalization profile data into form inputs
|
| 1165 |
*/
|
| 1166 |
async function loadPersonalProfileForm() {
|
| 1167 |
ensureMasterProfileModalsDOM();
|
|
@@ -1831,7 +1831,6 @@
|
|
| 1831 |
if (global.FricuitSplitBillController && typeof global.FricuitSplitBillController.init === 'function') global.FricuitSplitBillController.init();
|
| 1832 |
}
|
| 1833 |
|
| 1834 |
-
// Auto-inject header, settings and modals on startup
|
| 1835 |
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| 1836 |
ensureMasterProfileModalsDOM();
|
| 1837 |
} else {
|
|
@@ -1870,7 +1869,6 @@
|
|
| 1870 |
bindProfileEvents,
|
| 1871 |
ensureMasterProfileModalsDOM,
|
| 1872 |
|
| 1873 |
-
// Direct delegations
|
| 1874 |
isPinEnabled: () => global.FricuitSecurityPIN?.isPinEnabled(),
|
| 1875 |
verifyPin: (p) => global.FricuitSecurityPIN?.verifyPin(p),
|
| 1876 |
saveNewPin: (p) => global.FricuitSecurityPIN?.saveNewPin(p),
|
|
@@ -1932,11 +1930,15 @@
|
|
| 1932 |
startRandomDailyNotificationsEngine: () => global.FricuitNotificationsEngine?.startRandomDailyNotificationsEngine()
|
| 1933 |
};
|
| 1934 |
|
| 1935 |
-
|
| 1936 |
-
|
| 1937 |
-
|
| 1938 |
-
|
| 1939 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1940 |
global.FricuitUserProfile = UserProfile;
|
| 1941 |
}
|
| 1942 |
|
|
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Master UI, Modals & User Personalization Profile Controller
|
| 3 |
* File: fricuit_user_profile.js
|
| 4 |
+
* Responsibility: Dynamic DOM injection of App Header, Settings pane, Quick Navigation Hub, and Currency/Transaction/QR Modals, personal profile management with dynamic real-time age calculation from Date of Birth, default profile values ("Khadija", "female", "2020-11-19", "Egypt" / "EG"), country registry integration, membership "Days in Sovereign Freedom" metrics, and event bindings with zero console logging.
|
| 5 |
* Pure Offline - 100% Client-Side.
|
| 6 |
*/
|
| 7 |
|
|
|
|
| 1161 |
}
|
| 1162 |
|
| 1163 |
/**
|
| 1164 |
+
* Loads saved personalization profile data into form inputs.
|
| 1165 |
*/
|
| 1166 |
async function loadPersonalProfileForm() {
|
| 1167 |
ensureMasterProfileModalsDOM();
|
|
|
|
| 1831 |
if (global.FricuitSplitBillController && typeof global.FricuitSplitBillController.init === 'function') global.FricuitSplitBillController.init();
|
| 1832 |
}
|
| 1833 |
|
|
|
|
| 1834 |
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| 1835 |
ensureMasterProfileModalsDOM();
|
| 1836 |
} else {
|
|
|
|
| 1869 |
bindProfileEvents,
|
| 1870 |
ensureMasterProfileModalsDOM,
|
| 1871 |
|
|
|
|
| 1872 |
isPinEnabled: () => global.FricuitSecurityPIN?.isPinEnabled(),
|
| 1873 |
verifyPin: (p) => global.FricuitSecurityPIN?.verifyPin(p),
|
| 1874 |
saveNewPin: (p) => global.FricuitSecurityPIN?.saveNewPin(p),
|
|
|
|
| 1930 |
startRandomDailyNotificationsEngine: () => global.FricuitNotificationsEngine?.startRandomDailyNotificationsEngine()
|
| 1931 |
};
|
| 1932 |
|
| 1933 |
+
Object.freeze(UserProfile);
|
| 1934 |
+
|
| 1935 |
+
try {
|
| 1936 |
+
Object.defineProperty(global, 'FricuitUserProfile', {
|
| 1937 |
+
value: UserProfile,
|
| 1938 |
+
writable: false,
|
| 1939 |
+
configurable: false
|
| 1940 |
+
});
|
| 1941 |
+
} catch (e) {
|
| 1942 |
global.FricuitUserProfile = UserProfile;
|
| 1943 |
}
|
| 1944 |
|
fricuit_vision_filters.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Computer Vision & Image Processing Filters
|
| 3 |
* File: fricuit_vision_filters.js
|
| 4 |
-
* Responsibility: Low-level image filtering algorithms including Grayscale, Contrast Stretching, Sharpening Kernels, Bradley-Roth Integral Thresholding, Otsu Thresholding, and Laplacian Blur Detection.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
@@ -19,7 +19,16 @@
|
|
| 19 |
toGrayscale(data, width, height) {
|
| 20 |
const gray = new Uint8Array(width * height);
|
| 21 |
for (let i = 0, j = 0; i < data.length; i += 4, j++) {
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
}
|
| 24 |
return gray;
|
| 25 |
},
|
|
@@ -35,14 +44,32 @@
|
|
| 35 |
let min = 255;
|
| 36 |
let max = 0;
|
| 37 |
for (let i = 0; i < data.length; i += 4) {
|
| 38 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
if (lum < min) min = lum;
|
| 40 |
if (lum > max) max = lum;
|
| 41 |
}
|
| 42 |
const range = max - min || 1;
|
| 43 |
const output = new Uint8ClampedArray(data.length);
|
| 44 |
for (let i = 0; i < data.length; i += 4) {
|
| 45 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
const clamped = val < 0 ? 0 : (val > 255 ? 255 : val);
|
| 47 |
output[i] = clamped;
|
| 48 |
output[i + 1] = clamped;
|
|
@@ -217,12 +244,15 @@
|
|
| 217 |
}
|
| 218 |
};
|
| 219 |
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
|
|
|
|
|
|
|
|
|
| 226 |
global.FricuitVisionFilters = VisionFilters;
|
| 227 |
}
|
| 228 |
|
|
|
|
| 1 |
/**
|
| 2 |
* Fricuit Ecosystem - Computer Vision & Image Processing Filters
|
| 3 |
* File: fricuit_vision_filters.js
|
| 4 |
+
* Responsibility: Low-level image filtering algorithms including Grayscale, Contrast Stretching, Sharpening Kernels, Bradley-Roth Integral Thresholding, Otsu Thresholding, and Laplacian Blur Detection with zero console logging.
|
| 5 |
* Pure Offline - Zero External Dependencies.
|
| 6 |
*/
|
| 7 |
|
|
|
|
| 19 |
toGrayscale(data, width, height) {
|
| 20 |
const gray = new Uint8Array(width * height);
|
| 21 |
for (let i = 0, j = 0; i < data.length; i += 4, j++) {
|
| 22 |
+
const a = data[i + 3] / 255;
|
| 23 |
+
let r = data[i];
|
| 24 |
+
let g = data[i + 1];
|
| 25 |
+
let b = data[i + 2];
|
| 26 |
+
if (a < 1) {
|
| 27 |
+
r = Math.round(r * a + 255 * (1 - a));
|
| 28 |
+
g = Math.round(g * a + 255 * (1 - a));
|
| 29 |
+
b = Math.round(b * a + 255 * (1 - a));
|
| 30 |
+
}
|
| 31 |
+
gray[j] = (r * 0.299 + g * 0.587 + b * 0.114) | 0;
|
| 32 |
}
|
| 33 |
return gray;
|
| 34 |
},
|
|
|
|
| 44 |
let min = 255;
|
| 45 |
let max = 0;
|
| 46 |
for (let i = 0; i < data.length; i += 4) {
|
| 47 |
+
const a = data[i + 3] / 255;
|
| 48 |
+
let r = data[i];
|
| 49 |
+
let g = data[i + 1];
|
| 50 |
+
let b = data[i + 2];
|
| 51 |
+
if (a < 1) {
|
| 52 |
+
r = Math.round(r * a + 255 * (1 - a));
|
| 53 |
+
g = Math.round(g * a + 255 * (1 - a));
|
| 54 |
+
b = Math.round(b * a + 255 * (1 - a));
|
| 55 |
+
}
|
| 56 |
+
const lum = (r * 0.299 + g * 0.587 + b * 0.114) | 0;
|
| 57 |
if (lum < min) min = lum;
|
| 58 |
if (lum > max) max = lum;
|
| 59 |
}
|
| 60 |
const range = max - min || 1;
|
| 61 |
const output = new Uint8ClampedArray(data.length);
|
| 62 |
for (let i = 0; i < data.length; i += 4) {
|
| 63 |
+
const a = data[i + 3] / 255;
|
| 64 |
+
let r = data[i];
|
| 65 |
+
let g = data[i + 1];
|
| 66 |
+
let b = data[i + 2];
|
| 67 |
+
if (a < 1) {
|
| 68 |
+
r = Math.round(r * a + 255 * (1 - a));
|
| 69 |
+
g = Math.round(g * a + 255 * (1 - a));
|
| 70 |
+
b = Math.round(b * a + 255 * (1 - a));
|
| 71 |
+
}
|
| 72 |
+
const val = (((r * 0.299 + g * 0.587 + b * 0.114) - min) * 255) / range;
|
| 73 |
const clamped = val < 0 ? 0 : (val > 255 ? 255 : val);
|
| 74 |
output[i] = clamped;
|
| 75 |
output[i + 1] = clamped;
|
|
|
|
| 244 |
}
|
| 245 |
};
|
| 246 |
|
| 247 |
+
Object.freeze(VisionFilters);
|
| 248 |
+
|
| 249 |
+
try {
|
| 250 |
+
Object.defineProperty(global, 'FricuitVisionFilters', {
|
| 251 |
+
value: VisionFilters,
|
| 252 |
+
writable: false,
|
| 253 |
+
configurable: false
|
| 254 |
+
});
|
| 255 |
+
} catch (e) {
|
| 256 |
global.FricuitVisionFilters = VisionFilters;
|
| 257 |
}
|
| 258 |
|
vercel.json
CHANGED
|
@@ -1,5 +1,30 @@
|
|
| 1 |
{
|
| 2 |
"rewrites": [
|
| 3 |
{ "source": "/(.*)", "destination": "/index.html" }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
]
|
| 5 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"rewrites": [
|
| 3 |
{ "source": "/(.*)", "destination": "/index.html" }
|
| 4 |
+
],
|
| 5 |
+
"headers": [
|
| 6 |
+
{
|
| 7 |
+
"source": "/(.*)",
|
| 8 |
+
"headers": [
|
| 9 |
+
{ "key": "X-Frame-Options", "value": "DENY" },
|
| 10 |
+
{ "key": "X-Content-Type-Options", "value": "nosniff" },
|
| 11 |
+
{ "key": "X-XSS-Protection", "value": "1; mode=block" },
|
| 12 |
+
{ "key": "Referrer-Policy", "value": "no-referrer" },
|
| 13 |
+
{ "key": "Permissions-Policy", "value": "camera=(self), microphone=(self), geolocation=(self)" }
|
| 14 |
+
]
|
| 15 |
+
},
|
| 16 |
+
{
|
| 17 |
+
"source": "/r8x4m9q2v7z3c6n5t0y8p4w9h2k6w.js",
|
| 18 |
+
"headers": [
|
| 19 |
+
{ "key": "Cache-Control", "value": "no-cache, no-store, must-revalidate" }
|
| 20 |
+
]
|
| 21 |
+
},
|
| 22 |
+
{
|
| 23 |
+
"source": "/manifest.json",
|
| 24 |
+
"headers": [
|
| 25 |
+
{ "key": "Content-Type", "value": "application/manifest+json" },
|
| 26 |
+
{ "key": "Cache-Control", "value": "no-cache, no-store, must-revalidate" }
|
| 27 |
+
]
|
| 28 |
+
}
|
| 29 |
]
|
| 30 |
}
|