meta-sovereign 0.50.0 → 0.51.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.hi.md +3 -0
- package/README.md +3 -0
- package/README.ru.md +3 -0
- package/README.zh.md +3 -0
- package/js/src/cli/automation-commands.js +2 -1
- package/js/src/cli/contact-commands.js +2 -1
- package/js/src/cli/cv-commands.js +5 -4
- package/js/src/cli/greetings-commands.js +2 -1
- package/js/src/cli/index.js +44 -24
- package/js/src/cli/migrate-commands.js +2 -1
- package/js/src/cli/trust-commands.js +3 -2
- package/js/src/cv/browser.js +42 -2
- package/js/src/server/cors.js +11 -0
- package/js/src/server/index.js +4 -1
- package/js/src/server/log.js +2 -1
- package/js/src/server/routes-telegram.js +2 -13
- package/js/src/server/telegram-route-error.js +29 -0
- package/js/src/sources/telegram-account.js +10 -1
- package/js/src/sources/telegram-backfill.js +49 -25
- package/js/src/sources/telegram-catchup.js +24 -10
- package/js/src/sources/telegram-channels.js +13 -4
- package/js/src/sources/telegram-history.js +12 -0
- package/js/src/sources/telegram-pages.js +37 -10
- package/js/src/storage/app-directory.js +78 -0
- package/js/src/web/app.css +112 -1
- package/js/src/web/app.min.js +87 -87
- package/js/src/web/chat-calls.js +23 -9
- package/js/src/web/chat-folder-model.js +3 -2
- package/js/src/web/chat-folders.js +16 -10
- package/js/src/web/chat-format-bar.js +2 -1
- package/js/src/web/chat-history-load.js +80 -0
- package/js/src/web/chat-history-status.js +94 -0
- package/js/src/web/chat-profile.js +8 -3
- package/js/src/web/chat-sidebar.js +61 -9
- package/js/src/web/chat-state.js +37 -32
- package/js/src/web/chat-tab-scroll.js +40 -0
- package/js/src/web/chat-view.js +46 -16
- package/js/src/web/chat-window.js +95 -74
- package/js/src/web/client-log.js +8 -2
- package/js/src/web/connection-guides.js +8 -0
- package/js/src/web/icons.js +8 -0
- package/js/src/web/locales/en-chat.js +14 -1
- package/js/src/web/locales/en.js +9 -0
- package/js/src/web/locales/hi-chat.js +14 -1
- package/js/src/web/locales/hi.js +10 -0
- package/js/src/web/locales/ru-chat.js +13 -1
- package/js/src/web/locales/ru.js +10 -0
- package/js/src/web/locales/zh-chat.js +11 -1
- package/js/src/web/locales/zh.js +9 -0
- package/js/src/web/nav-items.js +1 -0
- package/js/src/web/panel-resize.js +95 -0
- package/js/src/web/peer-avatar.js +9 -2
- package/js/src/web/person-panel.js +11 -0
- package/js/src/web/settings-view.js +19 -0
- package/js/src/web/shell/app-shell.js +12 -1
- package/js/src/web/views.js +28 -1
- package/package.json +3 -1
package/README.hi.md
CHANGED
|
@@ -61,6 +61,8 @@ Default UI hosted app है: `serve` उसका पता छापता ह
|
|
|
61
61
|
|
|
62
62
|
macOS पर interactive `serve` पूरा setup करता है: इस installation की TLS key को Secure Enclave से protect करता है, certificate trust जाँचता है, जरूरत पर ही trust माँगता है और GitHub Pages app खोलता है। Headless launch: `node js/bin/meta-sovereign.js serve --trust=true --open=true`। `--trust=false` और `--open=false` से opt out करें। Swift helper को macOS Command Line Tools चाहिए; unsupported Mac पर `--hardware-key=false` स्पष्ट रूप से file key चुनता है। Hardware protection disk की key पर लागू है; Node key को memory में उपयोग करता है। नियम और सीमाएँ: [local server setup और security](docs/LOCAL-SERVER-SECURITY.md)।
|
|
63
63
|
|
|
64
|
+
खुली चैट पहले लोड होती है और लोडिंग या पुनःप्रयास की स्थिति दिखाती है। सभी / निजी / समूह / चैनल फ़िल्टर हर Telegram फ़ोल्डर में उपलब्ध हैं। कॉलम की चौड़ाई ऊर्ध्वाधर डिवाइडर खींचकर बदलें; आकार याद रखे जाते हैं। कॉल मुख्य मेनू में चैट के बाद है और Telegram खाता संपादन प्रोफ़ाइल में है। डार्क थीम में नियंत्रण सफ़ेद रेखा आइकन इस्तेमाल करते हैं। JS सर्वर OS उपयोगकर्ता की ऐप फ़ोल्डर में डेटा रखता है: macOS में `~/Library/Application Support/Meta Sovereign/store`, Windows में `%APPDATA%/Meta Sovereign/store` और Linux में `${XDG_CONFIG_HOME:-~/.config}/Meta Sovereign/store`। पुराने checkout से `serve` चलाने पर `.meta-sovereign` एक बार स्थानांतरित होता है; संदेश, संपर्क, सत्र, मीडिया और प्रमाणपत्र सुरक्षित रहते हैं तथा लिंक पुराने पथ बनाए रखता है। रीस्टार्ट और अलग कार्य निर्देशिका में वही स्टोर इस्तेमाल होता है। `--store` या `META_SOVEREIGN_STORE_DIR` से बदलें। [चैट और डेटा संग्रहण](docs/CHAT-READINESS.md) देखें।
|
|
65
|
+
|
|
64
66
|
## SPA को server से जोड़ें
|
|
65
67
|
|
|
66
68
|
SPA server को इस क्रम में चुनता है ([`js/src/web/discover.js`](js/src/web/discover.js)):
|
|
@@ -81,6 +83,7 @@ SPA server को इस क्रम में चुनता है ([`js/src
|
|
|
81
83
|
- **Telegram के अपने ऐप जैसी चैट**: हर संदेश एक बार भेजा जाता है, डिलीवरी टिक, लाइव अपडेट और अंतराल के बाद catch-up, पढ़ा-चिह्न, cloud ड्राफ़्ट, reload के बाद भी बचे रहने वाले retry ([`docs/CHAT-REFERENCES.hi.md`](docs/CHAT-REFERENCES.hi.md))।
|
|
82
84
|
- **CRM**: किसी संपर्क के बारे में सब कुछ एक दृश्य में, अलग-अलग नेटवर्क पर वही व्यक्ति, communities और सदस्यताएँ, खोज, नियम से ऑडियंस (`friends:vk & chat:vk:… - list:blocked`)।
|
|
83
85
|
- **संपर्क आयात**: macOS Contacts (सिंक किए गए iCloud खातों सहित) या किसी भी प्लेटफ़ॉर्म के vCard निर्यात का पूर्वावलोकन करके आयात करें। दोबारा आयात करने पर रिकॉर्ड की पहचान बनी रहती है। निर्देशिका सिंक के दौरान अपडेट होती है और बिना संदेश वाले लोग भी दिखाती है। [संपर्क आयात](docs/CONTACTS-IMPORT.md) देखें।
|
|
86
|
+
|
|
84
87
|
- **Outreach, broadcast और ऑटोमेशन**: किसे, कब, किन शर्तों पर और किस पाठ से लिखना है; कौन क्या पाएगा इसका preview; डिफ़ॉल्ट रूप से सुरक्षित ड्राफ़्ट, या quiet hours, हर चैट का cooldown, दैनिक सीमा और opt-out सूची के साथ धीमी गति से स्वचालित भेजना। हर स्वचालित भेजना एक ही outbox से जाता है: अधिक से अधिक एक बार, और अस्पष्ट परिणाम अपने आप दोहराया नहीं जाता ([`docs/USER-GUIDE.hi.md`](docs/USER-GUIDE.hi.md), §11)।
|
|
85
88
|
- **Operator कतार और उत्तर**: अपठित संदेश चैट-दर-चैट, आपके पिछले उत्तरों से सुझाव (Tab एक चुनता है), पैटर्न और उत्तर के रूप, मिलते संदेश पर auto-reply।
|
|
86
89
|
- **व्यवसाय और community खाते**: VK community token Bots Long Poll चलाता है; Telegram Business के quick replies, स्वागत और अनुपस्थिति संदेश, कार्य-समय और chatbots।
|
package/README.md
CHANGED
|
@@ -61,6 +61,8 @@ The hosted app is the default UI: `serve` prints its address, <https://link-foun
|
|
|
61
61
|
|
|
62
62
|
On macOS, interactive `serve` now handles the full setup: it protects this installation’s TLS key with Secure Enclave, detects existing certificate trust, requests trust only when needed, and opens the GitHub Pages app. For a headless launch use `node js/bin/meta-sovereign.js serve --trust=true --open=true`. `--trust=false` and `--open=false` opt out. The Swift helper needs the macOS Command Line Tools; `--hardware-key=false` explicitly opts into a file key on unsupported Macs. Hardware protection applies to the key on disk; Node uses it in memory. See [local server setup and security](docs/LOCAL-SERVER-SECURITY.md) for the exact access rules and limits.
|
|
63
63
|
|
|
64
|
+
Open conversations load first and show loading or retry status until their history is ready. All / Private / Groups / Channels filters remain available inside every Telegram folder. Drag the vertical dividers to resize columns; widths are remembered. Calls is in the main menu after Chat, and Telegram account editing is under Profile. The default dark controls use white line icons. The JS server keeps its store under your OS user: `~/Library/Application Support/Meta Sovereign/store` on macOS, `%APPDATA%/Meta Sovereign/store` on Windows, and `${XDG_CONFIG_HOME:-~/.config}/Meta Sovereign/store` on Linux. Starting `serve` from an older checkout migrates its `.meta-sovereign` store once, preserving messages, contacts, sessions, media and certificate; a compatibility link preserves old paths. Restarts and a different working directory reuse the same store. `--store` or `META_SOVEREIGN_STORE_DIR` override it. See [chat readiness and data storage](docs/CHAT-READINESS.md).
|
|
65
|
+
|
|
64
66
|
## Connect the SPA to your server
|
|
65
67
|
|
|
66
68
|
The SPA picks a server in this order ([`js/src/web/discover.js`](js/src/web/discover.js)):
|
|
@@ -81,6 +83,7 @@ The full user-facing flow — install nothing, install Rust server, install JS s
|
|
|
81
83
|
- **Chat that behaves like Telegram's own apps**: each message sent once, delivery ticks, live updates and catch-up after a gap, read marking, cloud drafts, retries that survive a reload. How the open-source Telegram, Signal and Element clients do it, pinned to the commits we read, is in [`docs/CHAT-REFERENCES.md`](docs/CHAT-REFERENCES.md).
|
|
82
84
|
- **CRM**: everything known about a contact in one view, the same person across networks, communities and memberships, search, audiences by rule (`friends:vk & chat:vk:… - list:blocked`).
|
|
83
85
|
- **Contact imports**: preview and import macOS Contacts (including synced iCloud accounts) or vCard exports from any platform; repeated imports keep stable identities. The directory updates during sync and includes people without messages. See [contact imports](docs/CONTACTS-IMPORT.md).
|
|
86
|
+
|
|
84
87
|
- **Outreach, broadcasts and automations**: who to message, when, on what conditions and with which text; a preview of who gets what; safe drafts by default or paced auto-send with quiet hours, a per-chat cooldown, daily caps and an opt-out list. Every automated send goes through one outbox, so it happens at most once and an unclear result is never retried by itself ([`docs/USER-GUIDE.md` §11](docs/USER-GUIDE.md#11-automations-message-people-by-rule-safely)).
|
|
85
88
|
- **Operator queue and replies**: walk unread messages chat by chat, reply suggestions from your own past answers (Tab takes one), patterns and reply variations, auto-replies on a matching message.
|
|
86
89
|
- **Business and community accounts**: a VK community token runs the Bots Long Poll; Telegram Business quick replies, greeting and away messages, hours and chatbots.
|
package/README.ru.md
CHANGED
|
@@ -61,6 +61,8 @@ node js/bin/meta-sovereign.js serve
|
|
|
61
61
|
|
|
62
62
|
В macOS интерактивный `serve` выполняет настройку целиком: защищает TLS-ключ этой установки через Secure Enclave, проверяет доверие сертификату, запрашивает его только при необходимости и открывает приложение на GitHub Pages. Без интерактивного терминала: `node js/bin/meta-sovereign.js serve --trust=true --open=true`. `--trust=false` и `--open=false` отключают эти действия. Для Swift-помощника нужны macOS Command Line Tools; `--hardware-key=false` явно выбирает файловый ключ на неподдерживаемом Mac. Аппаратная защита действует на диске; Node использует ключ в памяти. Правила и ограничения: [настройка и безопасность локального сервера](docs/LOCAL-SERVER-SECURITY.md).
|
|
63
63
|
|
|
64
|
+
Открытый чат загружается первым и показывает загрузку или ожидание повторной попытки. Фильтры Все / Личные / Группы / Каналы доступны внутри каждой папки Telegram. Вертикальные разделители меняют ширину столбцов; размеры запоминаются. Звонки находятся в главном меню после Чата, редактор аккаунта Telegram — в Профиле. В тёмной теме элементы управления используют белые контурные значки. JS-сервер хранит данные в папке пользователя ОС: `~/Library/Application Support/Meta Sovereign/store` в macOS, `%APPDATA%/Meta Sovereign/store` в Windows и `${XDG_CONFIG_HOME:-~/.config}/Meta Sovereign/store` в Linux. Запуск `serve` из старого checkout однократно переносит `.meta-sovereign`, сохраняя сообщения, контакты, сессии, медиа и сертификат; ссылка сохраняет старые пути. Перезапуск и другой рабочий каталог используют то же хранилище. Переопределение: `--store` или `META_SOVEREIGN_STORE_DIR`. См. [чаты и хранение данных](docs/CHAT-READINESS.md).
|
|
65
|
+
|
|
64
66
|
## Подключение SPA к серверу
|
|
65
67
|
|
|
66
68
|
SPA выбирает сервер в таком порядке ([`js/src/web/discover.js`](js/src/web/discover.js)):
|
|
@@ -81,6 +83,7 @@ SPA выбирает сервер в таком порядке ([`js/src/web/dis
|
|
|
81
83
|
- **Чат, который ведёт себя как приложения Telegram**: каждое сообщение отправляется один раз, галочки доставки, живые обновления и догрузка после разрыва, отметки о прочтении, облачные черновики, повторы, переживающие перезагрузку ([`docs/CHAT-REFERENCES.ru.md`](docs/CHAT-REFERENCES.ru.md)).
|
|
82
84
|
- **CRM**: всё о контакте в одном окне, один и тот же человек в разных сетях, сообщества и членства, поиск, аудитории по правилам (`friends:vk & chat:vk:… - list:blocked`).
|
|
83
85
|
- **Импорт контактов**: предпросмотр и импорт из Контактов macOS (включая синхронизированные учётные записи iCloud) или файлов vCard на любой платформе; повторный импорт сохраняет идентичность записей. Список обновляется при синхронизации и включает людей без сообщений. См. [импорт контактов](docs/CONTACTS-IMPORT.md).
|
|
86
|
+
|
|
84
87
|
- **Рассылки, broadcast и автоматизации**: кому писать, когда, при каких условиях и каким текстом; предпросмотр, кто что получит; по умолчанию безопасные черновики или отправка с паузами, тихими часами, перерывом для каждого чата, дневными лимитами и списком отписавшихся. Каждая автоматическая отправка идёт через один outbox: не больше одного раза, а неясный результат сам не повторяется ([`docs/USER-GUIDE.ru.md`](docs/USER-GUIDE.ru.md), §11).
|
|
85
88
|
- **Очередь оператора и ответы**: непрочитанные сообщения чат за чатом, подсказки ответов из ваших прошлых ответов (Tab берёт подсказку), паттерны и варианты ответов, автоответы на подходящее сообщение.
|
|
86
89
|
- **Аккаунты бизнеса и сообществ**: токен сообщества VK запускает Bots Long Poll; Telegram Business: быстрые ответы, приветствие и сообщение об отсутствии, часы работы, чат-боты.
|
package/README.zh.md
CHANGED
|
@@ -61,6 +61,8 @@ node js/bin/meta-sovereign.js serve
|
|
|
61
61
|
|
|
62
62
|
在 macOS 交互式终端中,`serve` 会完成整个设置:使用 Secure Enclave 保护本次安装的 TLS 私钥,检测证书是否已受信任,仅在需要时请求信任,并打开 GitHub Pages 应用。无交互终端时可运行 `node js/bin/meta-sovereign.js serve --trust=true --open=true`。`--trust=false` 和 `--open=false` 可关闭相应操作。Swift 辅助程序需要 macOS Command Line Tools;不支持的 Mac 可显式使用 `--hardware-key=false` 选择文件密钥。硬件保护针对磁盘上的密钥;Node 在内存中使用密钥。详见[本地服务器设置与安全](docs/LOCAL-SERVER-SECURITY.md)。
|
|
63
63
|
|
|
64
|
+
打开的聊天优先加载,并显示加载或重试状态。全部 / 私聊 / 群组 / 频道筛选在每个 Telegram 文件夹内都可用。拖动竖直分隔线调整列宽,尺寸会保存。通话位于主菜单聊天之后,Telegram 账户编辑移到个人资料。默认深色控件使用白色线条图标。 JS 服务器将数据保存在操作系统用户的应用文件夹:macOS 为 `~/Library/Application Support/Meta Sovereign/store`,Windows 为 `%APPDATA%/Meta Sovereign/store`,Linux 为 `${XDG_CONFIG_HOME:-~/.config}/Meta Sovereign/store`。从旧 checkout 启动 `serve` 会一次性迁移 `.meta-sovereign`,保留消息、联系人、会话、媒体和证书,并用兼容目录链接保留旧路径。重启或改变工作目录仍使用同一存储。可通过 `--store` 或 `META_SOVEREIGN_STORE_DIR` 覆盖。参见[聊天就绪与数据存储](docs/CHAT-READINESS.md)。
|
|
65
|
+
|
|
64
66
|
## 将 SPA 连接到服务器
|
|
65
67
|
|
|
66
68
|
SPA 按以下顺序选择服务器([`js/src/web/discover.js`](js/src/web/discover.js)):
|
|
@@ -81,6 +83,7 @@ SPA 按以下顺序选择服务器([`js/src/web/discover.js`](js/src/web/disco
|
|
|
81
83
|
- **像 Telegram 官方应用一样的聊天**:每条消息只发送一次,送达对勾,实时更新与断线后补齐,已读标记,云端草稿,刷新后仍保留的重试([`docs/CHAT-REFERENCES.zh.md`](docs/CHAT-REFERENCES.zh.md))。
|
|
82
84
|
- **CRM**:一个视图看到联系人的全部信息,跨网络识别同一个人,社区与成员关系,搜索,按规则定义受众(`friends:vk & chat:vk:… - list:blocked`)。
|
|
83
85
|
- **导入联系人**:预览并导入 macOS 通讯录(包括已同步的 iCloud 账户)或任何平台导出的 vCard;重复导入保留记录身份。目录在同步期间自动更新,也包含没有消息的联系人。参见[联系人导入](docs/CONTACTS-IMPORT.md)。
|
|
86
|
+
|
|
84
87
|
- **外联、群发与自动化**:给谁发、何时发、在什么条件下发、发什么内容;预览谁会收到什么;默认生成安全草稿,或按节奏自动发送,并有安静时段、每个聊天的冷却期、每日上限和退订名单。每次自动发送都经过同一个发件箱:最多发送一次,结果不明时不会自行重试([`docs/USER-GUIDE.zh.md`](docs/USER-GUIDE.zh.md) 第 11 节)。
|
|
85
88
|
- **Operator 队列与回复**:逐个聊天处理未读消息,根据您以往的回答给出回复建议(按 Tab 采用),模式与回复变体,对匹配消息自动回复。
|
|
86
89
|
- **企业与社区账号**:VK 社区令牌运行 Bots Long Poll;Telegram Business 的快捷回复、问候与离开消息、营业时间和聊天机器人。
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* the chat menu does); with `--test` the conditions are ignored there.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
+
import { defaultStoreDir } from '../storage/app-directory.js';
|
|
22
23
|
import { promises as fs } from 'node:fs';
|
|
23
24
|
import { createInterface } from 'node:readline/promises';
|
|
24
25
|
import { getSource } from '../sources/index.js';
|
|
@@ -383,7 +384,7 @@ const automationsCmd = async (args, log, deps) => {
|
|
|
383
384
|
log(`unknown subcommand "${sub}"\n${AUTOMATION_HELP}`);
|
|
384
385
|
return 1;
|
|
385
386
|
}
|
|
386
|
-
const store = await deps.openStore(args.store ??
|
|
387
|
+
const store = await deps.openStore(args.store ?? defaultStoreDir());
|
|
387
388
|
const { runner, close } = runnerFor(store, args, deps);
|
|
388
389
|
try {
|
|
389
390
|
return await handler({
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Local address-book imports, with the same parser and plan as the app.
|
|
2
|
+
import { defaultStoreDir } from '../storage/app-directory.js';
|
|
2
3
|
import { promises as fs } from 'node:fs';
|
|
3
4
|
import { decodeVcard, parseVcard } from '../sources/vcard.js';
|
|
4
5
|
import { readSystemContacts } from '../sources/system-contacts.js';
|
|
@@ -17,7 +18,7 @@ export const createContactCommands = ({ openStore }) => ({
|
|
|
17
18
|
log('choose --file=<contacts.vcf> or --system');
|
|
18
19
|
return 1;
|
|
19
20
|
}
|
|
20
|
-
const storeDir = args.store ??
|
|
21
|
+
const storeDir = args.store ?? defaultStoreDir();
|
|
21
22
|
const store = await openStore(storeDir);
|
|
22
23
|
try {
|
|
23
24
|
const rows = system
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* this module importing the entry point back.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { defaultStoreDir } from '../storage/app-directory.js';
|
|
14
15
|
import {
|
|
15
16
|
compareAllCvs,
|
|
16
17
|
cvPlatformCatalogue,
|
|
@@ -147,7 +148,7 @@ const planCmd = async (args, log) => {
|
|
|
147
148
|
};
|
|
148
149
|
|
|
149
150
|
const readCmd = async (args, log, openStore, injected) => {
|
|
150
|
-
const store = await openStore(args.store ??
|
|
151
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
151
152
|
const ids = parsePlatforms(args.platforms);
|
|
152
153
|
const { entries, failures } = await readAllCvs(
|
|
153
154
|
ids,
|
|
@@ -168,7 +169,7 @@ const readCmd = async (args, log, openStore, injected) => {
|
|
|
168
169
|
};
|
|
169
170
|
|
|
170
171
|
const diffCmd = async (args, log, openStore) => {
|
|
171
|
-
const store = await openStore(args.store ??
|
|
172
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
172
173
|
const entries = await loadStoredCvs(store, parsePlatforms(args.platforms));
|
|
173
174
|
const comparison = compareAllCvs(entries, { prefer: args.prefer ?? null });
|
|
174
175
|
const diffs = diffAllCvs(entries, null);
|
|
@@ -213,7 +214,7 @@ const applyConfirmed = async (preview, { args, log, ask, run }) => {
|
|
|
213
214
|
};
|
|
214
215
|
|
|
215
216
|
const syncCmd = async (args, log, openStore, injected) => {
|
|
216
|
-
const store = await openStore(args.store ??
|
|
217
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
217
218
|
const ids = parsePlatforms(args.platforms);
|
|
218
219
|
const run = (dryRun, confirmed = null) =>
|
|
219
220
|
syncCvAcross(ids, {
|
|
@@ -256,7 +257,7 @@ const syncCmd = async (args, log, openStore, injected) => {
|
|
|
256
257
|
};
|
|
257
258
|
|
|
258
259
|
const telemetryCmd = async (args, log, openStore) => {
|
|
259
|
-
const store = await openStore(args.store ??
|
|
260
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
260
261
|
const events = await loadCvTelemetry(store, {
|
|
261
262
|
runId: args.run ?? null,
|
|
262
263
|
platform: args.platform ?? null,
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* its own `openStore`, and the tests inject fake source adapters.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { defaultStoreDir } from '../storage/app-directory.js';
|
|
14
15
|
import { parseList } from './cv-commands.js';
|
|
15
16
|
import { getSource } from '../sources/index.js';
|
|
16
17
|
import {
|
|
@@ -40,7 +41,7 @@ const optionalNumber = (value) =>
|
|
|
40
41
|
|
|
41
42
|
const asJson = (value) => JSON.stringify(value, null, 2);
|
|
42
43
|
|
|
43
|
-
const storeOf = (args, openStore) => openStore(args.store ??
|
|
44
|
+
const storeOf = (args, openStore) => openStore(args.store ?? defaultStoreDir());
|
|
44
45
|
|
|
45
46
|
/**
|
|
46
47
|
* `--stickers=vk:21,facebook:369239263222822` → selection entries. The
|
package/js/src/cli/index.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* entry point unit-testable.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { defaultStoreDir, resolveUserStore } from '../storage/app-directory.js';
|
|
16
17
|
import path from 'node:path';
|
|
17
18
|
import { maybeOpenHostedApp } from './browser-open.js';
|
|
18
19
|
import { CONTACTS_HELP, createContactCommands } from './contact-commands.js';
|
|
@@ -144,14 +145,14 @@ const importCmd = async (args, log) => {
|
|
|
144
145
|
const file = args.file;
|
|
145
146
|
const archive = await fs.readFile(file, 'utf8');
|
|
146
147
|
const parsed = file.endsWith('.json') ? JSON.parse(archive) : archive;
|
|
147
|
-
const store = await openStore(args.store ??
|
|
148
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
148
149
|
const n = await importInto(store, args.source, parsed);
|
|
149
150
|
log(`imported ${n} messages from ${args.source}`);
|
|
150
151
|
return 0;
|
|
151
152
|
};
|
|
152
153
|
|
|
153
154
|
const exportCmd = async (args, log) => {
|
|
154
|
-
const store = await openReadOnly(args.store ??
|
|
155
|
+
const store = await openReadOnly(args.store ?? defaultStoreDir());
|
|
155
156
|
const links = await store.query();
|
|
156
157
|
await fs.writeFile(args.file, JSON.stringify(links, null, 2));
|
|
157
158
|
log(`exported ${links.length} links to ${args.file}`);
|
|
@@ -159,7 +160,7 @@ const exportCmd = async (args, log) => {
|
|
|
159
160
|
};
|
|
160
161
|
|
|
161
162
|
const backupCmd = async (args, log) => {
|
|
162
|
-
const store = await openReadOnly(args.store ??
|
|
163
|
+
const store = await openReadOnly(args.store ?? defaultStoreDir());
|
|
163
164
|
const file = await createBackup(store, { archiveDir: args.archive });
|
|
164
165
|
if (args.keep) {
|
|
165
166
|
await pruneBackups({ archiveDir: args.archive, keep: Number(args.keep) });
|
|
@@ -175,7 +176,7 @@ const passcodeCmd = async (args, log) => {
|
|
|
175
176
|
log('usage: meta-sovereign passcode --clear [--store=<dir>]');
|
|
176
177
|
return 1;
|
|
177
178
|
}
|
|
178
|
-
const cleared = clearPasscode(args.store ??
|
|
179
|
+
const cleared = clearPasscode(args.store ?? defaultStoreDir());
|
|
179
180
|
log(cleared ? 'local passcode cleared' : 'no local passcode was set');
|
|
180
181
|
return 0;
|
|
181
182
|
};
|
|
@@ -185,7 +186,7 @@ const restoreCmd = async (args, log) => {
|
|
|
185
186
|
// Swap the whole log in one step; the store must not be in use (R-Z16).
|
|
186
187
|
const n = await restoreBackupToDir(
|
|
187
188
|
args.file,
|
|
188
|
-
args.store ??
|
|
189
|
+
args.store ?? defaultStoreDir(),
|
|
189
190
|
{
|
|
190
191
|
replace: true,
|
|
191
192
|
}
|
|
@@ -193,7 +194,7 @@ const restoreCmd = async (args, log) => {
|
|
|
193
194
|
log(`replaced the store with ${n} records from ${args.file}`);
|
|
194
195
|
return 0;
|
|
195
196
|
}
|
|
196
|
-
const store = await openStore(args.store ??
|
|
197
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
197
198
|
const n = await restoreBackup(store, args.file);
|
|
198
199
|
log(`restored ${n} links from ${args.file}`);
|
|
199
200
|
return 0;
|
|
@@ -235,7 +236,7 @@ const logAddresses = (handle, log) => {
|
|
|
235
236
|
};
|
|
236
237
|
|
|
237
238
|
const serveCmd = async (args, log) => {
|
|
238
|
-
const storeDir = args.store ??
|
|
239
|
+
const storeDir = args.store ?? defaultStoreDir();
|
|
239
240
|
// Verbose logging is on by default: every request, event and error is
|
|
240
241
|
// written to a file in the app folder (and echoed here), so the root
|
|
241
242
|
// cause of any issue is always on disk. `--quiet-logs` opts out.
|
|
@@ -313,7 +314,7 @@ const rawEmailTransport = (args, protocol) =>
|
|
|
313
314
|
: null;
|
|
314
315
|
|
|
315
316
|
const sourcePullCmd = async (args, log) => {
|
|
316
|
-
const store = await openStore(args.store ??
|
|
317
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
317
318
|
const protocol = args.protocol;
|
|
318
319
|
const result = await pullLiveInto(store, args.source, {
|
|
319
320
|
protocol,
|
|
@@ -384,7 +385,7 @@ const githubLiveFromArgs = (args) =>
|
|
|
384
385
|
});
|
|
385
386
|
|
|
386
387
|
const githubCloneCmd = async (args, log) => {
|
|
387
|
-
const store = await openStore(args.store ??
|
|
388
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
388
389
|
const live = githubLiveFromArgs(args);
|
|
389
390
|
const result = await live.cloneRepo({
|
|
390
391
|
owner: args.owner,
|
|
@@ -490,7 +491,7 @@ const peoplePerHourMessageCmd = boardMessageCmd(
|
|
|
490
491
|
);
|
|
491
492
|
|
|
492
493
|
const audienceCmd = async (args, log) => {
|
|
493
|
-
const store = await openReadOnly(args.store ??
|
|
494
|
+
const store = await openReadOnly(args.store ?? defaultStoreDir());
|
|
494
495
|
const all = await store.query();
|
|
495
496
|
const matched = evalAudience(all, args.query ?? '');
|
|
496
497
|
log(JSON.stringify(matched, null, 2));
|
|
@@ -498,7 +499,7 @@ const audienceCmd = async (args, log) => {
|
|
|
498
499
|
};
|
|
499
500
|
|
|
500
501
|
const factsCmd = async (args, log) => {
|
|
501
|
-
const store = await openReadOnly(args.store ??
|
|
502
|
+
const store = await openReadOnly(args.store ?? defaultStoreDir());
|
|
502
503
|
const all = await store.query();
|
|
503
504
|
const messages = all.filter((l) => l.id?.startsWith('msg:'));
|
|
504
505
|
const patterns = all
|
|
@@ -515,7 +516,7 @@ const factsCmd = async (args, log) => {
|
|
|
515
516
|
};
|
|
516
517
|
|
|
517
518
|
const searchCmd = async (args, log) => {
|
|
518
|
-
const store = await openReadOnly(args.store ??
|
|
519
|
+
const store = await openReadOnly(args.store ?? defaultStoreDir());
|
|
519
520
|
const results = await localSearch(store, {
|
|
520
521
|
query: args.query ?? '',
|
|
521
522
|
min: Number(args.min ?? 0.2),
|
|
@@ -525,7 +526,7 @@ const searchCmd = async (args, log) => {
|
|
|
525
526
|
};
|
|
526
527
|
|
|
527
528
|
const broadcastCmd = async (args, log) => {
|
|
528
|
-
const store = await openStore(args.store ??
|
|
529
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
529
530
|
const networks = (args.networks?.split(',') ?? listSources()).filter((n) =>
|
|
530
531
|
listSources().includes(n)
|
|
531
532
|
);
|
|
@@ -543,7 +544,7 @@ const broadcastCmd = async (args, log) => {
|
|
|
543
544
|
};
|
|
544
545
|
|
|
545
546
|
const listByPrefixCmd = (prefix) => async (args, log) => {
|
|
546
|
-
const store = await openReadOnly(args.store ??
|
|
547
|
+
const store = await openReadOnly(args.store ?? defaultStoreDir());
|
|
547
548
|
const all = await store.query();
|
|
548
549
|
log(
|
|
549
550
|
JSON.stringify(
|
|
@@ -566,7 +567,7 @@ const patternsInferCmd = async (args, log) => {
|
|
|
566
567
|
};
|
|
567
568
|
|
|
568
569
|
const graphsRunCmd = async (args, log) => {
|
|
569
|
-
const store = await openStore(args.store ??
|
|
570
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
570
571
|
const persisted = await store.get(args.id);
|
|
571
572
|
if (!persisted) {
|
|
572
573
|
log(`graph ${args.id} not found`);
|
|
@@ -589,7 +590,7 @@ const graphsRunCmd = async (args, log) => {
|
|
|
589
590
|
};
|
|
590
591
|
|
|
591
592
|
const profileCmd = async (args, log) => {
|
|
592
|
-
const store = await openStore(args.store ??
|
|
593
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
593
594
|
if (args.name || args.bio) {
|
|
594
595
|
const profile = {
|
|
595
596
|
id: 'profile:me',
|
|
@@ -619,7 +620,7 @@ const profileCmd = async (args, log) => {
|
|
|
619
620
|
};
|
|
620
621
|
|
|
621
622
|
const resumeCmd = async (args, log) => {
|
|
622
|
-
const store = await openStore(args.store ??
|
|
623
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
623
624
|
if (args.title || args.body) {
|
|
624
625
|
const resume = {
|
|
625
626
|
id: 'resume:me',
|
|
@@ -648,7 +649,7 @@ const resumeCmd = async (args, log) => {
|
|
|
648
649
|
};
|
|
649
650
|
|
|
650
651
|
const syncListenCmd = async (args, log) => {
|
|
651
|
-
const store = await openStore(args.store ??
|
|
652
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
652
653
|
const peer = createPeer(store, {
|
|
653
654
|
node: 'cli-listener',
|
|
654
655
|
schemaVersion: CURRENT_SCHEMA,
|
|
@@ -662,7 +663,7 @@ const syncListenCmd = async (args, log) => {
|
|
|
662
663
|
};
|
|
663
664
|
|
|
664
665
|
const syncConnectCmd = async (args, log) => {
|
|
665
|
-
const store = await openStore(args.store ??
|
|
666
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
666
667
|
const peer = createPeer(store, {
|
|
667
668
|
node: 'cli-client',
|
|
668
669
|
schemaVersion: CURRENT_SCHEMA,
|
|
@@ -680,7 +681,7 @@ const syncConnectCmd = async (args, log) => {
|
|
|
680
681
|
};
|
|
681
682
|
|
|
682
683
|
const outreachCmd = async (args, log) => {
|
|
683
|
-
const store = await openStore(args.store ??
|
|
684
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
684
685
|
const all = await store.query();
|
|
685
686
|
const matched = evalAudience(all, args.query ?? '');
|
|
686
687
|
const replyGroup = args.reply
|
|
@@ -702,7 +703,7 @@ const exportEncryptedCmd = async (args, log) => {
|
|
|
702
703
|
log('export-encrypted: --passphrase is required (R-K13)');
|
|
703
704
|
return 1;
|
|
704
705
|
}
|
|
705
|
-
const store = await openReadOnly(args.store ??
|
|
706
|
+
const store = await openReadOnly(args.store ?? defaultStoreDir());
|
|
706
707
|
const file = await writeEncryptedExport(store, args.file, {
|
|
707
708
|
passphrase: args.passphrase,
|
|
708
709
|
});
|
|
@@ -715,7 +716,7 @@ const purgeTombstonesCmd = async (args, log) => {
|
|
|
715
716
|
log('purge-tombstones: refusing without --confirm=true (R-K4)');
|
|
716
717
|
return 1;
|
|
717
718
|
}
|
|
718
|
-
const store = await openStore(args.store ??
|
|
719
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
719
720
|
const idPrefix = args['id-prefix'] ?? null;
|
|
720
721
|
const olderThan = args['older-than']
|
|
721
722
|
? new Date(args['older-than']).toISOString()
|
|
@@ -741,7 +742,7 @@ const purgeTombstonesCmd = async (args, log) => {
|
|
|
741
742
|
};
|
|
742
743
|
|
|
743
744
|
const vaultFile = (args) =>
|
|
744
|
-
args.file ?? path.join(args.store ??
|
|
745
|
+
args.file ?? path.join(args.store ?? defaultStoreDir(), 'vault.json');
|
|
745
746
|
|
|
746
747
|
const vaultInitCmd = async (args, log) => {
|
|
747
748
|
const file = vaultFile(args);
|
|
@@ -860,7 +861,26 @@ export const runCli = async (argv, { log = console.log } = {}) => {
|
|
|
860
861
|
return 1;
|
|
861
862
|
}
|
|
862
863
|
try {
|
|
863
|
-
|
|
864
|
+
const args = parseArgs(rest);
|
|
865
|
+
if (
|
|
866
|
+
args.store === undefined &&
|
|
867
|
+
![
|
|
868
|
+
'help',
|
|
869
|
+
'--help',
|
|
870
|
+
'-h',
|
|
871
|
+
'version',
|
|
872
|
+
'--version',
|
|
873
|
+
'-v',
|
|
874
|
+
undefined,
|
|
875
|
+
].includes(cmd)
|
|
876
|
+
) {
|
|
877
|
+
args.store =
|
|
878
|
+
['serve', 'trust'].includes(cmd) &&
|
|
879
|
+
!process.env.META_SOVEREIGN_STORE_DIR
|
|
880
|
+
? await resolveUserStore({ log })
|
|
881
|
+
: defaultStoreDir();
|
|
882
|
+
}
|
|
883
|
+
return await handler(args, log);
|
|
864
884
|
} catch (error) {
|
|
865
885
|
if (error?.code === 'STORE_LOCKED' || SCHEMA_ERROR_CODES.has(error?.code)) {
|
|
866
886
|
log(error.message); // another process owns the store (R-Z2); the schema (R-Z17)
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* `serve`; this command is for doing it on purpose, or looking first.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import { defaultStoreDir } from '../storage/app-directory.js';
|
|
13
14
|
import { openDualStore } from '../storage/dual-store.js';
|
|
14
15
|
import {
|
|
15
16
|
MIGRATIONS,
|
|
@@ -100,7 +101,7 @@ const migrateCmd = async (dir, log) => {
|
|
|
100
101
|
|
|
101
102
|
export const createMigrateCommands = () => ({
|
|
102
103
|
migrate: async (args, log) => {
|
|
103
|
-
const dir = args.store ??
|
|
104
|
+
const dir = args.store ?? defaultStoreDir();
|
|
104
105
|
if (args.status) {
|
|
105
106
|
return statusCmd(dir, log);
|
|
106
107
|
}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* The runner is injected so the tests never touch a real keychain.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { defaultStoreDir } from '../storage/app-directory.js';
|
|
14
15
|
import { spawnSync } from 'node:child_process';
|
|
15
16
|
import os from 'node:os';
|
|
16
17
|
import path from 'node:path';
|
|
@@ -62,7 +63,7 @@ export const setupLocalCertificate = async (args, log, options = {}) => {
|
|
|
62
63
|
if (!enabled(args.tls) || process.env.MS_TLS === 'off' || !supported) {
|
|
63
64
|
return null;
|
|
64
65
|
}
|
|
65
|
-
const storeDir = args.store ??
|
|
66
|
+
const storeDir = args.store ?? defaultStoreDir();
|
|
66
67
|
const protector =
|
|
67
68
|
enabled(args['hardware-key']) && platform === 'darwin'
|
|
68
69
|
? await deviceProtector(storeDir)
|
|
@@ -128,7 +129,7 @@ export const createTrustCommands = ({
|
|
|
128
129
|
} = {}) => ({
|
|
129
130
|
trust: async (args, log) => {
|
|
130
131
|
const remove = String(args.remove ?? 'false') !== 'false';
|
|
131
|
-
const { certPath } = await loadLocalTls(args.store ??
|
|
132
|
+
const { certPath } = await loadLocalTls(args.store ?? defaultStoreDir());
|
|
132
133
|
if (platform !== 'darwin') {
|
|
133
134
|
log(manualSteps(certPath));
|
|
134
135
|
return 0;
|
package/js/src/cv/browser.js
CHANGED
|
@@ -15,13 +15,53 @@
|
|
|
15
15
|
|
|
16
16
|
import os from 'node:os';
|
|
17
17
|
import path from 'node:path';
|
|
18
|
+
import { promises as fs } from 'node:fs';
|
|
19
|
+
import { defaultStoreDir } from '../storage/app-directory.js';
|
|
18
20
|
|
|
19
21
|
/** Packages a live run needs, in the order we report them. */
|
|
20
22
|
export const BROWSER_PACKAGES = ['browser-commander', 'playwright'];
|
|
21
23
|
|
|
22
24
|
/** Default persistent profile directory: sessions must survive runs. */
|
|
23
25
|
export const defaultProfileDir = (platform = 'shared') =>
|
|
24
|
-
path.join(
|
|
26
|
+
path.join(defaultStoreDir(), 'browser-profiles', platform);
|
|
27
|
+
|
|
28
|
+
const persistentProfile = async (platform) => {
|
|
29
|
+
const target = defaultProfileDir(platform);
|
|
30
|
+
const legacy = path.join(
|
|
31
|
+
os.homedir(),
|
|
32
|
+
'.meta-sovereign',
|
|
33
|
+
'browser-profiles',
|
|
34
|
+
platform
|
|
35
|
+
);
|
|
36
|
+
const exists = (file) =>
|
|
37
|
+
fs.lstat(file).then(
|
|
38
|
+
() => true,
|
|
39
|
+
() => false
|
|
40
|
+
);
|
|
41
|
+
if (!(await exists(target)) && (await exists(legacy))) {
|
|
42
|
+
if (await exists(path.join(legacy, 'SingletonLock'))) {
|
|
43
|
+
throw Object.assign(
|
|
44
|
+
new Error(
|
|
45
|
+
'Close the existing CV browser before migrating its profile.'
|
|
46
|
+
),
|
|
47
|
+
{ code: 'browser-profile-in-use' }
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
await fs.mkdir(path.dirname(target), { recursive: true, mode: 0o700 });
|
|
51
|
+
await fs.rename(legacy, target);
|
|
52
|
+
try {
|
|
53
|
+
await fs.symlink(
|
|
54
|
+
target,
|
|
55
|
+
legacy,
|
|
56
|
+
process.platform === 'win32' ? 'junction' : 'dir'
|
|
57
|
+
);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
await fs.rename(target, legacy);
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return target;
|
|
64
|
+
};
|
|
25
65
|
|
|
26
66
|
const defaultImporters = {
|
|
27
67
|
'browser-commander': () => import('browser-commander'),
|
|
@@ -137,7 +177,7 @@ export const openBrowserSession = async ({
|
|
|
137
177
|
return launchWithCommander({
|
|
138
178
|
bc,
|
|
139
179
|
headless,
|
|
140
|
-
profileDir: profileDir ??
|
|
180
|
+
profileDir: profileDir ?? (await persistentProfile(platform)),
|
|
141
181
|
slowMo,
|
|
142
182
|
locale,
|
|
143
183
|
verbose,
|
package/js/src/server/cors.js
CHANGED
|
@@ -140,6 +140,17 @@ export const classifyOrigin = (req, allowed) => {
|
|
|
140
140
|
return allowed.includes(origin) ? 'allowed' : 'denied';
|
|
141
141
|
};
|
|
142
142
|
|
|
143
|
+
/** Safe metadata for refused requests, without URLs, cookies or tokens. */
|
|
144
|
+
export const corsContext = (req) => ({
|
|
145
|
+
origin: toOrigin(req.headers?.origin) ?? null,
|
|
146
|
+
transport: req.socket?.encrypted ? 'https' : 'http',
|
|
147
|
+
fetchSite: ['same-origin', 'same-site', 'cross-site', 'none'].includes(
|
|
148
|
+
req.headers?.['sec-fetch-site']
|
|
149
|
+
)
|
|
150
|
+
? req.headers['sec-fetch-site']
|
|
151
|
+
: null,
|
|
152
|
+
});
|
|
153
|
+
|
|
143
154
|
const writeEmpty = (res, status) => {
|
|
144
155
|
res.writeHead(status, { 'content-length': '0' });
|
|
145
156
|
res.end();
|
package/js/src/server/index.js
CHANGED
|
@@ -119,6 +119,7 @@ import { createLearnedRoute } from './routes-learned.js';
|
|
|
119
119
|
import { createGitHubCheck } from '../sources/github-check.js';
|
|
120
120
|
import { handleMetrics } from './metrics.js';
|
|
121
121
|
import { jsonLog } from './log.js';
|
|
122
|
+
import { defaultStoreDir } from '../storage/app-directory.js';
|
|
122
123
|
import { listenLocal } from './local-tls.js';
|
|
123
124
|
import { handlePullRoutes, pullContext } from './routes-pull.js';
|
|
124
125
|
import { createPeer, attachSyncWebSocket } from '../sync/index.js';
|
|
@@ -126,6 +127,7 @@ import { attachSignaling } from '../sync/webrtc-signaling.js';
|
|
|
126
127
|
import { registerDefaultHandlers } from './handlers-bootstrap.js';
|
|
127
128
|
import {
|
|
128
129
|
classifyOrigin,
|
|
130
|
+
corsContext,
|
|
129
131
|
handleCors,
|
|
130
132
|
hostAllowed,
|
|
131
133
|
parseAllowedOrigins,
|
|
@@ -710,6 +712,7 @@ const createHttpServer = (store, ctx, log) =>
|
|
|
710
712
|
method: req.method,
|
|
711
713
|
path: (req.url ?? '').split('?')[0],
|
|
712
714
|
status: res.statusCode,
|
|
715
|
+
...(res.statusCode === 403 ? corsContext(req) : {}),
|
|
713
716
|
duration_ms: Date.now() - start,
|
|
714
717
|
});
|
|
715
718
|
});
|
|
@@ -813,7 +816,7 @@ const automationFor = (store, automations, log, telegramAccount, later) =>
|
|
|
813
816
|
|
|
814
817
|
export const startServer = async ({
|
|
815
818
|
port = 0,
|
|
816
|
-
storeDir =
|
|
819
|
+
storeDir = defaultStoreDir(),
|
|
817
820
|
archiveDir,
|
|
818
821
|
store: providedStore,
|
|
819
822
|
enableHandlers = true,
|
package/js/src/server/log.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import { createWriteStream, openSync, statSync, unlinkSync } from 'node:fs';
|
|
20
20
|
import { chmod, mkdir } from 'node:fs/promises';
|
|
21
21
|
import path from 'node:path';
|
|
22
|
+
import { defaultStoreDir } from '../storage/app-directory.js';
|
|
22
23
|
import { redact } from './redact.js';
|
|
23
24
|
import { renameDurableSync } from '../storage/durable.js';
|
|
24
25
|
|
|
@@ -138,7 +139,7 @@ export const createServerLog = async ({
|
|
|
138
139
|
keep = KEEP_FILES,
|
|
139
140
|
stdout = (line) => process.stdout.write(line),
|
|
140
141
|
} = {}) => {
|
|
141
|
-
const dir = path.join(storeDir ??
|
|
142
|
+
const dir = path.join(storeDir ?? defaultStoreDir(), 'logs');
|
|
142
143
|
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
143
144
|
await chmod(dir, 0o700).catch(() => {});
|
|
144
145
|
const file = path.join(dir, 'meta-sovereign.log');
|
|
@@ -194,6 +194,7 @@
|
|
|
194
194
|
*/
|
|
195
195
|
|
|
196
196
|
import { classifyOrigin } from './cors.js';
|
|
197
|
+
import { telegramRouteError } from './telegram-route-error.js';
|
|
197
198
|
import { bodyFailure, json, readBody } from './util.js';
|
|
198
199
|
import { isTrustedMutation } from './routes-auto-import.js';
|
|
199
200
|
import { DIALOG_ACTIONS } from '../sources/telegram-dialogs.js';
|
|
@@ -1482,18 +1483,6 @@ export const handleTelegramRoutes = async (req, res, p, ctx) => {
|
|
|
1482
1483
|
if (bodyFailure(error)) {
|
|
1483
1484
|
throw error; // a bad request body: 400/413, not a 502 (R-Z1)
|
|
1484
1485
|
}
|
|
1485
|
-
ctx.log
|
|
1486
|
-
level: 'error',
|
|
1487
|
-
event: 'telegram_route_failed',
|
|
1488
|
-
path: p,
|
|
1489
|
-
error: error.message,
|
|
1490
|
-
});
|
|
1491
|
-
return (
|
|
1492
|
-
json(res, 502, {
|
|
1493
|
-
error: error.message,
|
|
1494
|
-
code: error.code,
|
|
1495
|
-
...(error.retryAfter ? { retryAfter: error.retryAfter } : {}),
|
|
1496
|
-
}) ?? true
|
|
1497
|
-
);
|
|
1486
|
+
return telegramRouteError(res, p, error, ctx.log);
|
|
1498
1487
|
}
|
|
1499
1488
|
};
|