@ -155,26 +155,40 @@ export class ElectronPlatformService extends CapacitorPlatformService {
* @returns Promise that resolves when the file is saved
* @returns Promise that resolves when the file is saved
* /
* /
async saveToDevice ( fileName : string , content : string ) : Promise < void > {
async saveToDevice ( fileName : string , content : string ) : Promise < void > {
try {
// Ensure content is valid JSON
try {
JSON . parse ( content ) ;
} catch {
throw new Error ( 'Content must be valid JSON' ) ;
}
// Generate unique filename
const uniqueFileName = this . generateUniqueFileNameElectron ( fileName ) ;
logger . info (
logger . info (
` [ElectronPlatformService] Using native IPC for direct file save: ${ fileName } ` ,
` [ElectronPlatformService] Using native IPC for direct file save ` ,
uniqueFileName ,
) ;
) ;
try {
// Check if we're running in Electron with the API available
// Check if we're running in Electron with the API available
if ( typeof window !== "undefined" && window . electronAPI ) {
if ( typeof window !== "undefined" && window . electronAPI ) {
// Use the native Electron IPC API for file exports
// Use the native Electron IPC API for file exports
const result = await window . electronAPI . exportData ( fileName , content ) ;
const result = await window . electronAPI . exportData ( uniqueF ileName, content ) ;
if ( result . success ) {
if ( result . success ) {
logger . info (
logger . info (
` [ElectronPlatformService] File saved successfully to: ${ result . path } ` ,
` [ElectronPlatformService] File saved successfully to ` ,
result . path ,
) ;
) ;
logger . info (
logger . info (
` [ElectronPlatformService] File saved to Downloads folder: ${ fileName } ` ,
` [ElectronPlatformService] File saved to Downloads folder ` ,
uniqueFileName ,
) ;
) ;
} else {
} else {
logger . error (
logger . error (
` [ElectronPlatformService] Native save failed: ${ result . error } ` ,
` [ElectronPlatformService] Native save failed ` ,
result . error ,
) ;
) ;
throw new Error ( ` Native file save failed: ${ result . error } ` ) ;
throw new Error ( ` Native file save failed: ${ result . error } ` ) ;
}
}
@ -188,7 +202,7 @@ export class ElectronPlatformService extends CapacitorPlatformService {
const url = URL . createObjectURL ( blob ) ;
const url = URL . createObjectURL ( blob ) ;
const downloadLink = document . createElement ( "a" ) ;
const downloadLink = document . createElement ( "a" ) ;
downloadLink . href = url ;
downloadLink . href = url ;
downloadLink . download = f ileName;
downloadLink . download = uniqueF ileName;
downloadLink . style . display = "none" ;
downloadLink . style . display = "none" ;
document . body . appendChild ( downloadLink ) ;
document . body . appendChild ( downloadLink ) ;
@ -198,11 +212,12 @@ export class ElectronPlatformService extends CapacitorPlatformService {
setTimeout ( ( ) = > URL . revokeObjectURL ( url ) , 1000 ) ;
setTimeout ( ( ) = > URL . revokeObjectURL ( url ) , 1000 ) ;
logger . info (
logger . info (
` [ElectronPlatformService] Fallback download initiated: ${ fileName } ` ,
` [ElectronPlatformService] Fallback download initiated ` ,
uniqueFileName ,
) ;
) ;
}
}
} catch ( error ) {
} catch ( error ) {
logger . error ( "[ElectronPlatformService] File save failed: " , error ) ;
logger . error ( "[ElectronPlatformService] File save failed" , error ) ;
throw new Error ( ` Failed to save file: ${ error } ` ) ;
throw new Error ( ` Failed to save file: ${ error } ` ) ;
}
}
}
}
@ -216,27 +231,41 @@ export class ElectronPlatformService extends CapacitorPlatformService {
* @returns Promise that resolves when the file is saved
* @returns Promise that resolves when the file is saved
* /
* /
async saveAs ( fileName : string , content : string ) : Promise < void > {
async saveAs ( fileName : string , content : string ) : Promise < void > {
try {
// Ensure content is valid JSON
try {
JSON . parse ( content ) ;
} catch {
throw new Error ( 'Content must be valid JSON' ) ;
}
// Generate unique filename
const uniqueFileName = this . generateUniqueFileNameElectron ( fileName ) ;
logger . info (
logger . info (
` [ElectronPlatformService] Using native IPC for save as dialog: ${ fileName } ` ,
` [ElectronPlatformService] Using native IPC for save as dialog ` ,
uniqueFileName ,
) ;
) ;
try {
// Check if we're running in Electron with the API available
// Check if we're running in Electron with the API available
if ( typeof window !== "undefined" && window . electronAPI ) {
if ( typeof window !== "undefined" && window . electronAPI ) {
// Use the native Electron IPC API for file exports (same as saveToDevice for now)
// Use the native Electron IPC API for file exports (same as saveToDevice for now)
// TODO: Implement native save dialog when available
// TODO: Implement native save dialog when available
const result = await window . electronAPI . exportData ( fileName , content ) ;
const result = await window . electronAPI . exportData ( uniqueF ileName, content ) ;
if ( result . success ) {
if ( result . success ) {
logger . info (
logger . info (
` [ElectronPlatformService] File saved successfully to: ${ result . path } ` ,
` [ElectronPlatformService] File saved successfully to ` ,
result . path ,
) ;
) ;
logger . info (
logger . info (
` [ElectronPlatformService] File saved via save as: ${ fileName } ` ,
` [ElectronPlatformService] File saved via save as ` ,
uniqueFileName ,
) ;
) ;
} else {
} else {
logger . error (
logger . error (
` [ElectronPlatformService] Native save as failed: ${ result . error } ` ,
` [ElectronPlatformService] Native save as failed ` ,
result . error ,
) ;
) ;
throw new Error ( ` Native file save as failed: ${ result . error } ` ) ;
throw new Error ( ` Native file save as failed: ${ result . error } ` ) ;
}
}
@ -250,7 +279,7 @@ export class ElectronPlatformService extends CapacitorPlatformService {
const url = URL . createObjectURL ( blob ) ;
const url = URL . createObjectURL ( blob ) ;
const downloadLink = document . createElement ( "a" ) ;
const downloadLink = document . createElement ( "a" ) ;
downloadLink . href = url ;
downloadLink . href = url ;
downloadLink . download = f ileName;
downloadLink . download = uniqueF ileName;
downloadLink . style . display = "none" ;
downloadLink . style . display = "none" ;
document . body . appendChild ( downloadLink ) ;
document . body . appendChild ( downloadLink ) ;
@ -260,15 +289,93 @@ export class ElectronPlatformService extends CapacitorPlatformService {
setTimeout ( ( ) = > URL . revokeObjectURL ( url ) , 1000 ) ;
setTimeout ( ( ) = > URL . revokeObjectURL ( url ) , 1000 ) ;
logger . info (
logger . info (
` [ElectronPlatformService] Fallback download initiated: ${ fileName } ` ,
` [ElectronPlatformService] Fallback download initiated ` ,
uniqueFileName ,
) ;
) ;
}
}
} catch ( error ) {
} catch ( error ) {
logger . error ( "[ElectronPlatformService] File save as failed: " , error ) ;
logger . error ( "[ElectronPlatformService] File save as failed" , error ) ;
throw new Error ( ` Failed to save file as: ${ error } ` ) ;
throw new Error ( ` Failed to save file as: ${ error } ` ) ;
}
}
}
}
/ * *
* Generates unique filename with timestamp , hashed device ID , and counter
* /
private generateUniqueFileNameElectron ( baseName : string , counter = 0 ) : string {
const now = new Date ( ) ;
const timestamp = now . toISOString ( )
. replace ( /[:.]/g , '-' )
. replace ( 'T' , '_' )
. replace ( 'Z' , '' ) ;
const deviceIdHash = this . getHashedDeviceIdentifierElectron ( ) ;
const counterSuffix = counter > 0 ? ` _ ${ counter } ` : '' ;
const maxBaseLength = 45 ;
const truncatedBase = baseName . length > maxBaseLength
? baseName . substring ( 0 , maxBaseLength )
: baseName ;
const nameWithoutExt = truncatedBase . replace ( /\.json$/i , '' ) ;
const extension = '.json' ;
const devicePart = ` _ ${ deviceIdHash } ` ;
const timestampPart = ` _ ${ timestamp } ${ counterSuffix } ` ;
const totalLength = nameWithoutExt . length + devicePart . length + timestampPart . length + extension . length ;
if ( totalLength > 200 ) {
const availableLength = 200 - devicePart . length - timestampPart . length - extension . length ;
const finalBase = nameWithoutExt . substring ( 0 , Math . max ( 10 , availableLength ) ) ;
return ` ${ finalBase } ${ devicePart } ${ timestampPart } ${ extension } ` ;
}
return ` ${ nameWithoutExt } ${ devicePart } ${ timestampPart } ${ extension } ` ;
}
/ * *
* Gets hashed device identifier
* /
private getHashedDeviceIdentifierElectron ( ) : string {
try {
const deviceInfo = this . getDeviceInfoElectron ( ) ;
return this . hashStringElectron ( deviceInfo ) ;
} catch ( error ) {
return 'electron' ;
}
}
/ * *
* Gets device info string
* /
private getDeviceInfoElectron ( ) : string {
try {
// Use machine-specific information
const os = require ( 'os' ) ;
const hostname = os . hostname ( ) || 'unknown' ;
const platform = os . platform ( ) || 'unknown' ;
const arch = os . arch ( ) || 'unknown' ;
// Create device info string
return ` ${ platform } _ ${ hostname } _ ${ arch } ` ;
} catch ( error ) {
return 'electron' ;
}
}
/ * *
* Simple hash function for device ID
* /
private hashStringElectron ( str : string ) : string {
let hash = 0 ;
for ( let i = 0 ; i < str . length ; i ++ ) {
const char = str . charCodeAt ( i ) ;
hash = ( ( hash << 5 ) - hash ) + char ;
hash = hash & hash ;
}
return Math . abs ( hash ) . toString ( 16 ) . padStart ( 4 , '0' ) . substring ( 0 , 4 ) ;
}
/ * *
/ * *
* Checks if running on Capacitor platform .
* Checks if running on Capacitor platform .
*
*