mirror of
https://github.com/lone-cloud/prism
synced 2026-06-03 08:43:10 -07:00
minor refactoring
This commit is contained in:
parent
cb5a7e870a
commit
606c4c6f23
7 changed files with 138 additions and 139 deletions
|
|
@ -17,7 +17,7 @@ import { PUBLIC_DIR } from '@/constants/paths';
|
|||
import { cleanupDaemon, initSignal } from '@/modules/signal';
|
||||
import admin from '@/routes/admin';
|
||||
import ntfy from '@/routes/ntfy';
|
||||
import unifiedpush from '@/routes/unifiedpush';
|
||||
import unifiedpush from '@/routes/unified-push';
|
||||
import { isLocalIP } from '@/utils/auth';
|
||||
import { logError, logInfo, logVerbose, logWarn } from '@/utils/log';
|
||||
|
||||
|
|
@ -33,7 +33,7 @@ if (!API_KEY) {
|
|||
|
||||
if (PROTON_IMAP_USERNAME && PROTON_IMAP_PASSWORD) {
|
||||
try {
|
||||
const { startProtonMonitor } = await import('./modules/protonmail');
|
||||
const { startProtonMonitor } = await import('./modules/proton-mail');
|
||||
await startProtonMonitor();
|
||||
} catch (err) {
|
||||
logError('Failed to start Proton Mail monitor:', err);
|
||||
|
|
|
|||
|
|
@ -37,8 +37,6 @@ export async function startProtonMonitor() {
|
|||
password: PROTON_IMAP_PASSWORD,
|
||||
host: PROTON_BRIDGE_HOST,
|
||||
port: PROTON_BRIDGE_PORT,
|
||||
tls: false,
|
||||
tlsOptions: { rejectUnauthorized: false },
|
||||
keepalive: true,
|
||||
});
|
||||
|
||||
|
|
@ -87,9 +85,11 @@ export async function startProtonMonitor() {
|
|||
fetch.on('message', (msg) => {
|
||||
msg.on('body', (stream) => {
|
||||
let buffer = '';
|
||||
|
||||
stream.on('data', (chunk) => {
|
||||
buffer += chunk.toString('utf8');
|
||||
});
|
||||
|
||||
stream.once('end', () => {
|
||||
const header = Imap.parseHeader(buffer);
|
||||
const rawFrom = header.from?.[0] || 'Unknown sender';
|
||||
|
|
@ -98,12 +98,15 @@ export async function startProtonMonitor() {
|
|||
|
||||
if (dateStr) {
|
||||
const messageDate = new Date(dateStr).getTime();
|
||||
|
||||
if (messageDate < monitorStartTime) {
|
||||
const formattedDate = new Date(dateStr).toLocaleString('en-US', {
|
||||
dateStyle: 'medium',
|
||||
timeStyle: 'short',
|
||||
});
|
||||
|
||||
logVerbose(`Skipping old email: ${subject} (${formattedDate})`);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -94,7 +94,7 @@ export async function finishLink() {
|
|||
return result;
|
||||
}
|
||||
|
||||
export async function unlinkDevice() {
|
||||
async function unlinkDevice() {
|
||||
account = null;
|
||||
currentLinkUri = null;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Database } from 'bun:sqlite';
|
||||
import { SUP_DB } from '@/constants/paths';
|
||||
import { createGroup } from './signal';
|
||||
import { createGroup } from '@/modules/signal';
|
||||
|
||||
interface EndpointMapping {
|
||||
endpoint: string;
|
||||
|
|
@ -18,18 +18,18 @@ db.run(`
|
|||
)
|
||||
`);
|
||||
|
||||
export const register = (endpoint: string, groupId: string, appName: string) => {
|
||||
export const register = (endpoint: string, groupId: string, appName: string) =>
|
||||
db.run('INSERT OR REPLACE INTO mappings (endpoint, groupId, appName) VALUES (?, ?, ?)', [
|
||||
endpoint,
|
||||
groupId,
|
||||
appName,
|
||||
]);
|
||||
};
|
||||
|
||||
export const getGroupId = (endpoint: string) => {
|
||||
const row = db.query('SELECT groupId FROM mappings WHERE endpoint = ?').get(endpoint) as
|
||||
| { groupId: string }
|
||||
| undefined;
|
||||
|
||||
return row?.groupId;
|
||||
};
|
||||
|
||||
|
|
@ -37,15 +37,15 @@ export const getAppName = (endpoint: string) => {
|
|||
const row = db.query('SELECT appName FROM mappings WHERE endpoint = ?').get(endpoint) as
|
||||
| { appName: string }
|
||||
| undefined;
|
||||
|
||||
return row?.appName;
|
||||
};
|
||||
|
||||
export const getAllMappings = () =>
|
||||
db.query('SELECT endpoint, groupId, appName FROM mappings').all() as EndpointMapping[];
|
||||
|
||||
export const remove = (endpoint: string) => {
|
||||
export const remove = (endpoint: string) =>
|
||||
db.run('DELETE FROM mappings WHERE endpoint = ?', [endpoint]);
|
||||
};
|
||||
|
||||
export const getOrCreateGroup = async (key: string, name: string) => {
|
||||
const existingGroupId = getGroupId(key);
|
||||
|
|
@ -53,5 +53,6 @@ export const getOrCreateGroup = async (key: string, name: string) => {
|
|||
|
||||
const groupId = await createGroup(name);
|
||||
register(key, groupId, name);
|
||||
|
||||
return groupId;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Hono } from 'hono';
|
||||
import { basicAuth } from 'hono/basic-auth';
|
||||
import { DEVICE_NAME, PROTON_IMAP_PASSWORD, PROTON_IMAP_USERNAME } from '@/constants/config';
|
||||
import { isImapConnected } from '@/modules/protonmail';
|
||||
import { isImapConnected } from '@/modules/proton-mail';
|
||||
import {
|
||||
checkSignalCli,
|
||||
finishLink,
|
||||
|
|
@ -19,133 +19,6 @@ let qrCacheTime = 0;
|
|||
let generatingPromise: Promise<string> | null = null;
|
||||
const QR_CACHE_TTL = 10 * 60 * 1000;
|
||||
|
||||
export const handleHealthFragment = async () => {
|
||||
const signalOk = await checkSignalCli();
|
||||
const linked = signalOk && (await hasValidAccount());
|
||||
const imap = isImapConnected();
|
||||
const hasProtonConfig = PROTON_IMAP_USERNAME && PROTON_IMAP_PASSWORD;
|
||||
const accountNumber = getAccount();
|
||||
|
||||
const html = `
|
||||
<div class="status">
|
||||
<div class="status-item ${signalOk ? 'status-ok' : 'status-error'}">
|
||||
Signal Network: ${signalOk ? 'Connected' : 'Disconnected'}
|
||||
</div>
|
||||
<div class="status-item ${linked ? 'status-ok' : 'status-error'}">
|
||||
Account: ${linked ? 'Linked' : 'Unlinked'}
|
||||
${linked && accountNumber ? `<span class="tooltip">${formatPhoneNumber(accountNumber)}</span>` : ''}
|
||||
</div>
|
||||
${
|
||||
hasProtonConfig
|
||||
? `<div class="status-item ${imap ? 'status-ok' : 'status-error'}">
|
||||
Proton Mail: ${imap ? 'Connected' : 'Disconnected'}
|
||||
${imap ? `<span class="tooltip">${PROTON_IMAP_USERNAME}</span>` : ''}
|
||||
</div>`
|
||||
: ''
|
||||
}
|
||||
</div>
|
||||
<div id="signal-info" hx-swap-oob="true">
|
||||
${await handleSignalInfoFragment()}
|
||||
</div>
|
||||
`;
|
||||
|
||||
return { html, linked };
|
||||
};
|
||||
|
||||
export const handleSignalInfoFragment = async () => {
|
||||
if (await hasValidAccount()) {
|
||||
cachedQR = null;
|
||||
return `<details class="unlink-details">
|
||||
<summary class="unlink-summary">Unlink and remove device</summary>
|
||||
<div class="unlink-instructions">
|
||||
<ol>
|
||||
<li>Open Signal app → <strong>Settings → Linked Devices</strong></li>
|
||||
<li>Find <strong>"${DEVICE_NAME}"</strong> and tap it</li>
|
||||
<li>Tap <strong>"Unlink Device"</strong></li>
|
||||
</ol>
|
||||
</div>
|
||||
</details>`;
|
||||
}
|
||||
|
||||
return handleQRSection();
|
||||
};
|
||||
|
||||
export const handleEndpointsFragment = async () => {
|
||||
const endpoints = getAllMappings();
|
||||
|
||||
if (endpoints.length === 0) {
|
||||
return '<p>No endpoints registered</p>';
|
||||
}
|
||||
|
||||
return `
|
||||
<ul class="endpoint-list">
|
||||
${endpoints
|
||||
.map(
|
||||
(e: { appName: string; endpoint: string }) => `
|
||||
<li class="endpoint-item">
|
||||
<div class="endpoint-name">
|
||||
<strong>${e.appName}</strong>
|
||||
</div>
|
||||
<button
|
||||
class="btn-delete"
|
||||
hx-delete="/action/delete-endpoint/${encodeURIComponent(e.endpoint)}"
|
||||
hx-target="#endpoints-list"
|
||||
hx-swap="innerHTML"
|
||||
>Delete</button>
|
||||
</li>
|
||||
`,
|
||||
)
|
||||
.join('')}
|
||||
</ul>
|
||||
`;
|
||||
};
|
||||
|
||||
export const handleQRSection = async () => {
|
||||
if (await hasValidAccount()) {
|
||||
return '<p>Account already linked</p>';
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
if ((!cachedQR || now - qrCacheTime > QR_CACHE_TTL) && !generatingPromise) {
|
||||
generatingPromise = (async () => {
|
||||
const qr = await generateLinkQR();
|
||||
cachedQR = qr;
|
||||
qrCacheTime = Date.now();
|
||||
|
||||
finishLink()
|
||||
.then(async () => {
|
||||
await initSignal();
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
generatingPromise = null;
|
||||
cachedQR = null;
|
||||
qrCacheTime = 0;
|
||||
});
|
||||
|
||||
return qr;
|
||||
})();
|
||||
}
|
||||
|
||||
if (generatingPromise && !cachedQR) {
|
||||
await generatingPromise;
|
||||
}
|
||||
|
||||
return `
|
||||
<p>Scan this QR code with your Signal app:</p>
|
||||
<p class="qr-instructions"><strong>Settings → Linked Devices → Link New Device</strong></p>
|
||||
<div class="qr-container">
|
||||
<img src="${cachedQR}" class="qr-image" alt="QR Code" />
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
|
||||
export const handleLinkStatusCheck = async () => {
|
||||
const linked = await hasValidAccount();
|
||||
return { linked };
|
||||
};
|
||||
|
||||
const admin = new Hono();
|
||||
|
||||
admin.use(
|
||||
|
|
@ -198,4 +71,126 @@ admin.delete('/action/delete-endpoint/:endpoint', async (c) => {
|
|||
return c.html(await handleEndpointsFragment());
|
||||
});
|
||||
|
||||
const handleHealthFragment = async () => {
|
||||
const signalOk = await checkSignalCli();
|
||||
const linked = signalOk && (await hasValidAccount());
|
||||
const imap = isImapConnected();
|
||||
const hasProtonConfig = PROTON_IMAP_USERNAME && PROTON_IMAP_PASSWORD;
|
||||
const accountNumber = getAccount();
|
||||
|
||||
const html = `
|
||||
<div class="status">
|
||||
<div class="status-item ${signalOk ? 'status-ok' : 'status-error'}">
|
||||
Signal Network: ${signalOk ? 'Connected' : 'Disconnected'}
|
||||
</div>
|
||||
<div class="status-item ${linked ? 'status-ok' : 'status-error'}">
|
||||
Account: ${linked ? 'Linked' : 'Unlinked'}
|
||||
${linked && accountNumber ? `<span class="tooltip">${formatPhoneNumber(accountNumber)}</span>` : ''}
|
||||
</div>
|
||||
${
|
||||
hasProtonConfig
|
||||
? `<div class="status-item ${imap ? 'status-ok' : 'status-error'}">
|
||||
Proton Mail: ${imap ? 'Connected' : 'Disconnected'}
|
||||
${imap ? `<span class="tooltip">${PROTON_IMAP_USERNAME}</span>` : ''}
|
||||
</div>`
|
||||
: ''
|
||||
}
|
||||
</div>
|
||||
<div id="signal-info" hx-swap-oob="true">
|
||||
${await handleSignalInfoFragment()}
|
||||
</div>
|
||||
`;
|
||||
|
||||
return { html, linked };
|
||||
};
|
||||
|
||||
const handleSignalInfoFragment = async () => {
|
||||
if (await hasValidAccount()) {
|
||||
cachedQR = null;
|
||||
return `<details class="unlink-details">
|
||||
<summary class="unlink-summary">Unlink and remove device</summary>
|
||||
<div class="unlink-instructions">
|
||||
<ol>
|
||||
<li>Open Signal app → <strong>Settings → Linked Devices</strong></li>
|
||||
<li>Find <strong>"${DEVICE_NAME}"</strong> and tap it</li>
|
||||
<li>Tap <strong>"Unlink Device"</strong></li>
|
||||
</ol>
|
||||
</div>
|
||||
</details>`;
|
||||
}
|
||||
|
||||
return handleQRSection();
|
||||
};
|
||||
|
||||
const handleEndpointsFragment = async () => {
|
||||
const endpoints = getAllMappings();
|
||||
|
||||
if (endpoints.length === 0) {
|
||||
return '<p>No endpoints registered</p>';
|
||||
}
|
||||
|
||||
return `
|
||||
<ul class="endpoint-list">
|
||||
${endpoints
|
||||
.map(
|
||||
(e: { appName: string; endpoint: string }) => `
|
||||
<li class="endpoint-item">
|
||||
<div class="endpoint-name">
|
||||
<strong>${e.appName}</strong>
|
||||
</div>
|
||||
<button
|
||||
class="btn-delete"
|
||||
hx-delete="/action/delete-endpoint/${encodeURIComponent(e.endpoint)}"
|
||||
hx-target="#endpoints-list"
|
||||
hx-swap="innerHTML"
|
||||
>Delete</button>
|
||||
</li>
|
||||
`,
|
||||
)
|
||||
.join('')}
|
||||
</ul>
|
||||
`;
|
||||
};
|
||||
|
||||
const handleQRSection = async () => {
|
||||
if (await hasValidAccount()) {
|
||||
return '<p>Account already linked</p>';
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
if ((!cachedQR || now - qrCacheTime > QR_CACHE_TTL) && !generatingPromise) {
|
||||
generatingPromise = (async () => {
|
||||
const qr = await generateLinkQR();
|
||||
cachedQR = qr;
|
||||
qrCacheTime = Date.now();
|
||||
|
||||
finishLink()
|
||||
.then(async () => {
|
||||
await initSignal();
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
generatingPromise = null;
|
||||
cachedQR = null;
|
||||
qrCacheTime = 0;
|
||||
});
|
||||
|
||||
return qr;
|
||||
})();
|
||||
}
|
||||
|
||||
if (generatingPromise && !cachedQR) {
|
||||
await generatingPromise;
|
||||
}
|
||||
|
||||
return `
|
||||
<p>Scan this QR code with your Signal app:</p>
|
||||
<p class="qr-instructions"><strong>Settings → Linked Devices → Link New Device</strong></p>
|
||||
<div class="qr-container">
|
||||
<img src="${cachedQR}" class="qr-image" alt="QR Code" />
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
|
||||
export default admin;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Hono } from 'hono';
|
||||
import { sendGroupMessage } from '@/modules/signal';
|
||||
import { getGroupId, getOrCreateGroup, remove } from '@/modules/store';
|
||||
import { formatAsSignalMessage, parseUnifiedPushRequest } from '@/modules/unifiedpush';
|
||||
import { formatAsSignalMessage, parseUnifiedPushRequest } from '@/modules/unified-push';
|
||||
|
||||
const unifiedpush = new Hono();
|
||||
|
||||
Loading…
Add table
Reference in a new issue