mirror of
https://github.com/lone-cloud/prism
synced 2026-06-03 19:54:44 -07:00
more minor refacotring and cleanup
This commit is contained in:
parent
e6f9542b0f
commit
9b5f6c28cf
12 changed files with 113 additions and 136 deletions
|
|
@ -1,4 +1,3 @@
|
||||||
import { networkInterfaces } from 'node:os';
|
|
||||||
import { Hono } from 'hono';
|
import { Hono } from 'hono';
|
||||||
import { getConnInfo, serveStatic } from 'hono/bun';
|
import { getConnInfo, serveStatic } from 'hono/bun';
|
||||||
import { compress } from 'hono/compress';
|
import { compress } from 'hono/compress';
|
||||||
|
|
@ -15,12 +14,23 @@ import {
|
||||||
} from '@/constants/config';
|
} from '@/constants/config';
|
||||||
import { PUBLIC_DIR } from '@/constants/paths';
|
import { PUBLIC_DIR } from '@/constants/paths';
|
||||||
import { cleanupDaemon, initSignal } from '@/modules/signal';
|
import { cleanupDaemon, initSignal } from '@/modules/signal';
|
||||||
import admin from '@/routes/admin';
|
import { admin } from '@/routes/admin';
|
||||||
import ntfy from '@/routes/ntfy';
|
import { ntfy } from '@/routes/ntfy';
|
||||||
import unifiedpush from '@/routes/unified-push';
|
import { unifiedpush } from '@/routes/unified-push';
|
||||||
import { isLocalIP } from '@/utils/auth';
|
import { getLanIP, isLocalIP } from '@/utils/auth';
|
||||||
|
import { formatToCspString } from '@/utils/format';
|
||||||
import { logError, logInfo, logVerbose, logWarn } from '@/utils/log';
|
import { logError, logInfo, logVerbose, logWarn } from '@/utils/log';
|
||||||
|
|
||||||
|
const cspConfig = {
|
||||||
|
defaultSrc: ["'self'"],
|
||||||
|
scriptSrc: ["'self'"],
|
||||||
|
styleSrc: ["'self'", "'unsafe-inline'"],
|
||||||
|
imgSrc: ["'self'", 'data:'],
|
||||||
|
formAction: ["'self'"],
|
||||||
|
frameAncestors: ["'none'"],
|
||||||
|
objectSrc: ["'none'"],
|
||||||
|
};
|
||||||
|
|
||||||
initSignal();
|
initSignal();
|
||||||
|
|
||||||
if (!API_KEY) {
|
if (!API_KEY) {
|
||||||
|
|
@ -37,43 +47,8 @@ if (PROTON_IMAP_USERNAME && PROTON_IMAP_PASSWORD) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getLanIP = () => {
|
|
||||||
const nets = networkInterfaces();
|
|
||||||
|
|
||||||
for (const name of Object.keys(nets)) {
|
|
||||||
const interfaces = nets[name];
|
|
||||||
|
|
||||||
if (!interfaces) continue;
|
|
||||||
|
|
||||||
for (const iface of interfaces) {
|
|
||||||
if (iface.family === 'IPv4' && !iface.internal) {
|
|
||||||
return iface.address;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const app = new Hono();
|
const app = new Hono();
|
||||||
|
|
||||||
const cspConfig = {
|
|
||||||
defaultSrc: ["'self'"],
|
|
||||||
scriptSrc: ["'self'"],
|
|
||||||
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
||||||
imgSrc: ["'self'", 'data:'],
|
|
||||||
formAction: ["'self'"],
|
|
||||||
frameAncestors: ["'none'"],
|
|
||||||
objectSrc: ["'none'"],
|
|
||||||
};
|
|
||||||
|
|
||||||
const cspString = Object.entries(cspConfig)
|
|
||||||
.map(([key, values]) => {
|
|
||||||
const directive = key.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
||||||
|
|
||||||
return `${directive} ${values.join(' ')}`;
|
|
||||||
})
|
|
||||||
.join('; ');
|
|
||||||
|
|
||||||
app.use('*', (c, next) => {
|
app.use('*', (c, next) => {
|
||||||
const proto = c.req.header('x-forwarded-proto') || 'http';
|
const proto = c.req.header('x-forwarded-proto') || 'http';
|
||||||
const addr = getConnInfo(c).remote.address;
|
const addr = getConnInfo(c).remote.address;
|
||||||
|
|
@ -84,18 +59,15 @@ app.use('*', (c, next) => {
|
||||||
})(c, next);
|
})(c, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
c.header('Content-Security-Policy', cspString);
|
c.header('Content-Security-Policy', formatToCspString(cspConfig));
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use('*', (c, next) => {
|
app.use('*', timeout(5000));
|
||||||
if (c.req.path === '/link/status-check') {
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
return timeout(5000)(c, next);
|
|
||||||
});
|
|
||||||
app.use('*', compress());
|
app.use('*', compress());
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
'*',
|
'*',
|
||||||
rateLimiter({
|
rateLimiter({
|
||||||
|
|
|
||||||
|
|
@ -24,34 +24,10 @@ const getReconnectDelay = () => {
|
||||||
return delay;
|
return delay;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function startProtonMonitor() {
|
async function sendNotification(from: string, subject: string) {
|
||||||
if (!PROTON_IMAP_USERNAME || !PROTON_IMAP_PASSWORD) {
|
|
||||||
logError('Missing required env vars: PROTON_IMAP_USERNAME and PROTON_IMAP_PASSWORD');
|
|
||||||
logWarn('Run: docker compose run --rm protonmail-bridge init');
|
|
||||||
logWarn('Then use `login` and `info` commands to get IMAP credentials');
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(await hasValidAccount())) {
|
|
||||||
logWarn('Signal account not linked. Proton Mail notifications will be skipped.');
|
|
||||||
logWarn('Link your Signal account to enable email notifications.');
|
|
||||||
}
|
|
||||||
|
|
||||||
logInfo(`Connecting to Proton Bridge at ${PROTON_BRIDGE_HOST}:${PROTON_BRIDGE_PORT}`);
|
|
||||||
logInfo(`Monitoring mailbox: ${PROTON_IMAP_USERNAME}`);
|
|
||||||
|
|
||||||
const imap = new Imap({
|
|
||||||
user: PROTON_IMAP_USERNAME,
|
|
||||||
password: PROTON_IMAP_PASSWORD,
|
|
||||||
host: PROTON_BRIDGE_HOST,
|
|
||||||
port: PROTON_BRIDGE_PORT,
|
|
||||||
keepalive: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
async function sendNotification(from: string, subject: string) {
|
|
||||||
if (!(await hasValidAccount())) {
|
if (!(await hasValidAccount())) {
|
||||||
logVerbose('Skipping notification (Signal not linked)');
|
logVerbose('Skipping notification (Signal not linked)');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,18 +43,38 @@ export async function startProtonMonitor() {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError('Failed to send notification:', error);
|
logError('Failed to send notification:', error);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function startProtonMonitor() {
|
||||||
|
if (!PROTON_IMAP_USERNAME || !PROTON_IMAP_PASSWORD) {
|
||||||
|
logError('Missing required env vars: PROTON_IMAP_USERNAME and PROTON_IMAP_PASSWORD');
|
||||||
|
logWarn('Run: docker compose run --rm protonmail-bridge init');
|
||||||
|
logWarn('Then use `login` and `info` commands to get IMAP credentials');
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logInfo(`Connecting to Proton Bridge at ${PROTON_BRIDGE_HOST}:${PROTON_BRIDGE_PORT}`);
|
||||||
|
logInfo(`Monitoring mailbox: ${PROTON_IMAP_USERNAME}`);
|
||||||
|
|
||||||
|
const imap = new Imap({
|
||||||
|
user: PROTON_IMAP_USERNAME,
|
||||||
|
password: PROTON_IMAP_PASSWORD,
|
||||||
|
host: PROTON_BRIDGE_HOST,
|
||||||
|
port: PROTON_BRIDGE_PORT,
|
||||||
|
keepalive: true,
|
||||||
|
});
|
||||||
|
|
||||||
const openInbox = () =>
|
const openInbox = () =>
|
||||||
imap.openBox('INBOX', false, (err, box) => {
|
imap.openBox('INBOX', false, (err, box) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
logError('Failed to open inbox:', err);
|
logError('Failed to open inbox:', err);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!monitorStartTime) {
|
if (!monitorStartTime) {
|
||||||
monitorStartTime = Date.now();
|
monitorStartTime = Date.now();
|
||||||
logVerbose(`Inbox opened with ${box.messages.total} existing messages`);
|
|
||||||
} else {
|
} else {
|
||||||
logVerbose('Inbox reopened (reconnection)');
|
logVerbose('Inbox reopened (reconnection)');
|
||||||
}
|
}
|
||||||
|
|
@ -124,6 +120,7 @@ export async function startProtonMonitor() {
|
||||||
const from = nameMatch?.[1]?.trim() || rawFrom;
|
const from = nameMatch?.[1]?.trim() || rawFrom;
|
||||||
|
|
||||||
reconnectAttempts = 0;
|
reconnectAttempts = 0;
|
||||||
|
|
||||||
sendNotification(from, subject);
|
sendNotification(from, subject);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -149,6 +146,7 @@ export async function startProtonMonitor() {
|
||||||
|
|
||||||
const delay = getReconnectDelay();
|
const delay = getReconnectDelay();
|
||||||
logInfo(`Attempting to reconnect in ${delay / 1000}s (attempt ${reconnectAttempts})...`);
|
logInfo(`Attempting to reconnect in ${delay / 1000}s (attempt ${reconnectAttempts})...`);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!imapConnected) {
|
if (!imapConnected) {
|
||||||
logInfo('Reconnecting to Proton Bridge...');
|
logInfo('Reconnecting to Proton Bridge...');
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,24 @@ import {
|
||||||
VERBOSE_LOGGING,
|
VERBOSE_LOGGING,
|
||||||
} from '@/constants/config';
|
} from '@/constants/config';
|
||||||
import { SIGNAL_CLI, SIGNAL_CLI_DATA, SIGNAL_CLI_SOCKET } from '@/constants/paths';
|
import { SIGNAL_CLI, SIGNAL_CLI_DATA, SIGNAL_CLI_SOCKET } from '@/constants/paths';
|
||||||
import type { ListAccountsResult, StartLinkResult, UpdateGroupResult } from '@/types';
|
|
||||||
import { formatPhoneNumber } from '@/utils/format';
|
import { formatPhoneNumber } from '@/utils/format';
|
||||||
import { logError, logInfo, logSuccess, logVerbose, logWarn } from '@/utils/log';
|
import { logError, logInfo, logSuccess, logVerbose, logWarn } from '@/utils/log';
|
||||||
import { call } from '@/utils/rpc';
|
import { call } from '@/utils/rpc';
|
||||||
|
|
||||||
|
interface StartLinkResult {
|
||||||
|
deviceLinkUri: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateGroupResult {
|
||||||
|
groupId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListAccountsResult = {
|
||||||
|
number: string;
|
||||||
|
name?: string;
|
||||||
|
uuid?: string;
|
||||||
|
}[];
|
||||||
|
|
||||||
let account: string | null = null;
|
let account: string | null = null;
|
||||||
let currentLinkUri: string | null = null;
|
let currentLinkUri: string | null = null;
|
||||||
let daemon: ReturnType<typeof Bun.spawn> | null = null;
|
let daemon: ReturnType<typeof Bun.spawn> | null = null;
|
||||||
|
|
@ -27,20 +40,21 @@ export async function initSignal({ accountOverride }: { accountOverride?: string
|
||||||
if (accountOverride) {
|
if (accountOverride) {
|
||||||
account = accountOverride;
|
account = accountOverride;
|
||||||
logVerbose(`Signal account set: ${account}`);
|
logVerbose(`Signal account set: ${account}`);
|
||||||
logSuccess('Signal account linked');
|
|
||||||
return { linked: true, account };
|
return { linked: true, account };
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = (await call('listAccounts', {}, null)) as ListAccountsResult;
|
const result = (await call('listAccounts', {}, null)) as ListAccountsResult;
|
||||||
const [firstAccount] = result;
|
const [firstAccount] = result;
|
||||||
|
|
||||||
if (firstAccount) {
|
if (firstAccount) {
|
||||||
account = firstAccount.number;
|
account = firstAccount.number;
|
||||||
logVerbose(`Signal account initialized: ${formatPhoneNumber(account)}`);
|
logVerbose(`Signal account initialized: ${formatPhoneNumber(account)}`);
|
||||||
logSuccess('Signal account linked');
|
|
||||||
return { linked: true, account };
|
return { linked: true, account };
|
||||||
}
|
}
|
||||||
|
|
||||||
logVerbose('No Signal accounts found');
|
logVerbose('No Signal accounts found');
|
||||||
|
|
||||||
return { linked: false, account: null };
|
return { linked: false, account: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,7 +104,9 @@ export async function finishLink() {
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|
||||||
logSuccess('Device linked successfully');
|
logSuccess('Device linked successfully');
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,6 +119,7 @@ async function unlinkDevice() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await rm(SIGNAL_CLI_DATA, { recursive: true, force: true });
|
await rm(SIGNAL_CLI_DATA, { recursive: true, force: true });
|
||||||
|
|
||||||
logSuccess('Account data removed');
|
logSuccess('Account data removed');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError('Failed to remove account data directory:', error);
|
logError('Failed to remove account data directory:', error);
|
||||||
|
|
@ -155,6 +172,7 @@ export async function sendGroupMessage(
|
||||||
export async function checkSignalCli() {
|
export async function checkSignalCli() {
|
||||||
try {
|
try {
|
||||||
await call('listAccounts', {}, account);
|
await call('listAccounts', {}, account);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -164,6 +182,7 @@ export async function checkSignalCli() {
|
||||||
export async function hasValidAccount() {
|
export async function hasValidAccount() {
|
||||||
try {
|
try {
|
||||||
const result = (await call('listAccounts', {}, account)) as ListAccountsResult;
|
const result = (await call('listAccounts', {}, account)) as ListAccountsResult;
|
||||||
|
|
||||||
return result.length > 0;
|
return result.length > 0;
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -233,6 +252,7 @@ export async function startDaemon() {
|
||||||
|
|
||||||
for (let i = 0; i < 60; i++) {
|
for (let i = 0; i < 60; i++) {
|
||||||
await Bun.sleep(500);
|
await Bun.sleep(500);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const socket = await Bun.connect({
|
const socket = await Bun.connect({
|
||||||
unix: SIGNAL_CLI_SOCKET,
|
unix: SIGNAL_CLI_SOCKET,
|
||||||
|
|
@ -240,22 +260,31 @@ export async function startDaemon() {
|
||||||
data() {},
|
data() {},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.end();
|
socket.end();
|
||||||
|
|
||||||
logSuccess(`signal-cli daemon started (${(i + 1) * 0.5}s)`);
|
logSuccess(`signal-cli daemon started (${(i + 1) * 0.5}s)`);
|
||||||
|
|
||||||
daemon = proc;
|
daemon = proc;
|
||||||
|
|
||||||
return proc;
|
return proc;
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authError && !cleaned) {
|
if (authError && !cleaned) {
|
||||||
logWarn('Detected stale account data, cleaning up and retrying...');
|
logWarn('Detected stale account data, cleaning up and retrying...');
|
||||||
|
|
||||||
proc.kill();
|
proc.kill();
|
||||||
|
|
||||||
await unlinkDevice();
|
await unlinkDevice();
|
||||||
|
|
||||||
cleaned = true;
|
cleaned = true;
|
||||||
|
|
||||||
return startDaemon();
|
return startDaemon();
|
||||||
}
|
}
|
||||||
|
|
||||||
logError('Failed to connect to signal-cli socket: daemon did not start within 30 seconds');
|
logError('Failed to connect to signal-cli socket: daemon did not start within 30 seconds');
|
||||||
|
|
||||||
if (proc.exitCode !== null) {
|
if (proc.exitCode !== null) {
|
||||||
logError('signal-cli process exited with code:', proc.exitCode);
|
logError('signal-cli process exited with code:', proc.exitCode);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { SUP_ENDPOINT_PREFIX } from '@/constants/config';
|
import { SUP_ENDPOINT_PREFIX } from '@/constants/config';
|
||||||
|
|
||||||
export interface UnifiedPushMessage {
|
interface UnifiedPushMessage {
|
||||||
endpoint: string;
|
endpoint: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
body?: string;
|
body?: string;
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@
|
||||||
<h2>Endpoints</h2>
|
<h2>Endpoints</h2>
|
||||||
<div id="endpoints-list"
|
<div id="endpoints-list"
|
||||||
hx-get="/fragment/endpoints"
|
hx-get="/fragment/endpoints"
|
||||||
hx-trigger="load"
|
hx-trigger="load, every 10s"
|
||||||
hx-indicator="#endpoints-list">
|
hx-indicator="#endpoints-list">
|
||||||
<div class="loading">
|
<div class="loading">
|
||||||
<div class="spinner"></div>
|
<div class="spinner"></div>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import {
|
||||||
generateLinkQR,
|
generateLinkQR,
|
||||||
getAccount,
|
getAccount,
|
||||||
hasValidAccount,
|
hasValidAccount,
|
||||||
initSignal,
|
|
||||||
} from '@/modules/signal';
|
} from '@/modules/signal';
|
||||||
import { getAllMappings, remove } from '@/modules/store';
|
import { getAllMappings, remove } from '@/modules/store';
|
||||||
import { verifyApiKey } from '@/utils/auth';
|
import { verifyApiKey } from '@/utils/auth';
|
||||||
|
|
@ -19,7 +18,7 @@ let qrCacheTime = 0;
|
||||||
let generatingPromise: Promise<string> | null = null;
|
let generatingPromise: Promise<string> | null = null;
|
||||||
const QR_CACHE_TTL = 10 * 60 * 1000;
|
const QR_CACHE_TTL = 10 * 60 * 1000;
|
||||||
|
|
||||||
const admin = new Hono();
|
export const admin = new Hono();
|
||||||
|
|
||||||
admin.use(
|
admin.use(
|
||||||
'*',
|
'*',
|
||||||
|
|
@ -166,12 +165,7 @@ const handleQRSection = async () => {
|
||||||
cachedQR = qr;
|
cachedQR = qr;
|
||||||
qrCacheTime = Date.now();
|
qrCacheTime = Date.now();
|
||||||
|
|
||||||
finishLink()
|
finishLink().finally(() => {
|
||||||
.then(async () => {
|
|
||||||
await initSignal();
|
|
||||||
})
|
|
||||||
.catch(() => {})
|
|
||||||
.finally(() => {
|
|
||||||
generatingPromise = null;
|
generatingPromise = null;
|
||||||
cachedQR = null;
|
cachedQR = null;
|
||||||
qrCacheTime = 0;
|
qrCacheTime = 0;
|
||||||
|
|
@ -201,5 +195,3 @@ const handleQRSection = async () => {
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default admin;
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { getOrCreateGroup } from '@/modules/store';
|
||||||
import { verifyApiKey } from '@/utils/auth';
|
import { verifyApiKey } from '@/utils/auth';
|
||||||
import { logError, logVerbose } from '@/utils/log';
|
import { logError, logVerbose } from '@/utils/log';
|
||||||
|
|
||||||
const ntfy = new Hono();
|
export const ntfy = new Hono();
|
||||||
|
|
||||||
ntfy.use(
|
ntfy.use(
|
||||||
'*',
|
'*',
|
||||||
|
|
@ -64,5 +64,3 @@ ntfy.post('/:topic', async (c) => {
|
||||||
return c.text('Internal server error', 500);
|
return c.text('Internal server error', 500);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default ntfy;
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { sendGroupMessage } from '@/modules/signal';
|
||||||
import { getGroupId, getOrCreateGroup, remove } from '@/modules/store';
|
import { getGroupId, getOrCreateGroup, remove } from '@/modules/store';
|
||||||
import { formatAsSignalMessage, parseUnifiedPushRequest } from '@/modules/unified-push';
|
import { formatAsSignalMessage, parseUnifiedPushRequest } from '@/modules/unified-push';
|
||||||
|
|
||||||
const unifiedpush = new Hono();
|
export const unifiedpush = new Hono();
|
||||||
|
|
||||||
unifiedpush.get('/up', (c) =>
|
unifiedpush.get('/up', (c) =>
|
||||||
c.json({
|
c.json({
|
||||||
|
|
@ -45,5 +45,3 @@ unifiedpush.delete('/up/:instance', async (c) => {
|
||||||
remove(endpointId);
|
remove(endpointId);
|
||||||
return c.body(null, 204);
|
return c.body(null, 204);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default unifiedpush;
|
|
||||||
|
|
|
||||||
36
server/types.d.ts
vendored
36
server/types.d.ts
vendored
|
|
@ -1,36 +0,0 @@
|
||||||
export interface JsonRpcRequest {
|
|
||||||
jsonrpc: '2.0';
|
|
||||||
id: number;
|
|
||||||
method: string;
|
|
||||||
params: Record<string, unknown>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface JsonRpcResponse<T = unknown> {
|
|
||||||
jsonrpc: '2.0';
|
|
||||||
id: number;
|
|
||||||
result?: T;
|
|
||||||
error?: {
|
|
||||||
code: number;
|
|
||||||
message: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StartLinkResult {
|
|
||||||
deviceLinkUri: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FinishLinkResult {
|
|
||||||
account: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UpdateGroupResult {
|
|
||||||
groupId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AccountInfo {
|
|
||||||
number: string;
|
|
||||||
name?: string;
|
|
||||||
uuid?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ListAccountsResult = AccountInfo[];
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { timingSafeEqual } from 'node:crypto';
|
import { timingSafeEqual } from 'node:crypto';
|
||||||
|
import { networkInterfaces } from 'node:os';
|
||||||
import type { Context } from 'hono';
|
import type { Context } from 'hono';
|
||||||
import { getConnInfo } from 'hono/bun';
|
import { getConnInfo } from 'hono/bun';
|
||||||
import { ALLOW_INSECURE_HTTP, API_KEY } from '@/constants/config';
|
import { ALLOW_INSECURE_HTTP, API_KEY } from '@/constants/config';
|
||||||
|
|
@ -10,6 +11,7 @@ export const isLocalIP = (addr: string | undefined) => {
|
||||||
if (octets.length !== 4 || octets.some((n) => Number.isNaN(n))) return false;
|
if (octets.length !== 4 || octets.some((n) => Number.isNaN(n))) return false;
|
||||||
|
|
||||||
const [a, b] = octets;
|
const [a, b] = octets;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
a === 127 ||
|
a === 127 ||
|
||||||
a === 10 ||
|
a === 10 ||
|
||||||
|
|
@ -35,3 +37,21 @@ export const verifyApiKey = (password: string, c?: Context) => {
|
||||||
|
|
||||||
return timingSafeEqual(providedBuffer, keyBuffer);
|
return timingSafeEqual(providedBuffer, keyBuffer);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getLanIP = () => {
|
||||||
|
const nets = networkInterfaces();
|
||||||
|
|
||||||
|
for (const name of Object.keys(nets)) {
|
||||||
|
const interfaces = nets[name];
|
||||||
|
|
||||||
|
if (!interfaces) continue;
|
||||||
|
|
||||||
|
for (const iface of interfaces) {
|
||||||
|
if (iface.family === 'IPv4' && !iface.internal) {
|
||||||
|
return iface.address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -33,3 +33,8 @@ export const formatPhoneNumber = (phone: string): string => {
|
||||||
|
|
||||||
return phone;
|
return phone;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const formatToCspString = (cspConfig: Record<string, string[]>) =>
|
||||||
|
Object.entries(cspConfig)
|
||||||
|
.map(([key, values]) => `${key.replace(/([A-Z])/g, '-$1').toLowerCase()} ${values.join(' ')}`)
|
||||||
|
.join('; ');
|
||||||
|
|
|
||||||
|
|
@ -26,14 +26,15 @@ export const call = (method: string, params: Record<string, unknown>, account: s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error(_socket, error) {
|
error(_, error) {
|
||||||
reject(error);
|
reject(error);
|
||||||
},
|
},
|
||||||
connectError(_socket, error) {
|
connectError(_, error) {
|
||||||
reject(error);
|
reject(error);
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
const isComplete = response.includes(MESSAGE_DELIMITER);
|
const isComplete = response.includes(MESSAGE_DELIMITER);
|
||||||
|
|
||||||
if (!isComplete) {
|
if (!isComplete) {
|
||||||
reject(new Error('Connection closed before response received'));
|
reject(new Error('Connection closed before response received'));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue