piclets-server / verification_helper.js
Fraser's picture
secure-ish
542f3b7
/**
* Piclet Verification Helper for Frontend
* Use this in your Svelte game to generate verified Piclets
*/
// This should match the server's secret key
const PICLET_SECRET_KEY = "piclets-dev-key-change-in-production";
/**
* Create HMAC-SHA256 signature (requires crypto-js or similar)
* For browser compatibility, you'll need to include crypto-js:
* npm install crypto-js
* or include via CDN: https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js
*/
async function createHMAC(message, key) {
// Browser-native crypto API version (modern browsers)
if (window.crypto && window.crypto.subtle) {
const encoder = new TextEncoder();
const keyData = encoder.encode(key);
const messageData = encoder.encode(message);
const cryptoKey = await window.crypto.subtle.importKey(
'raw',
keyData,
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
const signature = await window.crypto.subtle.sign('HMAC', cryptoKey, messageData);
const hashArray = Array.from(new Uint8Array(signature));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
// Fallback: Use crypto-js if available
if (window.CryptoJS) {
return CryptoJS.HmacSHA256(message, key).toString();
}
throw new Error("No crypto implementation available. Include crypto-js or use modern browser.");
}
/**
* Create verification data for a Piclet
* Call this when generating a Piclet in your game
*/
async function createPicletVerification(picletData, imageCaption = "", conceptString = "") {
const timestamp = Math.floor(Date.now() / 1000);
// Core data used for signature
const coreData = {
name: picletData.name || "",
primaryType: picletData.primaryType || "",
baseStats: picletData.baseStats || {},
movepool: picletData.movepool || [],
timestamp: timestamp
};
// Generation metadata
const generationData = {
image_caption: imageCaption,
concept_string: conceptString,
generation_method: "official_app"
};
// Add generation data to core data
coreData.generation_data = generationData;
// Create deterministic JSON string
const dataString = JSON.stringify(coreData, null, 0);
// Create signature
const signature = await createHMAC(dataString, PICLET_SECRET_KEY);
return {
signature: signature,
timestamp: timestamp,
generation_data: generationData,
verification_version: "1.0"
};
}
/**
* Add verification to a Piclet before saving
* Use this in your PicletGenerator component
*/
async function verifyAndPreparePiclet(picletData, imageCaption = "", conceptString = "") {
try {
// Create verification data
const verification = await createPicletVerification(picletData, imageCaption, conceptString);
// Add verification to Piclet data
const verifiedPiclet = {
...picletData,
verification: verification
};
return {
success: true,
piclet: verifiedPiclet,
signature: verification.signature
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
/**
* Save a verified Piclet to the server
* Use this instead of direct API calls
*/
async function saveVerifiedPiclet(gradioClient, picletData, imageFile = null, imageCaption = "", conceptString = "") {
try {
// Create verified Piclet
const verificationResult = await verifyAndPreparePiclet(picletData, imageCaption, conceptString);
if (!verificationResult.success) {
return {
success: false,
error: `Verification failed: ${verificationResult.error}`
};
}
// Save to server
const result = await gradioClient.predict("/save_piclet_api", [
JSON.stringify(verificationResult.piclet),
verificationResult.signature,
imageFile
]);
return JSON.parse(result.data[0]);
} catch (error) {
return {
success: false,
error: `Save failed: ${error.message}`
};
}
}
/**
* Example usage in your Svelte component:
*
* // In PicletGenerator.svelte or similar component
* import { saveVerifiedPiclet } from './verification_helper.js';
*
* async function savePiclet() {
* const picletData = {
* name: generatedName,
* primaryType: detectedType,
* baseStats: calculatedStats,
* movepool: generatedMoves,
* // ... other data
* };
*
* const result = await saveVerifiedPiclet(
* gradioClient,
* picletData,
* uploadedImageFile,
* imageCaption,
* conceptString
* );
*
* if (result.success) {
* console.log(`Piclet saved with ID: ${result.piclet_id}`);
* } else {
* console.error(`Save failed: ${result.error}`);
* }
* }
*/
// Export for ES modules
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
createPicletVerification,
verifyAndPreparePiclet,
saveVerifiedPiclet
};
}
// Global functions for script tag usage
if (typeof window !== 'undefined') {
window.PicletVerification = {
createPicletVerification,
verifyAndPreparePiclet,
saveVerifiedPiclet
};
}