@xerktech/claude-hud 0.1.0
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 +50 -0
- package/dist/_broker/attached.js +78 -0
- package/dist/_broker/attached.js.map +1 -0
- package/dist/_broker/audio-codec.js +82 -0
- package/dist/_broker/audio-codec.js.map +1 -0
- package/dist/_broker/audio-session.js +81 -0
- package/dist/_broker/audio-session.js.map +1 -0
- package/dist/_broker/auth.js +32 -0
- package/dist/_broker/auth.js.map +1 -0
- package/dist/_broker/bus.js +76 -0
- package/dist/_broker/bus.js.map +1 -0
- package/dist/_broker/cert.js +72 -0
- package/dist/_broker/cert.js.map +1 -0
- package/dist/_broker/claude.js +160 -0
- package/dist/_broker/claude.js.map +1 -0
- package/dist/_broker/cli.js +134 -0
- package/dist/_broker/cli.js.map +1 -0
- package/dist/_broker/cors.js +48 -0
- package/dist/_broker/cors.js.map +1 -0
- package/dist/_broker/hooks.js +196 -0
- package/dist/_broker/hooks.js.map +1 -0
- package/dist/_broker/index.js +48 -0
- package/dist/_broker/index.js.map +1 -0
- package/dist/_broker/intent-dispatcher.js +86 -0
- package/dist/_broker/intent-dispatcher.js.map +1 -0
- package/dist/_broker/intent.js +127 -0
- package/dist/_broker/intent.js.map +1 -0
- package/dist/_broker/jsonl-tail.js +185 -0
- package/dist/_broker/jsonl-tail.js.map +1 -0
- package/dist/_broker/mdns.js +41 -0
- package/dist/_broker/mdns.js.map +1 -0
- package/dist/_broker/projects.js +161 -0
- package/dist/_broker/projects.js.map +1 -0
- package/dist/_broker/qr.js +11 -0
- package/dist/_broker/qr.js.map +1 -0
- package/dist/_broker/routes.js +379 -0
- package/dist/_broker/routes.js.map +1 -0
- package/dist/_broker/server.js +325 -0
- package/dist/_broker/server.js.map +1 -0
- package/dist/_broker/sessions-store.js +50 -0
- package/dist/_broker/sessions-store.js.map +1 -0
- package/dist/_broker/sessions.js +792 -0
- package/dist/_broker/sessions.js.map +1 -0
- package/dist/_broker/store.js +79 -0
- package/dist/_broker/store.js.map +1 -0
- package/dist/_broker/stt.js +93 -0
- package/dist/_broker/stt.js.map +1 -0
- package/dist/broker-bin.js +37 -0
- package/dist/broker-bin.js.map +1 -0
- package/dist/broker-lifecycle.js +186 -0
- package/dist/broker-lifecycle.js.map +1 -0
- package/dist/index.js +189 -0
- package/dist/index.js.map +1 -0
- package/dist/paths.js +17 -0
- package/dist/paths.js.map +1 -0
- package/dist/wrapper/index.js +263 -0
- package/dist/wrapper/index.js.map +1 -0
- package/dist/wrapper/slug.js +5 -0
- package/dist/wrapper/slug.js.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import https from 'node:https';
|
|
3
|
+
import express from 'express';
|
|
4
|
+
import { WebSocketServer } from 'ws';
|
|
5
|
+
import { Store } from "./store.js";
|
|
6
|
+
import { createBearerAuth, constantTimeEqual } from "./auth.js";
|
|
7
|
+
import { corsMiddleware } from "./cors.js";
|
|
8
|
+
import { ensureCert } from "./cert.js";
|
|
9
|
+
import { EventBus } from "./bus.js";
|
|
10
|
+
import { registerRoutes } from "./routes.js";
|
|
11
|
+
import { SessionManager } from "./sessions.js";
|
|
12
|
+
import { ProjectsStore } from "./projects.js";
|
|
13
|
+
import { SessionsSnapshotStore } from "./sessions-store.js";
|
|
14
|
+
import { AudioSession } from "./audio-session.js";
|
|
15
|
+
import { IntentDispatcher } from "./intent-dispatcher.js";
|
|
16
|
+
import { HttpWhisperClient, NullWhisperClient } from "./stt.js";
|
|
17
|
+
import { HookManager } from "./hooks.js";
|
|
18
|
+
import { AttachedSessionHub } from "./attached.js";
|
|
19
|
+
export async function startServer(opts = {}) {
|
|
20
|
+
const store = opts.store ?? new Store();
|
|
21
|
+
const state = store.load();
|
|
22
|
+
const projects = opts.projects ?? new ProjectsStore(store.dir);
|
|
23
|
+
const sessionsStore = opts.sessionsStore ?? new SessionsSnapshotStore(store.dir);
|
|
24
|
+
const bus = new EventBus();
|
|
25
|
+
// Forward-declared so the two managers can call into each other without a
|
|
26
|
+
// construction-time circular reference. Hook env is also resolved lazily —
|
|
27
|
+
// the URL isn't known until `server.listen()` binds a port (port 0 in tests).
|
|
28
|
+
let hooks;
|
|
29
|
+
let hookEnv;
|
|
30
|
+
const attached = new AttachedSessionHub();
|
|
31
|
+
const sessions = new SessionManager({
|
|
32
|
+
bus,
|
|
33
|
+
spawn: opts.spawnClaude,
|
|
34
|
+
defaultCwd: opts.defaultCwd,
|
|
35
|
+
idleArchiveMs: state.settings.idleArchiveMinutes * 60_000,
|
|
36
|
+
onTerminate: (sessionId, reason) => {
|
|
37
|
+
hooks?.cancelSession(sessionId, `session_${reason}`);
|
|
38
|
+
},
|
|
39
|
+
getHookEnv: () => hookEnv,
|
|
40
|
+
attachedHub: attached,
|
|
41
|
+
});
|
|
42
|
+
hooks = new HookManager({
|
|
43
|
+
bus,
|
|
44
|
+
onPendingChange: (sessionId, kind) => {
|
|
45
|
+
sessions.setHookStatus(sessionId, kind);
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
// Load — but do NOT yet hydrate. Hydration spawns `claude --resume` for every
|
|
49
|
+
// persisted active session, and each spawn needs the `BROKER_HOOKS_*` env vars
|
|
50
|
+
// baked in. Those vars depend on the bound URL, which isn't known until
|
|
51
|
+
// `server.listen()` returns. We defer the hydrate to after listen below.
|
|
52
|
+
const previousSnapshot = sessionsStore.load();
|
|
53
|
+
const persist = () => sessionsStore.save(sessions.snapshot());
|
|
54
|
+
const app = express();
|
|
55
|
+
app.use(express.json({ limit: '1mb' }));
|
|
56
|
+
app.use(corsMiddleware({ allowedOrigins: opts.allowedOrigins }));
|
|
57
|
+
// Public endpoint, registered before auth so it bypasses the bearer check.
|
|
58
|
+
app.get('/api/health', (_req, res) => {
|
|
59
|
+
res.json({ ok: true, version: '0.1.0' });
|
|
60
|
+
});
|
|
61
|
+
app.use(createBearerAuth(state.token));
|
|
62
|
+
registerRoutes(app, { bus, sessions, projects, hooks, persist });
|
|
63
|
+
const useHttp = opts.http ?? process.env.BROKER_HTTP === '1';
|
|
64
|
+
let server;
|
|
65
|
+
let protocol;
|
|
66
|
+
if (useHttp) {
|
|
67
|
+
server = http.createServer(app);
|
|
68
|
+
protocol = 'http';
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
const cert = ensureCert(store.certDir(), state.settings.hostname);
|
|
72
|
+
server = https.createServer({ cert: cert.cert, key: cert.key }, app);
|
|
73
|
+
protocol = 'https';
|
|
74
|
+
if (cert.source !== 'reused') {
|
|
75
|
+
console.log(`[cert] generated ${cert.source} cert for ${state.settings.hostname}`);
|
|
76
|
+
console.log(`[cert] cert: ${cert.certPath}`);
|
|
77
|
+
console.log(`[cert] key : ${cert.keyPath}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const whisper = opts.whisper ??
|
|
81
|
+
(state.settings.whisper.url
|
|
82
|
+
? new HttpWhisperClient({
|
|
83
|
+
url: state.settings.whisper.url,
|
|
84
|
+
model: state.settings.whisper.model,
|
|
85
|
+
language: state.settings.whisper.language,
|
|
86
|
+
})
|
|
87
|
+
: new NullWhisperClient());
|
|
88
|
+
const dispatcher = new IntentDispatcher({
|
|
89
|
+
sessions: {
|
|
90
|
+
create: o => {
|
|
91
|
+
const s = sessions.create(o);
|
|
92
|
+
return { id: s.id };
|
|
93
|
+
},
|
|
94
|
+
focus: id => sessions.focus(id),
|
|
95
|
+
writeInput: (id, text) => sessions.writeInput(id, text),
|
|
96
|
+
getFocusedId: () => sessions.getFocusedId(),
|
|
97
|
+
findActiveByProject: id => {
|
|
98
|
+
const s = sessions.findActiveByProject(id);
|
|
99
|
+
return s ? { id: s.id } : undefined;
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
onChange: persist,
|
|
103
|
+
});
|
|
104
|
+
const wss = new WebSocketServer({ noServer: true });
|
|
105
|
+
attachWsRouter(server, wss, state.token, bus, whisper, projects, dispatcher, sessions, attached);
|
|
106
|
+
const port = opts.port ?? Number(process.env.BROKER_PORT ?? state.settings.port);
|
|
107
|
+
const host = opts.host ?? process.env.BROKER_HOST ?? '0.0.0.0';
|
|
108
|
+
await new Promise((resolve, reject) => {
|
|
109
|
+
const onError = (err) => reject(err);
|
|
110
|
+
server.once('error', onError);
|
|
111
|
+
server.listen(port, host, () => {
|
|
112
|
+
server.off('error', onError);
|
|
113
|
+
resolve();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
const addr = server.address();
|
|
117
|
+
const boundPort = typeof addr === 'object' && addr ? addr.port : port;
|
|
118
|
+
const url = `${protocol}://${state.settings.hostname}:${boundPort}`;
|
|
119
|
+
// Now that the listener is bound and the URL is known, plug the hook env
|
|
120
|
+
// into the SessionManager so every subsequent `claude` spawn has the
|
|
121
|
+
// BROKER_HOOKS_* vars the bundled helper script needs. For HTTPS we tell
|
|
122
|
+
// the helper to skip TLS verification — the broker's cert is mkcert- or
|
|
123
|
+
// selfsigned-issued and won't validate against Node's default CA bundle
|
|
124
|
+
// inside the helper script. This is scoped to the helper only.
|
|
125
|
+
hookEnv = {
|
|
126
|
+
url,
|
|
127
|
+
token: state.token,
|
|
128
|
+
insecure: protocol === 'https',
|
|
129
|
+
};
|
|
130
|
+
// Hydrate AFTER hookEnv is set so every resumed `claude` process inherits the
|
|
131
|
+
// BROKER_HOOKS_* env vars. Hydrating earlier (before listen) would respawn
|
|
132
|
+
// with no env vars, and the bundled `claude-hook.mjs` would then trip its
|
|
133
|
+
// safe-default deny path on every PreToolUse. Pinned by
|
|
134
|
+
// `broker/test/lifecycle.test.ts > hydrate runs after the URL is known`.
|
|
135
|
+
if (previousSnapshot.sessions.length > 0) {
|
|
136
|
+
sessions.hydrate(previousSnapshot);
|
|
137
|
+
}
|
|
138
|
+
if (!opts.noIdleArchiver) {
|
|
139
|
+
sessions.startIdleArchiver();
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
app,
|
|
143
|
+
server,
|
|
144
|
+
bus,
|
|
145
|
+
sessions,
|
|
146
|
+
projects,
|
|
147
|
+
hooks,
|
|
148
|
+
attached,
|
|
149
|
+
url,
|
|
150
|
+
port: boundPort,
|
|
151
|
+
token: state.token,
|
|
152
|
+
protocol,
|
|
153
|
+
close: () => new Promise(resolve => {
|
|
154
|
+
wss.clients.forEach(c => {
|
|
155
|
+
try {
|
|
156
|
+
c.close();
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// ignore
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
attached.closeAll('broker_shutdown');
|
|
163
|
+
hooks.cancelAll('broker_shutdown');
|
|
164
|
+
sessions.stopIdleArchiver();
|
|
165
|
+
void sessions.closeAll();
|
|
166
|
+
try {
|
|
167
|
+
persist();
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
// ignore — best-effort on shutdown
|
|
171
|
+
}
|
|
172
|
+
bus.closeAll();
|
|
173
|
+
server.close(() => resolve());
|
|
174
|
+
}),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function attachWsRouter(server, wss, token, bus, whisper, projects, dispatcher, sessions, attached) {
|
|
178
|
+
server.on('upgrade', (req, socket, head) => {
|
|
179
|
+
const url = new URL(req.url ?? '/', 'http://localhost');
|
|
180
|
+
const audioMatch = /^\/api\/sessions\/([^/]+)\/audio$/.exec(url.pathname);
|
|
181
|
+
const injectMatch = /^\/api\/sessions\/([^/]+)\/inject$/.exec(url.pathname);
|
|
182
|
+
if (!audioMatch && !injectMatch) {
|
|
183
|
+
socket.write('HTTP/1.1 404 Not Found\r\n\r\n');
|
|
184
|
+
socket.destroy();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const presented = url.searchParams.get('token') ?? '';
|
|
188
|
+
if (!presented || !constantTimeEqual(presented, token)) {
|
|
189
|
+
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
|
190
|
+
socket.destroy();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (injectMatch) {
|
|
194
|
+
const sessionId = injectMatch[1];
|
|
195
|
+
// We don't require the session to exist *yet* — the wrapper may open the
|
|
196
|
+
// WS before the POST /api/sessions/attached round-trip completes in some
|
|
197
|
+
// races. We still gate on having a project though, to avoid an open WS
|
|
198
|
+
// from a stale token spamming the hub.
|
|
199
|
+
void sessions;
|
|
200
|
+
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
201
|
+
attached.register(sessionId, ws);
|
|
202
|
+
ws.on('message', () => {
|
|
203
|
+
// The wrapper currently only listens; if it ever sends an ack,
|
|
204
|
+
// drop it silently rather than crash.
|
|
205
|
+
});
|
|
206
|
+
ws.send(JSON.stringify({ type: 'hello', sessionId, role: 'inject' }));
|
|
207
|
+
});
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const sessionId = audioMatch[1];
|
|
211
|
+
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
212
|
+
let finalised = false;
|
|
213
|
+
const audio = new AudioSession(sessionId, {
|
|
214
|
+
stt: whisper,
|
|
215
|
+
listProjects: () => projects.list(),
|
|
216
|
+
onResult: result => {
|
|
217
|
+
const outcome = handleAudioResult(result, dispatcher);
|
|
218
|
+
// Best-effort send: the client may have already closed the WS.
|
|
219
|
+
if (ws.readyState === ws.OPEN) {
|
|
220
|
+
try {
|
|
221
|
+
ws.send(JSON.stringify(outcome));
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
console.warn(`[audio] send result failed: ${err.message}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
publishVoiceEvents(bus, sessionId, result, outcome);
|
|
228
|
+
// Close after the result has been queued so the client sees it before
|
|
229
|
+
// the close event lands.
|
|
230
|
+
if (ws.readyState === ws.OPEN) {
|
|
231
|
+
try {
|
|
232
|
+
ws.close(1000, 'audio_result sent');
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
// ignore
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
onError: err => console.warn(`[audio] session=${sessionId} error: ${err.message}`),
|
|
240
|
+
});
|
|
241
|
+
const finalise = async () => {
|
|
242
|
+
if (finalised)
|
|
243
|
+
return;
|
|
244
|
+
finalised = true;
|
|
245
|
+
try {
|
|
246
|
+
await audio.close();
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
console.warn(`[audio] session=${sessionId} close failed: ${err.message}`);
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
ws.on('message', (data, isBinary) => {
|
|
253
|
+
// Text messages are control frames — currently only `{"type":"finalize"}`.
|
|
254
|
+
if (!isBinary) {
|
|
255
|
+
const text = data.toString();
|
|
256
|
+
try {
|
|
257
|
+
const parsed = JSON.parse(text);
|
|
258
|
+
if (parsed.type === 'finalize') {
|
|
259
|
+
void finalise();
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
// ignore — unknown text frames are dropped.
|
|
265
|
+
}
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (Array.isArray(data)) {
|
|
269
|
+
for (const chunk of data)
|
|
270
|
+
audio.pushFrame(chunk);
|
|
271
|
+
}
|
|
272
|
+
else if (Buffer.isBuffer(data)) {
|
|
273
|
+
audio.pushFrame(data);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
audio.pushFrame(Buffer.from(data));
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
ws.on('close', () => {
|
|
280
|
+
void finalise();
|
|
281
|
+
});
|
|
282
|
+
ws.on('error', () => {
|
|
283
|
+
void finalise();
|
|
284
|
+
});
|
|
285
|
+
ws.send(JSON.stringify({ type: 'hello', sessionId, milestone: 'M6' }));
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
function handleAudioResult(result, dispatcher) {
|
|
290
|
+
const outcome = dispatcher.dispatch(result.intent);
|
|
291
|
+
const msg = {
|
|
292
|
+
type: 'audio_result',
|
|
293
|
+
sessionId: result.sessionId,
|
|
294
|
+
transcript: result.transcript.text,
|
|
295
|
+
durationMs: result.durationMs,
|
|
296
|
+
bytes: result.bytes,
|
|
297
|
+
outcome,
|
|
298
|
+
};
|
|
299
|
+
if (result.transcript.language)
|
|
300
|
+
msg.language = result.transcript.language;
|
|
301
|
+
if (result.transcript.unavailable)
|
|
302
|
+
msg.unavailable = true;
|
|
303
|
+
if (result.transcript.reason)
|
|
304
|
+
msg.reason = result.transcript.reason;
|
|
305
|
+
return msg;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Mirror the audio result onto the session's SSE channel so a plugin that's
|
|
309
|
+
* subscribed to /events sees the voice command even when the WS isn't the
|
|
310
|
+
* primary control channel. Lets multiple clients (e.g. a future web admin UI)
|
|
311
|
+
* stay in sync without polling the WS.
|
|
312
|
+
*/
|
|
313
|
+
function publishVoiceEvents(bus, sessionId, result, outcome) {
|
|
314
|
+
bus.publish(`session:${sessionId}`, 'voice_transcript', {
|
|
315
|
+
text: result.transcript.text,
|
|
316
|
+
language: result.transcript.language,
|
|
317
|
+
unavailable: result.transcript.unavailable ?? false,
|
|
318
|
+
durationMs: result.durationMs,
|
|
319
|
+
});
|
|
320
|
+
bus.publish(`session:${sessionId}`, 'voice_intent', {
|
|
321
|
+
intent: result.intent,
|
|
322
|
+
outcome: outcome.outcome,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
const DEFAULT_DIR = process.env.CLAUDE_HUD_DIR ?? path.join(os.homedir(), '.claude-hud');
|
|
5
|
+
/**
|
|
6
|
+
* Atomic on-disk snapshot of the session registry.
|
|
7
|
+
*
|
|
8
|
+
* Kept separate from `Store` (which owns the broker bearer token + settings) so
|
|
9
|
+
* that high-frequency session-state writes don't churn the file that also holds
|
|
10
|
+
* the bearer token.
|
|
11
|
+
*/
|
|
12
|
+
export class SessionsSnapshotStore {
|
|
13
|
+
dir;
|
|
14
|
+
file;
|
|
15
|
+
constructor(dir = DEFAULT_DIR) {
|
|
16
|
+
this.dir = dir;
|
|
17
|
+
this.file = path.join(dir, 'sessions.json');
|
|
18
|
+
}
|
|
19
|
+
load() {
|
|
20
|
+
if (!fs.existsSync(this.file)) {
|
|
21
|
+
return { version: 1, sessions: [] };
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const parsed = JSON.parse(fs.readFileSync(this.file, 'utf8'));
|
|
25
|
+
return {
|
|
26
|
+
version: 1,
|
|
27
|
+
sessions: Array.isArray(parsed.sessions) ? parsed.sessions : [],
|
|
28
|
+
focusedId: typeof parsed.focusedId === 'string' ? parsed.focusedId : undefined,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
const backup = `${this.file}.corrupt-${Date.now()}`;
|
|
33
|
+
try {
|
|
34
|
+
fs.renameSync(this.file, backup);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// ignore — disk may be read-only in tests
|
|
38
|
+
}
|
|
39
|
+
console.warn(`[sessions-store] sessions.json was unreadable (${err.message}); moved to ${backup}`);
|
|
40
|
+
return { version: 1, sessions: [] };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
save(snapshot) {
|
|
44
|
+
fs.mkdirSync(this.dir, { recursive: true });
|
|
45
|
+
const tmp = `${this.file}.tmp`;
|
|
46
|
+
fs.writeFileSync(tmp, `${JSON.stringify(snapshot, null, 2)}\n`, { mode: 0o600 });
|
|
47
|
+
fs.renameSync(tmp, this.file);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=sessions-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions-store.js","sourceRoot":"","sources":["../src/sessions-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,MAAM,SAAS,CAAA;AAGxB,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,aAAa,CAAC,CAAA;AAExF;;;;;;GAMG;AACH,MAAM,OAAO,qBAAqB;IACvB,GAAG,CAAQ;IACX,IAAI,CAAQ;IAErB,YAAY,MAAc,WAAW;QACnC,IAAI,CAAC,GAAG,GAAG,GAAG,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAA;IAC7C,CAAC;IAED,IAAI;QACF,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAA;QACrC,CAAC;QACD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAA0B,CAAA;YACtF,OAAO;gBACL,OAAO,EAAE,CAAC;gBACV,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;gBAC/D,SAAS,EAAE,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;aAC/E,CAAA;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,YAAY,IAAI,CAAC,GAAG,EAAE,EAAE,CAAA;YACnD,IAAI,CAAC;gBACH,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;YAClC,CAAC;YAAC,MAAM,CAAC;gBACP,0CAA0C;YAC5C,CAAC;YACD,OAAO,CAAC,IAAI,CACV,kDAAmD,GAAa,CAAC,OAAO,eAAe,MAAM,EAAE,CAChG,CAAA;YACD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAA;QACrC,CAAC;IACH,CAAC;IAED,IAAI,CAAC,QAAsB;QACzB,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QAC3C,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,IAAI,MAAM,CAAA;QAC9B,EAAE,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;QAChF,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;IAC/B,CAAC;CACF"}
|