@easyoref/shared 1.21.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/dist/config.d.ts +128 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +158 -0
- package/dist/config.js.map +1 -0
- package/dist/helpers.d.ts +6 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +15 -0
- package/dist/helpers.js.map +1 -0
- package/dist/i18n.d.ts +51 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +248 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/redis.d.ts +6 -0
- package/dist/redis.d.ts.map +1 -0
- package/dist/redis.js +21 -0
- package/dist/redis.js.map +1 -0
- package/dist/schemas.d.ts +1496 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +556 -0
- package/dist/schemas.js.map +1 -0
- package/dist/store.d.ts +55 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +162 -0
- package/dist/store.js.map +1 -0
- package/package.json +22 -0
- package/src/config.ts +248 -0
- package/src/helpers.ts +17 -0
- package/src/i18n.ts +306 -0
- package/src/index.ts +6 -0
- package/src/redis.ts +23 -0
- package/src/schemas.ts +712 -0
- package/src/store.ts +232 -0
- package/tsconfig.json +9 -0
package/src/store.ts
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-based alert state store — Redis operations.
|
|
3
|
+
*
|
|
4
|
+
* A "session" spans the lifecycle of one attack event:
|
|
5
|
+
* early_warning → (optional red_alert) → resolved → +10 min tail
|
|
6
|
+
*
|
|
7
|
+
* Keys:
|
|
8
|
+
* session:active — ActiveSession JSON TTL 45min
|
|
9
|
+
* session:posts — LPUSH list of ChannelPost TTL 45min
|
|
10
|
+
* session:ext_cache — HASH {post_hash → extraction JSON} TTL 45min
|
|
11
|
+
* alert:{alertId}:meta — AlertMeta JSON TTL 20min
|
|
12
|
+
*
|
|
13
|
+
* Only the LATEST alert's Telegram message gets enrichment edits.
|
|
14
|
+
* Posts accumulate across the entire session (shared context).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { getRedis } from "./redis.js";
|
|
18
|
+
import type {
|
|
19
|
+
ActiveSession,
|
|
20
|
+
AlertMeta,
|
|
21
|
+
AlertType,
|
|
22
|
+
ChannelPost,
|
|
23
|
+
EnrichmentData,
|
|
24
|
+
TelegramMessage,
|
|
25
|
+
} from "./schemas.js";
|
|
26
|
+
import { createEmptyEnrichmentData } from "./schemas.js";
|
|
27
|
+
|
|
28
|
+
// Re-export types for backward compatibility
|
|
29
|
+
export type { ActiveSession, AlertMeta, ChannelPost, TelegramMessage };
|
|
30
|
+
|
|
31
|
+
// Schema version for migration handling
|
|
32
|
+
export const SCHEMA_VERSION = "2.0.0";
|
|
33
|
+
const SCHEMA_VERSION_KEY = "schema:version";
|
|
34
|
+
|
|
35
|
+
let schemaVersionChecked = false;
|
|
36
|
+
|
|
37
|
+
export async function ensureSchemaVersion(): Promise<void> {
|
|
38
|
+
if (schemaVersionChecked) return;
|
|
39
|
+
schemaVersionChecked = true;
|
|
40
|
+
|
|
41
|
+
const redis = getRedis();
|
|
42
|
+
const stored = await redis.get(SCHEMA_VERSION_KEY);
|
|
43
|
+
|
|
44
|
+
if (stored !== SCHEMA_VERSION) {
|
|
45
|
+
await redis.flushall();
|
|
46
|
+
await redis.set(SCHEMA_VERSION_KEY, SCHEMA_VERSION);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const META_TTL_S = 20 * 60; // 20 minutes
|
|
51
|
+
const SESSION_TTL_S = 45 * 60; // 45 min worst case
|
|
52
|
+
|
|
53
|
+
// ── Session phase timeouts ─────────────────────────────
|
|
54
|
+
|
|
55
|
+
/** Max duration (ms) for each phase before auto-expire */
|
|
56
|
+
export const PHASE_TIMEOUT_MS: Record<AlertType, number> = {
|
|
57
|
+
early_warning: 30 * 60 * 1000, // 30 min
|
|
58
|
+
red_alert: 15 * 60 * 1000, // 15 min
|
|
59
|
+
resolved: 10 * 60 * 1000, // 10 min tail
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** Enrichment interval (ms) per phase */
|
|
63
|
+
export const PHASE_ENRICH_DELAY_MS: Record<AlertType, number> = {
|
|
64
|
+
early_warning: 60_000, // 60s — channels need time to post; saves tokens
|
|
65
|
+
red_alert: 45_000, // 45s
|
|
66
|
+
resolved: 150_000, // 150s (2.5 min) — per user requirement: 10 min window, update every 2.5 min
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/** Initial enrichment delay — first job after alert (channels need time to post) */
|
|
70
|
+
export const PHASE_INITIAL_DELAY_MS: Record<AlertType, number> = {
|
|
71
|
+
early_warning: 120_000, // 2 min — wait for launch reports
|
|
72
|
+
red_alert: 15_000, // 15s
|
|
73
|
+
resolved: 90_000, // 90s — wait for first wave of post-incident reports
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// ──Alert Meta (per-alert) ─────────────────────────────
|
|
77
|
+
|
|
78
|
+
export async function saveAlertMeta(meta: AlertMeta): Promise<void> {
|
|
79
|
+
const redis = getRedis();
|
|
80
|
+
await redis.setex(
|
|
81
|
+
`alert:${meta.alertId}:meta`,
|
|
82
|
+
META_TTL_S,
|
|
83
|
+
JSON.stringify(meta),
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function getAlertMeta(
|
|
88
|
+
alertId: string,
|
|
89
|
+
): Promise<AlertMeta | undefined> {
|
|
90
|
+
const redis = getRedis();
|
|
91
|
+
const raw = await redis.get(`alert:${alertId}:meta`);
|
|
92
|
+
return raw ? (JSON.parse(raw) as AlertMeta) : undefined;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── Session posts (shared across entire session) ───────
|
|
96
|
+
|
|
97
|
+
export async function pushSessionPost(post: ChannelPost): Promise<void> {
|
|
98
|
+
const redis = getRedis();
|
|
99
|
+
await redis.lpush("session:posts", JSON.stringify(post));
|
|
100
|
+
await redis.expire("session:posts", SESSION_TTL_S);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function getSessionPosts(): Promise<ChannelPost[]> {
|
|
104
|
+
const redis = getRedis();
|
|
105
|
+
const items = await redis.lrange("session:posts", 0, -1);
|
|
106
|
+
return items.map((i: string) => JSON.parse(i) as ChannelPost);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ── Active session ─────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
export async function setActiveSession(session: ActiveSession): Promise<void> {
|
|
112
|
+
const redis = getRedis();
|
|
113
|
+
await redis.setex("session:active", SESSION_TTL_S, JSON.stringify(session));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export async function getActiveSession(): Promise<ActiveSession | undefined> {
|
|
117
|
+
const redis = getRedis();
|
|
118
|
+
const raw = await redis.get("session:active");
|
|
119
|
+
return raw ? (JSON.parse(raw) as ActiveSession) : undefined;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export async function clearSession(): Promise<void> {
|
|
123
|
+
const redis = getRedis();
|
|
124
|
+
await redis.del(
|
|
125
|
+
"session:active",
|
|
126
|
+
"session:posts",
|
|
127
|
+
"session:enrichment",
|
|
128
|
+
EXT_CACHE_KEY,
|
|
129
|
+
LAST_UPDATE_KEY,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function isPhaseExpired(session: ActiveSession): boolean {
|
|
134
|
+
const elapsed = Date.now() - session.phaseStartTs;
|
|
135
|
+
return elapsed >= PHASE_TIMEOUT_MS[session.phase];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ── Compat shims (used by gramjs-monitor, graph) ───────
|
|
139
|
+
|
|
140
|
+
export async function getActiveAlert(): Promise<
|
|
141
|
+
| {
|
|
142
|
+
alertId: string;
|
|
143
|
+
alertTs: number;
|
|
144
|
+
alertType: AlertType;
|
|
145
|
+
}
|
|
146
|
+
| undefined
|
|
147
|
+
> {
|
|
148
|
+
const s = await getActiveSession();
|
|
149
|
+
if (!s) return undefined;
|
|
150
|
+
return {
|
|
151
|
+
alertId: s.latestAlertId,
|
|
152
|
+
alertTs: s.latestAlertTs,
|
|
153
|
+
alertType: s.phase,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function pushChannelPost(
|
|
158
|
+
_alertId: string,
|
|
159
|
+
post: ChannelPost,
|
|
160
|
+
): Promise<void> {
|
|
161
|
+
await pushSessionPost(post);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export async function getChannelPosts(
|
|
165
|
+
_alertId: string,
|
|
166
|
+
): Promise<ChannelPost[]> {
|
|
167
|
+
return getSessionPosts();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── Enrichment data (cross-phase persistence) ──────────
|
|
171
|
+
|
|
172
|
+
export async function saveEnrichmentData(data: EnrichmentData): Promise<void> {
|
|
173
|
+
const redis = getRedis();
|
|
174
|
+
await redis.setex("session:enrichment", SESSION_TTL_S, JSON.stringify(data));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export async function getEnrichmentData(): Promise<EnrichmentData> {
|
|
178
|
+
const redis = getRedis();
|
|
179
|
+
const raw = await redis.get("session:enrichment");
|
|
180
|
+
return raw
|
|
181
|
+
? (JSON.parse(raw) as EnrichmentData)
|
|
182
|
+
: createEmptyEnrichmentData();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ── Last update timestamp (tracks when last enrichment job ran) ──
|
|
186
|
+
|
|
187
|
+
const LAST_UPDATE_KEY = "session:last_update_ts";
|
|
188
|
+
|
|
189
|
+
export async function getLastUpdateTs(): Promise<number> {
|
|
190
|
+
const redis = getRedis();
|
|
191
|
+
const raw = await redis.get(LAST_UPDATE_KEY);
|
|
192
|
+
return raw ? Number(raw) : 0;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export async function setLastUpdateTs(ts: number): Promise<void> {
|
|
196
|
+
const redis = getRedis();
|
|
197
|
+
await redis.setex(LAST_UPDATE_KEY, SESSION_TTL_S, String(ts));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ── Extraction cache (post-level dedup between jobs) ───
|
|
201
|
+
|
|
202
|
+
const EXT_CACHE_KEY = "session:ext_cache";
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Get cached extraction results for a batch of post hashes.
|
|
206
|
+
* Returns a map: postHash → serialized ValidatedExtraction JSON.
|
|
207
|
+
*/
|
|
208
|
+
export async function getCachedExtractions(
|
|
209
|
+
postHashes: string[],
|
|
210
|
+
): Promise<Map<string, string>> {
|
|
211
|
+
if (postHashes.length === 0) return new Map();
|
|
212
|
+
const redis = getRedis();
|
|
213
|
+
const results = await redis.hmget(EXT_CACHE_KEY, ...postHashes);
|
|
214
|
+
const map = new Map<string, string>();
|
|
215
|
+
postHashes.forEach((hash, i) => {
|
|
216
|
+
if (results[i]) map.set(hash, results[i]!);
|
|
217
|
+
});
|
|
218
|
+
return map;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Save new extraction results to cache.
|
|
223
|
+
* @param entries - Record of postHash → serialized ValidatedExtraction JSON
|
|
224
|
+
*/
|
|
225
|
+
export async function saveCachedExtractions(
|
|
226
|
+
entries: Record<string, string>,
|
|
227
|
+
): Promise<void> {
|
|
228
|
+
if (Object.keys(entries).length === 0) return;
|
|
229
|
+
const redis = getRedis();
|
|
230
|
+
await redis.hset(EXT_CACHE_KEY, entries);
|
|
231
|
+
await redis.expire(EXT_CACHE_KEY, SESSION_TTL_S);
|
|
232
|
+
}
|