meta-sovereign 0.53.1 → 0.53.2
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.md +1 -1
- package/js/src/crm/reengage.js +18 -5
- package/js/src/server/local-tls.js +14 -3
- package/js/src/server/routes-events.js +1 -1
- package/js/src/sources/telegram-chat-info.js +6 -1
- package/js/src/sources/telegram-chat-url.js +34 -0
- package/js/src/web/app.css +31 -25
- package/js/src/web/app.min.js +90 -90
- package/js/src/web/chat-automation.js +8 -1
- package/js/src/web/chat-follow.js +22 -6
- package/js/src/web/chat-sidebar.js +6 -4
- package/js/src/web/chat-view.js +3 -11
- package/js/src/web/locales/en-chat.js +4 -6
- package/js/src/web/locales/en.js +10 -0
- package/js/src/web/locales/hi-chat.js +4 -5
- package/js/src/web/locales/hi.js +10 -0
- package/js/src/web/locales/ru-chat.js +4 -5
- package/js/src/web/locales/ru.js +10 -0
- package/js/src/web/locales/zh-chat.js +4 -4
- package/js/src/web/locales/zh.js +10 -0
- package/js/src/web/navigation-counts.js +6 -4
- package/js/src/web/operator-model.js +6 -2
- package/js/src/web/operator-prefs.js +58 -0
- package/js/src/web/operator-unread.js +7 -5
- package/js/src/web/operator-view.js +3 -1
- package/js/src/web/person-panel.js +20 -26
- package/js/src/web/profile-stories.js +5 -2
- package/js/src/web/settings-view.js +38 -0
- package/js/src/web/stories-pref.js +5 -0
- package/js/src/web/stories.js +1 -1
- package/package.json +1 -1
- package/js/src/web/chat-history-status.js +0 -76
package/README.md
CHANGED
|
@@ -61,7 +61,7 @@ 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
|
|
64
|
+
Open conversations load first. History loads automatically on scroll, with a compact indicator inside the message list while requesting or retrying; no history status panel or manual loading button is shown. Earlier history loads automatically on scroll in Chat and Operator. Message selection and menus work across the full row; unread cards use refreshed Telegram counters and retain cached data during connection failures. 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. Buttons and action links have nonzero spacing and aligned control padding. Stories is off by default; Settings → Stories enables its display and requests. 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
65
|
|
|
66
66
|
## Connect the SPA to your server
|
|
67
67
|
|
package/js/src/crm/reengage.js
CHANGED
|
@@ -22,6 +22,10 @@ import {
|
|
|
22
22
|
definedOnly,
|
|
23
23
|
defaultAddressRules,
|
|
24
24
|
} from './address-form.js';
|
|
25
|
+
import {
|
|
26
|
+
telegramChatUrl,
|
|
27
|
+
isTelegramWebUrl,
|
|
28
|
+
} from '../sources/telegram-chat-url.js';
|
|
25
29
|
import { addressFormOf } from './person-learn.js';
|
|
26
30
|
import {
|
|
27
31
|
batchStopReason,
|
|
@@ -300,12 +304,18 @@ export const templateFor = (templates, form) =>
|
|
|
300
304
|
* where the platform documents one; `null` elsewhere (copy the text).
|
|
301
305
|
*
|
|
302
306
|
* - e-mail: RFC 6068 `mailto:<addr>?body=` (line breaks as %0D%0A).
|
|
303
|
-
* - Telegram:
|
|
304
|
-
*
|
|
305
|
-
* pre-fill parameter for a user chat.
|
|
307
|
+
* - Telegram: documented username/phone links can prefill text; numeric-only
|
|
308
|
+
* peers open the specific chat in Telegram Web (sign-in may be required).
|
|
306
309
|
* - VK: `https://vk.com/im?sel=<peer>` opens the dialog with a user.
|
|
307
310
|
*/
|
|
308
|
-
export const prefillLinkFor = ({
|
|
311
|
+
export const prefillLinkFor = ({
|
|
312
|
+
source,
|
|
313
|
+
contact,
|
|
314
|
+
chat,
|
|
315
|
+
text,
|
|
316
|
+
username,
|
|
317
|
+
phone,
|
|
318
|
+
}) => {
|
|
309
319
|
const id = String(contact ?? '');
|
|
310
320
|
const peer = String(chat ?? id);
|
|
311
321
|
if (source === 'email' && /^[^\s@]+@[^\s@]+$/.test(id)) {
|
|
@@ -319,7 +329,10 @@ export const prefillLinkFor = ({ source, contact, chat, text }) => {
|
|
|
319
329
|
}
|
|
320
330
|
const tg = source === 'telegram' ? id.match(/^(?:user)?(\d+)$/) : null;
|
|
321
331
|
if (tg) {
|
|
322
|
-
|
|
332
|
+
const url = telegramChatUrl({ source, chat: tg[1], username, phone, text });
|
|
333
|
+
return url
|
|
334
|
+
? { url, prefillsText: Boolean(text) && !isTelegramWebUrl(url) }
|
|
335
|
+
: null;
|
|
323
336
|
}
|
|
324
337
|
return null;
|
|
325
338
|
};
|
|
@@ -20,7 +20,7 @@ import crypto from 'node:crypto';
|
|
|
20
20
|
import { promises as fs } from 'node:fs';
|
|
21
21
|
import net from 'node:net';
|
|
22
22
|
import path from 'node:path';
|
|
23
|
-
import
|
|
23
|
+
import http2 from 'node:http2';
|
|
24
24
|
import { DIR_MODE, tighten, writeFileDurable } from '../storage/durable.js';
|
|
25
25
|
import {
|
|
26
26
|
certificateFingerprint,
|
|
@@ -265,8 +265,19 @@ export const listenHttpAndHttps = (
|
|
|
265
265
|
host = '127.0.0.1'
|
|
266
266
|
) =>
|
|
267
267
|
new Promise((resolve, reject) => {
|
|
268
|
-
|
|
269
|
-
|
|
268
|
+
// Multiplex API calls, media and event streams over HTTPS. HTTP/1's six
|
|
269
|
+
// browser connections can otherwise all wait behind long media downloads.
|
|
270
|
+
const secure = http2.createSecureServer(
|
|
271
|
+
{ key, cert, allowHTTP1: true },
|
|
272
|
+
(req, res) => {
|
|
273
|
+
// Keep the existing strict Host and Origin checks for HTTP/2 too.
|
|
274
|
+
req.headers.host ??= req.headers[':authority'];
|
|
275
|
+
httpServer.emit('request', req, res);
|
|
276
|
+
}
|
|
277
|
+
);
|
|
278
|
+
secure.requestTimeout = httpServer.requestTimeout;
|
|
279
|
+
secure.on('upgrade', (req, socket, head) =>
|
|
280
|
+
httpServer.emit('upgrade', req, socket, head)
|
|
270
281
|
);
|
|
271
282
|
// A browser that does not trust the certificate yet hangs up.
|
|
272
283
|
secure.on('tlsClientError', () => {});
|
|
@@ -69,7 +69,7 @@ export const handleEventRoutes = (store, req, res, p, ctx) => {
|
|
|
69
69
|
res.writeHead(200, {
|
|
70
70
|
'content-type': 'text/event-stream; charset=utf-8',
|
|
71
71
|
'cache-control': 'no-cache, no-transform',
|
|
72
|
-
connection: 'keep-alive',
|
|
72
|
+
...(req.httpVersionMajor === 2 ? {} : { connection: 'keep-alive' }),
|
|
73
73
|
'x-accel-buffering': 'no',
|
|
74
74
|
});
|
|
75
75
|
// Reconnect quickly after the server restarts.
|
|
@@ -377,6 +377,11 @@ const extrasOf = (user, full) => ({
|
|
|
377
377
|
const botRevenueOf = (user, full) =>
|
|
378
378
|
user.bot && full.canViewRevenue ? { canViewStarsRevenue: true } : {};
|
|
379
379
|
|
|
380
|
+
const contactReach = (user) => ({
|
|
381
|
+
username: user.username ?? null,
|
|
382
|
+
phone: user.phone ?? null,
|
|
383
|
+
});
|
|
384
|
+
|
|
380
385
|
const fetchUser = async (client, Api, helpers, inputPeer, peers, chat) => {
|
|
381
386
|
const storedPeer = inputPeer ?? (await peers.input(chat));
|
|
382
387
|
const id = inputUserOf(Api, helpers, storedPeer, chat);
|
|
@@ -389,7 +394,7 @@ const fetchUser = async (client, Api, helpers, inputPeer, peers, chat) => {
|
|
|
389
394
|
title: nameOf(user),
|
|
390
395
|
members: 1,
|
|
391
396
|
online,
|
|
392
|
-
|
|
397
|
+
...contactReach(user),
|
|
393
398
|
about: full.about ?? null,
|
|
394
399
|
photo: hasPhoto(user.photo),
|
|
395
400
|
pinned: full.pinnedMsgId ?? null,
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Normal Telegram clients ignore tg://user?id (a Bot API abstraction).
|
|
2
|
+
// Use documented username/phone/message links; numeric-only peers use Web A's
|
|
3
|
+
// signed-in chat route instead of launching a native app without a destination.
|
|
4
|
+
// References: core.telegram.org/api/links; Ajaxy/telegram-tt src/util/routing.ts.
|
|
5
|
+
export const telegramChatUrl = ({
|
|
6
|
+
source,
|
|
7
|
+
chat,
|
|
8
|
+
username,
|
|
9
|
+
phone,
|
|
10
|
+
lastId,
|
|
11
|
+
text,
|
|
12
|
+
} = {}) => {
|
|
13
|
+
if (source !== 'telegram' || !/^-?[1-9]\d*$/.test(String(chat))) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const handle = String(username ?? '').replace(/^@/, '');
|
|
17
|
+
const number = String(phone ?? '').replace(/[\s()+-]/g, '');
|
|
18
|
+
const native = /^[a-zA-Z][a-zA-Z0-9_]{3,31}$/.test(handle)
|
|
19
|
+
? `https://t.me/${handle}`
|
|
20
|
+
: /^[1-9]\d{6,14}$/.test(number)
|
|
21
|
+
? `https://t.me/+${number}`
|
|
22
|
+
: null;
|
|
23
|
+
if (native) {
|
|
24
|
+
return text ? `${native}?text=${encodeURIComponent(text)}` : native;
|
|
25
|
+
}
|
|
26
|
+
const id = String(chat);
|
|
27
|
+
if (/^-100\d+$/.test(id) && /^[1-9]\d*$/.test(String(lastId))) {
|
|
28
|
+
return `https://t.me/c/${id.slice(4)}/${lastId}`;
|
|
29
|
+
}
|
|
30
|
+
return `https://web.telegram.org/a/#${id}`;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const isTelegramWebUrl = (url) =>
|
|
34
|
+
String(url ?? '').startsWith('https://web.telegram.org/a/#');
|
package/js/src/web/app.css
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
/* One height for every button, input and select; one header height
|
|
16
16
|
the sticky drawer, rail and full-height chat measure against. */
|
|
17
17
|
--control-h: 2.25rem;
|
|
18
|
+
--control-gap: 0.5rem;
|
|
19
|
+
--control-pad: 0.4rem 0.9rem;
|
|
18
20
|
--radius: 8px;
|
|
19
21
|
--topbar-h: 3.5rem;
|
|
20
22
|
--bottom-nav-h: 4rem;
|
|
@@ -174,7 +176,7 @@ body {
|
|
|
174
176
|
}
|
|
175
177
|
.header-btn {
|
|
176
178
|
width: var(--control-h);
|
|
177
|
-
padding: 0;
|
|
179
|
+
padding: 0.25rem;
|
|
178
180
|
}
|
|
179
181
|
.btn-label {
|
|
180
182
|
display: none;
|
|
@@ -193,7 +195,16 @@ body {
|
|
|
193
195
|
flex-wrap: wrap;
|
|
194
196
|
gap: 0.25rem;
|
|
195
197
|
}
|
|
196
|
-
.topbar nav
|
|
198
|
+
.topbar nav /* Action containers inherit a nonzero gap even when a new toolbar omits it.
|
|
199
|
+
Named layouts can choose a denser spacing; no margins fight their alignment. */
|
|
200
|
+
:where(*:has(> :is(button, a.button, [role='button']) ~ :is(button, a.button, [role='button']))) {
|
|
201
|
+
gap: var(--control-gap);
|
|
202
|
+
}
|
|
203
|
+
/* Inline actions in prose/table cells do not participate in flex/grid gaps. */
|
|
204
|
+
:where(p, td, dd, .meta) > :is(button, a.button) + :is(button, a.button) {
|
|
205
|
+
margin-inline-start: var(--control-gap);
|
|
206
|
+
}
|
|
207
|
+
button {
|
|
197
208
|
background: transparent;
|
|
198
209
|
color: var(--text-muted);
|
|
199
210
|
border: 1px solid transparent;
|
|
@@ -426,6 +437,9 @@ main[data-view='chat'] > .web-mode-banner {
|
|
|
426
437
|
min-height: 1px;
|
|
427
438
|
}
|
|
428
439
|
|
|
440
|
+
.chat-list > .chat-item + .chat-item {
|
|
441
|
+
margin-block-start: 0.25rem;
|
|
442
|
+
}
|
|
429
443
|
.chat-item {
|
|
430
444
|
/* Rows out of sight are not laid out or painted (R-Y71). */
|
|
431
445
|
content-visibility: auto;
|
|
@@ -1197,8 +1211,10 @@ main[data-view='chat'] > .web-mode-banner {
|
|
|
1197
1211
|
a.button {
|
|
1198
1212
|
display: inline-flex;
|
|
1199
1213
|
align-items: center;
|
|
1214
|
+
justify-content: center;
|
|
1215
|
+
gap: var(--control-gap);
|
|
1200
1216
|
min-height: var(--control-h);
|
|
1201
|
-
padding:
|
|
1217
|
+
padding: var(--control-pad);
|
|
1202
1218
|
border: 1px solid var(--border);
|
|
1203
1219
|
border-radius: var(--radius);
|
|
1204
1220
|
background: var(--bg-active);
|
|
@@ -1409,7 +1425,7 @@ a.button {
|
|
|
1409
1425
|
}
|
|
1410
1426
|
.icon-btn {
|
|
1411
1427
|
width: var(--control-h);
|
|
1412
|
-
padding: 0;
|
|
1428
|
+
padding: 0.25rem;
|
|
1413
1429
|
border-color: transparent;
|
|
1414
1430
|
background: transparent;
|
|
1415
1431
|
}
|
|
@@ -1522,7 +1538,7 @@ button {
|
|
|
1522
1538
|
color: inherit;
|
|
1523
1539
|
border: 1px solid var(--border);
|
|
1524
1540
|
border-radius: var(--radius);
|
|
1525
|
-
padding:
|
|
1541
|
+
padding: var(--control-pad);
|
|
1526
1542
|
font: inherit;
|
|
1527
1543
|
font-size: 0.9rem;
|
|
1528
1544
|
line-height: 1.2;
|
|
@@ -1551,7 +1567,7 @@ button.primary:hover:not(:disabled) {
|
|
|
1551
1567
|
}
|
|
1552
1568
|
.row {
|
|
1553
1569
|
display: flex;
|
|
1554
|
-
gap:
|
|
1570
|
+
gap: var(--control-gap);
|
|
1555
1571
|
align-items: center;
|
|
1556
1572
|
flex-wrap: wrap;
|
|
1557
1573
|
}
|
|
@@ -2833,6 +2849,10 @@ button.primary .recommended-tag {
|
|
|
2833
2849
|
flex-direction: column;
|
|
2834
2850
|
gap: 0.3rem;
|
|
2835
2851
|
}
|
|
2852
|
+
.automation-list li {
|
|
2853
|
+
display: grid;
|
|
2854
|
+
gap: var(--control-gap);
|
|
2855
|
+
}
|
|
2836
2856
|
.automation-list button {
|
|
2837
2857
|
width: 100%;
|
|
2838
2858
|
text-align: left;
|
|
@@ -3608,6 +3628,9 @@ button.primary .recommended-tag {
|
|
|
3608
3628
|
rounded, frosted, an icon before each item; it grows from the corner
|
|
3609
3629
|
it opened at. */
|
|
3610
3630
|
.msg-context-menu {
|
|
3631
|
+
display: flex;
|
|
3632
|
+
flex-direction: column;
|
|
3633
|
+
gap: 0.25rem;
|
|
3611
3634
|
position: fixed;
|
|
3612
3635
|
/* Above a dialog: the files' Send button has a menu too (R-Y102). */
|
|
3613
3636
|
z-index: 2100;
|
|
@@ -6791,7 +6814,7 @@ button[aria-busy='true']::before {
|
|
|
6791
6814
|
scrollbar-width: thin;
|
|
6792
6815
|
}
|
|
6793
6816
|
.stories-strip.is-compact .stories-list {
|
|
6794
|
-
gap: 0;
|
|
6817
|
+
gap: 0.25rem;
|
|
6795
6818
|
}
|
|
6796
6819
|
.story-peer {
|
|
6797
6820
|
display: flex;
|
|
@@ -9184,7 +9207,7 @@ span.bubble-effect {
|
|
|
9184
9207
|
left: 50%;
|
|
9185
9208
|
top: 50%;
|
|
9186
9209
|
transform: translate(-50%, -50%);
|
|
9187
|
-
padding:
|
|
9210
|
+
padding: var(--control-pad);
|
|
9188
9211
|
border: 0;
|
|
9189
9212
|
border-radius: 999px;
|
|
9190
9213
|
background: rgba(0, 0, 0, 0.6);
|
|
@@ -12908,23 +12931,6 @@ audio.remote-player {
|
|
|
12908
12931
|
flex-shrink: 0;
|
|
12909
12932
|
scrollbar-width: thin;
|
|
12910
12933
|
}
|
|
12911
|
-
.chat-history-status {
|
|
12912
|
-
display: flex;
|
|
12913
|
-
flex: 0 0 auto;
|
|
12914
|
-
align-items: center;
|
|
12915
|
-
gap: 0.5rem;
|
|
12916
|
-
padding: 0.5rem 0.75rem;
|
|
12917
|
-
border-bottom: 1px solid var(--border);
|
|
12918
|
-
color: var(--text);
|
|
12919
|
-
background: var(--chat-sidebar);
|
|
12920
|
-
font-size: 0.85rem;
|
|
12921
|
-
}
|
|
12922
|
-
.chat-history-status span {
|
|
12923
|
-
flex: 1;
|
|
12924
|
-
}
|
|
12925
|
-
.chat-history-status button {
|
|
12926
|
-
flex-shrink: 0;
|
|
12927
|
-
}
|
|
12928
12934
|
.calls-content {
|
|
12929
12935
|
min-height: 12rem;
|
|
12930
12936
|
}
|