Compare commits

..

8 Commits

15 changed files with 252 additions and 49 deletions

15
app.go
View File

@@ -44,7 +44,20 @@ func NewApp() *App {
// so we can call the runtime methods // so we can call the runtime methods
func (a *App) startup(ctx context.Context) { func (a *App) startup(ctx context.Context) {
a.ctx = ctx a.ctx = ctx
Log("Wails startup")
isViewer := false
for _, arg := range os.Args {
if strings.Contains(arg, "--view-image") || strings.Contains(arg, "--view-pdf") {
isViewer = true
break
}
}
if isViewer {
Log("Second instance launch")
} else {
Log("Wails startup")
}
} }
func (a *App) GetConfig() *utils.Config { func (a *App) GetConfig() *utils.Config {

View File

@@ -2,6 +2,7 @@ package internal
import ( import (
"bytes" "bytes"
"encoding/base64"
"fmt" "fmt"
"io" "io"
"net/mail" "net/mail"
@@ -58,10 +59,68 @@ func ReadEmlFile(filePath string) (*EmailData, error) {
body = email.TextBody body = email.TextBody
} }
// Process attachments and detect PEC // Process attachments list and PEC detection
var attachments []EmailAttachment var attachments []EmailAttachment
var hasDatiCert, hasSmime, hasInnerEmail bool var hasDatiCert, hasSmime, hasInnerEmail bool
// Process embedded files (inline images) -> add to body AND add as attachments
for _, ef := range email.EmbeddedFiles {
data, err := io.ReadAll(ef.Data)
if err != nil {
continue
}
// Convert to base64
b64 := base64.StdEncoding.EncodeToString(data)
mimeType := ef.ContentType
if parts := strings.Split(mimeType, ";"); len(parts) > 0 {
mimeType = strings.TrimSpace(parts[0])
}
if mimeType == "" {
mimeType = "application/octet-stream"
}
// Create data URI
dataURI := fmt.Sprintf("data:%s;base64,%s", mimeType, b64)
// Replace cid:reference with data URI in HTML body
// ef.CID is already trimmed of <>
target := "cid:" + ef.CID
body = strings.ReplaceAll(body, target, dataURI)
// ALSO ADD AS ATTACHMENTS for the viewer
filename := ef.CID
if filename == "" {
filename = "embedded_image"
}
// If no extension, try to infer from mimetype
if !strings.Contains(filename, ".") {
ext := "dat"
switch mimeType {
case "image/jpeg":
ext = "jpg"
case "image/png":
ext = "png"
case "image/gif":
ext = "gif"
case "application/pdf":
ext = "pdf"
default:
if parts := strings.Split(mimeType, "/"); len(parts) > 1 {
ext = parts[1]
}
}
filename = fmt.Sprintf("%s.%s", filename, ext)
}
attachments = append(attachments, EmailAttachment{
Filename: filename,
ContentType: mimeType,
Data: data,
})
}
// Process standard attachments
for _, att := range email.Attachments { for _, att := range email.Attachments {
data, err := io.ReadAll(att.Data) data, err := io.ReadAll(att.Data)
if err != nil { if err != nil {

View File

@@ -1,6 +1,6 @@
[EMLy] [EMLy]
SDK_DECODER_SEMVER="1.3.0" SDK_DECODER_SEMVER="1.3.1"
SDK_DECODER_RELEASE_CHANNEL="beta" SDK_DECODER_RELEASE_CHANNEL="beta"
GUI_SEMVER="1.2.4" GUI_SEMVER="1.3.0"
GUI_RELEASE_CHANNEL="beta" GUI_RELEASE_CHANNEL="beta"
LANGUAGE="it" LANGUAGE="it"

View File

@@ -9,6 +9,7 @@
import { mailState } from "$lib/stores/mail-state.svelte"; import { mailState } from "$lib/stores/mail-state.svelte";
import { settingsStore } from "$lib/stores/settings.svelte"; import { settingsStore } from "$lib/stores/settings.svelte";
import * as m from "$lib/paraglide/messages"; import * as m from "$lib/paraglide/messages";
import { dev } from "$app/environment";
let unregisterEvents = () => {}; let unregisterEvents = () => {};
let isLoading = $state(false); let isLoading = $state(false);
@@ -22,11 +23,13 @@
} }
$effect(() => { $effect(() => {
console.log("Current email changed:", mailState.currentEmail); if(dev) {
console.log(mailState.currentEmail)
}
console.info("Current email changed:", mailState.currentEmail?.subject);
if(mailState.currentEmail !== null) { if(mailState.currentEmail !== null) {
sidebarOpen.set(false); sidebarOpen.set(false);
} }
console.log(mailState.currentEmail?.attachments)
}) })
onDestroy(() => { onDestroy(() => {

View File

@@ -0,0 +1,56 @@
import { FrontendLog } from '$lib/wailsjs/go/main/App';
function safeStringify(obj: any): string {
try {
if (typeof obj === 'object' && obj !== null) {
return JSON.stringify(obj);
}
return String(obj);
} catch (e) {
return '[Circular/Error]';
}
}
export function setupConsoleLogger() {
if ((window as any).__logger_initialized__) return;
(window as any).__logger_initialized__ = true;
const originalLog = console.log;
const originalWarn = console.warn;
const originalError = console.error;
const originalInfo = console.info;
function logToBackend(level: string, args: any[]) {
try {
// Avoid logging if wails runtime is not ready or function is missing
if (typeof FrontendLog !== 'function') return;
const message = args.map(arg => safeStringify(arg)).join(' ');
FrontendLog(level, message).catch(() => {});
} catch (e) {
// ignore
}
}
console.log = (...args) => {
originalLog(...args);
logToBackend("INFO", args);
};
console.warn = (...args) => {
originalWarn(...args);
logToBackend("WARN", args);
};
console.error = (...args) => {
originalError(...args);
logToBackend("ERROR", args);
};
console.info = (...args) => {
originalInfo(...args);
logToBackend("INFO", args);
};
originalLog("Console logger hooked to Wails backend");
}

View File

@@ -6,6 +6,8 @@ import {internal} from '../models';
export function CheckIsDefaultEMLHandler():Promise<boolean>; export function CheckIsDefaultEMLHandler():Promise<boolean>;
export function FrontendLog(arg1:string,arg2:string):Promise<void>;
export function GetConfig():Promise<utils.Config>; export function GetConfig():Promise<utils.Config>;
export function GetImageViewerData():Promise<main.ImageViewerData>; export function GetImageViewerData():Promise<main.ImageViewerData>;

View File

@@ -3,85 +3,89 @@
// This file is automatically generated. DO NOT EDIT // This file is automatically generated. DO NOT EDIT
export function CheckIsDefaultEMLHandler() { export function CheckIsDefaultEMLHandler() {
return ObfuscatedCall(0, []); return window['go']['main']['App']['CheckIsDefaultEMLHandler']();
}
export function FrontendLog(arg1, arg2) {
return window['go']['main']['App']['FrontendLog'](arg1, arg2);
} }
export function GetConfig() { export function GetConfig() {
return ObfuscatedCall(1, []); return window['go']['main']['App']['GetConfig']();
} }
export function GetImageViewerData() { export function GetImageViewerData() {
return ObfuscatedCall(2, []); return window['go']['main']['App']['GetImageViewerData']();
} }
export function GetMachineData() { export function GetMachineData() {
return ObfuscatedCall(3, []); return window['go']['main']['App']['GetMachineData']();
} }
export function GetPDFViewerData() { export function GetPDFViewerData() {
return ObfuscatedCall(4, []); return window['go']['main']['App']['GetPDFViewerData']();
} }
export function GetStartupFile() { export function GetStartupFile() {
return ObfuscatedCall(5, []); return window['go']['main']['App']['GetStartupFile']();
} }
export function GetViewerData() { export function GetViewerData() {
return ObfuscatedCall(6, []); return window['go']['main']['App']['GetViewerData']();
} }
export function IsDebuggerRunning() { export function IsDebuggerRunning() {
return ObfuscatedCall(7, []); return window['go']['main']['App']['IsDebuggerRunning']();
} }
export function OpenDefaultAppsSettings() { export function OpenDefaultAppsSettings() {
return ObfuscatedCall(8, []); return window['go']['main']['App']['OpenDefaultAppsSettings']();
} }
export function OpenEMLWindow(arg1, arg2) { export function OpenEMLWindow(arg1, arg2) {
return ObfuscatedCall(9, [arg1, arg2]); return window['go']['main']['App']['OpenEMLWindow'](arg1, arg2);
} }
export function OpenImage(arg1, arg2) { export function OpenImage(arg1, arg2) {
return ObfuscatedCall(10, [arg1, arg2]); return window['go']['main']['App']['OpenImage'](arg1, arg2);
} }
export function OpenImageWindow(arg1, arg2) { export function OpenImageWindow(arg1, arg2) {
return ObfuscatedCall(11, [arg1, arg2]); return window['go']['main']['App']['OpenImageWindow'](arg1, arg2);
} }
export function OpenPDF(arg1, arg2) { export function OpenPDF(arg1, arg2) {
return ObfuscatedCall(12, [arg1, arg2]); return window['go']['main']['App']['OpenPDF'](arg1, arg2);
} }
export function OpenPDFWindow(arg1, arg2) { export function OpenPDFWindow(arg1, arg2) {
return ObfuscatedCall(13, [arg1, arg2]); return window['go']['main']['App']['OpenPDFWindow'](arg1, arg2);
} }
export function QuitApp() { export function QuitApp() {
return ObfuscatedCall(14, []); return window['go']['main']['App']['QuitApp']();
} }
export function ReadEML(arg1) { export function ReadEML(arg1) {
return ObfuscatedCall(15, [arg1]); return window['go']['main']['App']['ReadEML'](arg1);
} }
export function ReadMSG(arg1, arg2) { export function ReadMSG(arg1, arg2) {
return ObfuscatedCall(16, [arg1, arg2]); return window['go']['main']['App']['ReadMSG'](arg1, arg2);
} }
export function ReadMSGOSS(arg1) { export function ReadMSGOSS(arg1) {
return ObfuscatedCall(17, [arg1]); return window['go']['main']['App']['ReadMSGOSS'](arg1);
} }
export function ReadPEC(arg1) { export function ReadPEC(arg1) {
return ObfuscatedCall(18, [arg1]); return window['go']['main']['App']['ReadPEC'](arg1);
} }
export function SaveConfig(arg1) { export function SaveConfig(arg1) {
return ObfuscatedCall(19, [arg1]); return window['go']['main']['App']['SaveConfig'](arg1);
} }
export function ShowOpenFileDialog() { export function ShowOpenFileDialog() {
return ObfuscatedCall(20, []); return window['go']['main']['App']['ShowOpenFileDialog']();
} }

View File

@@ -31,10 +31,12 @@
} from "$lib/wailsjs/runtime/runtime"; } from "$lib/wailsjs/runtime/runtime";
import { RefreshCcwDot } from "@lucide/svelte"; import { RefreshCcwDot } from "@lucide/svelte";
import { IsDebuggerRunning, QuitApp } from "$lib/wailsjs/go/main/App"; import { IsDebuggerRunning, QuitApp } from "$lib/wailsjs/go/main/App";
import { settingsStore } from "$lib/stores/settings.svelte.js";
let versionInfo: utils.Config | null = $state(null); let versionInfo: utils.Config | null = $state(null);
let isMaximized = $state(false); let isMaximized = $state(false);
let isDebugerOn: boolean = $state(false); let isDebugerOn: boolean = $state(false);
let isDebbugerProtectionOn: boolean = $state(true);
async function syncMaxState() { async function syncMaxState() {
isMaximized = await WindowIsMaximised(); isMaximized = await WindowIsMaximised();
@@ -69,7 +71,7 @@
} }
onMount(async () => { onMount(async () => {
if (browser) { if (browser && isDebbugerProtectionOn) {
detectDebugging(); detectDebugging();
setInterval(detectDebugging, 1000); setInterval(detectDebugging, 1000);
} }
@@ -118,6 +120,8 @@
} catch { } catch {
stored = null; stored = null;
} }
isDebbugerProtectionOn = settingsStore.settings.enableAttachedDebuggerProtection ? true : false;
$inspect(isDebbugerProtectionOn, "isDebbugerProtectionOn");
applyTheme(stored === "light" ? "light" : "dark"); applyTheme(stored === "light" ? "light" : "dark");
}); });

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import MailViewer from "$lib/components/dashboard/MailViewer.svelte"; import MailViewer from "$lib/components/MailViewer.svelte";
import { mailState } from "$lib/stores/mail-state.svelte"; import { mailState } from "$lib/stores/mail-state.svelte";
let { data } = $props(); let { data } = $props();

View File

@@ -2,7 +2,6 @@ import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types'; import type { PageLoad } from './$types';
import { GetViewerData, GetStartupFile, ReadEML, ReadMSG } from '$lib/wailsjs/go/main/App'; import { GetViewerData, GetStartupFile, ReadEML, ReadMSG } from '$lib/wailsjs/go/main/App';
import DOMPurify from 'dompurify'; import DOMPurify from 'dompurify';
import { settingsStore } from '$lib/stores/settings.svelte';
import type { internal } from '$lib/wailsjs/go/models'; import type { internal } from '$lib/wailsjs/go/models';
export const load: PageLoad = async () => { export const load: PageLoad = async () => {

View File

@@ -6,7 +6,7 @@
import { Label } from "$lib/components/ui/label"; import { Label } from "$lib/components/ui/label";
import { Separator } from "$lib/components/ui/separator"; import { Separator } from "$lib/components/ui/separator";
import { Switch } from "$lib/components/ui/switch"; import { Switch } from "$lib/components/ui/switch";
import { ChevronLeft, Command, Option, Flame } from "@lucide/svelte"; import { ChevronLeft, Flame } from "@lucide/svelte";
import type { EMLy_GUI_Settings } from "$lib/types"; import type { EMLy_GUI_Settings } from "$lib/types";
import { toast } from "svelte-sonner"; import { toast } from "svelte-sonner";
import { It, Us } from "svelte-flags"; import { It, Us } from "svelte-flags";

View File

@@ -1,9 +1,11 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { setupConsoleLogger } from '$lib/utils/logger-hook';
let { children } = $props(); let { children } = $props();
onMount(() => { onMount(() => {
setupConsoleLogger();
const loader = document.getElementById('app-loading'); const loader = document.getElementById('app-loading');
if (loader) { if (loader) {
loader.style.opacity = '0'; loader.style.opacity = '0';

View File

@@ -1,42 +1,46 @@
#define ApplicationName 'EMLy'
#define ApplicationVersion GetVersionNumbersString('EMLy.exe')
#define ApplicationVersion '1.2.4_beta'
[Setup] [Setup]
AppName=EMLy AppName={#ApplicationName}
AppVersion=1.2.2 AppVersion={#ApplicationVersion}
DefaultDirName={autopf}\EMLy DefaultDirName={autopf}\EMLy
OutputBaseFilename=EMLy_Installer_1.2.2 OutputBaseFilename={#ApplicationName}_Installer_{#ApplicationVersion}
ArchitecturesInstallIn64BitMode=x64compatible ArchitecturesInstallIn64BitMode=x64compatible
DisableProgramGroupPage=yes DisableProgramGroupPage=yes
; Request administrative privileges for HKA to write to HKLM if needed, ; Request administrative privileges for HKA to write to HKLM if needed,
; or use "lowest" if purely per-user, but file associations usually work better with admin rights or proper HKA handling. ; or use "lowest" if purely per-user, but file associations usually work better with admin rights or proper HKA handling.
PrivilegesRequired=admin PrivilegesRequired=admin
SetupIconFile=..\build\windows\icon.ico SetupIconFile=..\build\windows\icon.ico
UninstallDisplayIcon={app}\EMLy.exe UninstallDisplayIcon={app}\{#ApplicationName}.exe
AppVerName={#ApplicationName} {#ApplicationVersion}
[Files] [Files]
; Source path relative to this .iss file (assuming it is in the "installer" folder and build is in "../build") ; Source path relative to this .iss file (assuming it is in the "installer" folder and build is in "../build")
Source: "..\build\bin\EMLy.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "..\build\bin\{#ApplicationName}.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\build\bin\config.ini"; DestDir: "{app}"; Flags: ignoreversion Source: "..\build\bin\config.ini"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\build\bin\signed_msg.exe"; DestDir: "{app}"; Flags: ignoreversion
[Registry] [Registry]
; 1. Register the .eml extension and point it to our internal ProgID "EMLy.EML" ; 1. Register the .eml extension and point it to our internal ProgID "EMLy.EML"
Root: HKA; Subkey: "Software\Classes\.eml"; ValueType: string; ValueName: ""; ValueData: "EMLy.EML"; Flags: uninsdeletevalue Root: HKA; Subkey: "Software\Classes\.eml"; ValueType: string; ValueName: ""; ValueData: "{#ApplicationName}.EML"; Flags: uninsdeletevalue
Root: HKA; Subkey: "Software\Classes\.msg"; ValueType: string; ValueName: ""; ValueData: "EMLy.MSG"; Flags: uninsdeletevalue Root: HKA; Subkey: "Software\Classes\.msg"; ValueType: string; ValueName: ""; ValueData: "{#ApplicationName}.MSG"; Flags: uninsdeletevalue
; 2. Define the ProgID with a readable name and icon ; 2. Define the ProgID with a readable name and icon
Root: HKA; Subkey: "Software\Classes\EMLy.EML"; ValueType: string; ValueName: ""; ValueData: "EMLy Email Message"; Flags: uninsdeletekey Root: HKA; Subkey: "Software\Classes\{#ApplicationName}.EML"; ValueType: string; ValueName: ""; ValueData: "{#ApplicationName} Email Message"; Flags: uninsdeletekey
Root: HKA; Subkey: "Software\Classes\EMLy.EML\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\EMLy.exe,0" Root: HKA; Subkey: "Software\Classes\{#ApplicationName}.EML\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#ApplicationName}.exe,0"
Root: HKA; Subkey: "Software\Classes\EMLy.MSG"; ValueType: string; ValueName: ""; ValueData: "EMLy Outlook Message"; Flags: uninsdeletekey Root: HKA; Subkey: "Software\Classes\{#ApplicationName}.MSG"; ValueType: string; ValueName: ""; ValueData: "{#ApplicationName} Outlook Message"; Flags: uninsdeletekey
Root: HKA; Subkey: "Software\Classes\EMLy.MSG\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\EMLy.exe,0" Root: HKA; Subkey: "Software\Classes\{#ApplicationName}.MSG\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#ApplicationName}.exe,0"
; 3. Define the open command ; 3. Define the open command
; "%1" passes the file path to the application ; "%1" passes the file path to the application
Root: HKA; Subkey: "Software\Classes\EMLy.EML\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\EMLy.exe"" ""%1""" Root: HKA; Subkey: "Software\Classes\{#ApplicationName}.EML\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\EMLy.exe"" ""%1"""
Root: HKA; Subkey: "Software\Classes\EMLy.MSG\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\EMLy.exe"" ""%1""" Root: HKA; Subkey: "Software\Classes\{#ApplicationName}.MSG\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\EMLy.exe"" ""%1"""
; Optional: Add "Open with EMLy" to context menu explicitly (though file association typically handles the double click) ; Optional: Add "Open with EMLy" to context menu explicitly (though file association typically handles the double click)
Root: HKA; Subkey: "Software\Classes\EMLy.EML\shell\open"; ValueType: string; ValueName: "FriendlyAppName"; ValueData: "EMLy" Root: HKA; Subkey: "Software\Classes\{#ApplicationName}.EML\shell\open"; ValueType: string; ValueName: "FriendlyAppName"; ValueData: "{#ApplicationName}"
Root: HKA; Subkey: "Software\Classes\EMLy.MSG\shell\open"; ValueType: string; ValueName: "FriendlyAppName"; ValueData: "EMLy" Root: HKA; Subkey: "Software\Classes\{#ApplicationName}.MSG\shell\open"; ValueType: string; ValueName: "FriendlyAppName"; ValueData: "{#ApplicationName}"
[Icons] [Icons]
Name: "{autoprograms}\EMLy"; Filename: "{app}\EMLy.exe" Name: "{autoprograms}\{#ApplicationName}"; Filename: "{app}\{#ApplicationName}.exe"

View File

@@ -2,6 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"io"
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
@@ -10,7 +11,47 @@ import (
"time" "time"
) )
var logger = log.New(os.Stdout, "", 0) var (
logger = log.New(os.Stdout, "", 0)
logFile *os.File
)
// InitLogger initializes the logger to write to both stdout and a file in AppData
func InitLogger() error {
configDir, err := os.UserConfigDir()
if err != nil {
return err
}
appDir := filepath.Join(configDir, "EMLy")
logsDir := filepath.Join(appDir, "logs")
if err := os.MkdirAll(logsDir, 0755); err != nil {
return err
}
logPath := filepath.Join(logsDir, "app.log")
// Open file in Append mode
file, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
logFile = file
// MultiWriter to write to both stdout and file
multi := io.MultiWriter(os.Stdout, file)
logger = log.New(multi, "", 0)
Log("Logger initialized. Writing to: " + logPath)
return nil
}
// CloseLogger closes the log file
func CloseLogger() {
if logFile != nil {
logFile.Close()
}
}
// Log prints a timestamped, file:line tagged log line. // Log prints a timestamped, file:line tagged log line.
func Log(args ...any) { func Log(args ...any) {
@@ -27,3 +68,14 @@ func Log(args ...any) {
msg := fmt.Sprintln(args...) msg := fmt.Sprintln(args...)
logger.Printf("[%s] - [%s] - [%s] - %s", date, tm, loc, strings.TrimRight(msg, "\n")) logger.Printf("[%s] - [%s] - [%s] - %s", date, tm, loc, strings.TrimRight(msg, "\n"))
} }
// FrontendLog allows the frontend to send logs to the backend logger
func (a *App) FrontendLog(level string, message string) {
now := time.Now()
date := now.Format("2006-01-02")
tm := now.Format("15:04:05")
// We don't use runtime.Caller here because it would point to this function
// Instead we tag it as [FRONTEND]
logger.Printf("[%s] - [%s] - [FRONTEND] - [%s] %s", date, tm, level, message)
}

View File

@@ -28,6 +28,11 @@ func (a *App) onSecondInstanceLaunch(secondInstanceData options.SecondInstanceDa
} }
func main() { func main() {
if err := InitLogger(); err != nil {
log.Println("Error initializing logger:", err)
}
defer CloseLogger()
// Check for custom args // Check for custom args
args := os.Args args := os.Args
uniqueId := "emly-app-lock" uniqueId := "emly-app-lock"