@sumicom/quicksave 0.5.2 → 0.6.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/ai/asyncQueue.d.ts +17 -0
- package/dist/ai/asyncQueue.d.ts.map +1 -0
- package/dist/ai/asyncQueue.js +49 -0
- package/dist/ai/asyncQueue.js.map +1 -0
- package/dist/ai/asyncQueue.test.d.ts +2 -0
- package/dist/ai/asyncQueue.test.d.ts.map +1 -0
- package/dist/ai/asyncQueue.test.js +56 -0
- package/dist/ai/asyncQueue.test.js.map +1 -0
- package/dist/ai/cardBuilder.d.ts +73 -0
- package/dist/ai/cardBuilder.d.ts.map +1 -0
- package/dist/ai/cardBuilder.js +595 -0
- package/dist/ai/cardBuilder.js.map +1 -0
- package/dist/ai/claudeCliProvider.d.ts +19 -0
- package/dist/ai/claudeCliProvider.d.ts.map +1 -0
- package/dist/ai/claudeCliProvider.js +466 -0
- package/dist/ai/claudeCliProvider.js.map +1 -0
- package/dist/ai/claudeSdkProvider.d.ts +18 -0
- package/dist/ai/claudeSdkProvider.d.ts.map +1 -0
- package/dist/ai/claudeSdkProvider.js +418 -0
- package/dist/ai/claudeSdkProvider.js.map +1 -0
- package/dist/ai/commitSummary.d.ts +4 -0
- package/dist/ai/commitSummary.d.ts.map +1 -1
- package/dist/ai/commitSummary.js +80 -40
- package/dist/ai/commitSummary.js.map +1 -1
- package/dist/ai/provider.d.ts +54 -0
- package/dist/ai/provider.d.ts.map +1 -0
- package/dist/ai/provider.js +2 -0
- package/dist/ai/provider.js.map +1 -0
- package/dist/ai/sandboxMcp.d.ts +12 -0
- package/dist/ai/sandboxMcp.d.ts.map +1 -0
- package/dist/ai/sandboxMcp.js +12 -0
- package/dist/ai/sandboxMcp.js.map +1 -0
- package/dist/ai/sandboxMcpStdio.d.ts +16 -0
- package/dist/ai/sandboxMcpStdio.d.ts.map +1 -0
- package/dist/ai/sandboxMcpStdio.js +127 -0
- package/dist/ai/sandboxMcpStdio.js.map +1 -0
- package/dist/ai/sessionManager.d.ts +100 -0
- package/dist/ai/sessionManager.d.ts.map +1 -0
- package/dist/ai/sessionManager.js +595 -0
- package/dist/ai/sessionManager.js.map +1 -0
- package/dist/ai/sessionRegistry.d.ts +28 -0
- package/dist/ai/sessionRegistry.d.ts.map +1 -0
- package/dist/ai/sessionRegistry.js +148 -0
- package/dist/ai/sessionRegistry.js.map +1 -0
- package/dist/connection/connection.d.ts +24 -6
- package/dist/connection/connection.d.ts.map +1 -1
- package/dist/connection/connection.js +85 -13
- package/dist/connection/connection.js.map +1 -1
- package/dist/connection/pubsub.d.ts +41 -0
- package/dist/connection/pubsub.d.ts.map +1 -0
- package/dist/connection/pubsub.js +101 -0
- package/dist/connection/pubsub.js.map +1 -0
- package/dist/connection/pubsub.test.d.ts +2 -0
- package/dist/connection/pubsub.test.d.ts.map +1 -0
- package/dist/connection/pubsub.test.js +96 -0
- package/dist/connection/pubsub.test.js.map +1 -0
- package/dist/git/operations.d.ts +6 -1
- package/dist/git/operations.d.ts.map +1 -1
- package/dist/git/operations.js +31 -2
- package/dist/git/operations.js.map +1 -1
- package/dist/handlers/messageHandler.d.ts +25 -5
- package/dist/handlers/messageHandler.d.ts.map +1 -1
- package/dist/handlers/messageHandler.js +266 -29
- package/dist/handlers/messageHandler.js.map +1 -1
- package/dist/handlers/messageHandler.test.js +1 -1
- package/dist/index.js +192 -41
- package/dist/index.js.map +1 -1
- package/dist/service/ensureDaemon.d.ts.map +1 -1
- package/dist/service/ensureDaemon.js +15 -2
- package/dist/service/ensureDaemon.js.map +1 -1
- package/dist/service/ipc.test.js +6 -6
- package/dist/service/ipcClient.js +1 -1
- package/dist/service/run.d.ts.map +1 -1
- package/dist/service/run.js +120 -11
- package/dist/service/run.js.map +1 -1
- package/dist/service/singleton.d.ts +4 -0
- package/dist/service/singleton.d.ts.map +1 -1
- package/dist/service/singleton.js +28 -2
- package/dist/service/singleton.js.map +1 -1
- package/dist/service/singleton.test.js +1 -1
- package/dist/service/types.d.ts +29 -0
- package/dist/service/types.d.ts.map +1 -1
- package/dist/service/types.js +11 -0
- package/dist/service/types.js.map +1 -1
- package/package.json +5 -3
- package/dist/ai/claudeCodeService.d.ts +0 -111
- package/dist/ai/claudeCodeService.d.ts.map +0 -1
- package/dist/ai/claudeCodeService.js +0 -764
- package/dist/ai/claudeCodeService.js.map +0 -1
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session registry — tracks which sessions have been opened via quicksave.
|
|
3
|
+
*
|
|
4
|
+
* Storage layout:
|
|
5
|
+
* ~/.quicksave/state/session-registry/{encoded-project-path}/{sessionId}.json
|
|
6
|
+
*
|
|
7
|
+
* Path encoding: `/` → `-` (same convention as Claude Code's ~/.claude/projects/).
|
|
8
|
+
*
|
|
9
|
+
* In-memory: two-level Map<cwd, Map<sessionId, entry>>.
|
|
10
|
+
* Loaded once at daemon startup; writes update memory + single file synchronously.
|
|
11
|
+
*/
|
|
12
|
+
import { readdirSync, readFileSync, writeFileSync, unlinkSync, mkdirSync, existsSync, statSync, } from 'fs';
|
|
13
|
+
import { join } from 'path';
|
|
14
|
+
import { getSessionRegistryDir } from '../service/singleton.js';
|
|
15
|
+
function encodeProjectPath(cwd) {
|
|
16
|
+
return cwd.replace(/\//g, '-');
|
|
17
|
+
}
|
|
18
|
+
export class SessionRegistry {
|
|
19
|
+
registry = new Map();
|
|
20
|
+
/**
|
|
21
|
+
* Scan session-registry directory and load all entries into memory.
|
|
22
|
+
*/
|
|
23
|
+
loadAll() {
|
|
24
|
+
const baseDir = getSessionRegistryDir();
|
|
25
|
+
if (!existsSync(baseDir))
|
|
26
|
+
return;
|
|
27
|
+
let subdirs;
|
|
28
|
+
try {
|
|
29
|
+
subdirs = readdirSync(baseDir);
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
console.error('[sessionRegistry] Failed to read registry dir:', err);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
for (const subdir of subdirs) {
|
|
36
|
+
const subdirPath = join(baseDir, subdir);
|
|
37
|
+
try {
|
|
38
|
+
if (!statSync(subdirPath).isDirectory())
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
let files;
|
|
45
|
+
try {
|
|
46
|
+
files = readdirSync(subdirPath).filter((f) => f.endsWith('.json'));
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
for (const file of files) {
|
|
52
|
+
try {
|
|
53
|
+
const raw = readFileSync(join(subdirPath, file), 'utf-8');
|
|
54
|
+
const entry = JSON.parse(raw);
|
|
55
|
+
if (!entry.sessionId || !entry.cwd) {
|
|
56
|
+
console.warn(`[sessionRegistry] Skipping invalid entry: ${join(subdir, file)}`);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
this.setInMemory(entry);
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
console.warn(`[sessionRegistry] Skipping malformed file: ${join(subdir, file)}`, err);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const totalEntries = Array.from(this.registry.values()).reduce((sum, m) => sum + m.size, 0);
|
|
67
|
+
console.log(`[sessionRegistry] Loaded ${totalEntries} entries from ${this.registry.size} projects`);
|
|
68
|
+
}
|
|
69
|
+
getEntry(cwd, sessionId) {
|
|
70
|
+
return this.registry.get(cwd)?.get(sessionId);
|
|
71
|
+
}
|
|
72
|
+
getEntriesForProject(cwd) {
|
|
73
|
+
if (cwd) {
|
|
74
|
+
const projectMap = this.registry.get(cwd);
|
|
75
|
+
if (!projectMap)
|
|
76
|
+
return [];
|
|
77
|
+
return Array.from(projectMap.values()).sort((a, b) => b.lastAccessedAt - a.lastAccessedAt);
|
|
78
|
+
}
|
|
79
|
+
// Return all entries across all projects
|
|
80
|
+
const all = [];
|
|
81
|
+
for (const projectMap of this.registry.values()) {
|
|
82
|
+
for (const entry of projectMap.values()) {
|
|
83
|
+
all.push(entry);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return all.sort((a, b) => b.lastAccessedAt - a.lastAccessedAt);
|
|
87
|
+
}
|
|
88
|
+
upsertEntry(entry) {
|
|
89
|
+
this.setInMemory(entry);
|
|
90
|
+
this.writeToDisk(entry);
|
|
91
|
+
}
|
|
92
|
+
updateEntry(cwd, sessionId, updates) {
|
|
93
|
+
const existing = this.getEntry(cwd, sessionId);
|
|
94
|
+
if (!existing)
|
|
95
|
+
return null;
|
|
96
|
+
const updated = { ...existing, ...updates };
|
|
97
|
+
this.upsertEntry(updated);
|
|
98
|
+
return updated;
|
|
99
|
+
}
|
|
100
|
+
deleteEntry(cwd, sessionId) {
|
|
101
|
+
const projectMap = this.registry.get(cwd);
|
|
102
|
+
if (!projectMap || !projectMap.has(sessionId))
|
|
103
|
+
return false;
|
|
104
|
+
projectMap.delete(sessionId);
|
|
105
|
+
if (projectMap.size === 0) {
|
|
106
|
+
this.registry.delete(cwd);
|
|
107
|
+
}
|
|
108
|
+
// Remove file
|
|
109
|
+
try {
|
|
110
|
+
const filePath = join(getSessionRegistryDir(), encodeProjectPath(cwd), `${sessionId}.json`);
|
|
111
|
+
unlinkSync(filePath);
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
console.error(`[sessionRegistry] Failed to delete file for session=${sessionId}:`, err);
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
setInMemory(entry) {
|
|
119
|
+
let projectMap = this.registry.get(entry.cwd);
|
|
120
|
+
if (!projectMap) {
|
|
121
|
+
projectMap = new Map();
|
|
122
|
+
this.registry.set(entry.cwd, projectMap);
|
|
123
|
+
}
|
|
124
|
+
projectMap.set(entry.sessionId, entry);
|
|
125
|
+
}
|
|
126
|
+
writeToDisk(entry) {
|
|
127
|
+
try {
|
|
128
|
+
const dir = join(getSessionRegistryDir(), encodeProjectPath(entry.cwd));
|
|
129
|
+
if (!existsSync(dir)) {
|
|
130
|
+
mkdirSync(dir, { recursive: true });
|
|
131
|
+
}
|
|
132
|
+
const filePath = join(dir, `${entry.sessionId}.json`);
|
|
133
|
+
writeFileSync(filePath, JSON.stringify(entry, null, 2) + '\n', 'utf-8');
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
console.error(`[sessionRegistry] Failed to write entry for session=${entry.sessionId}:`, err);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
let instance = null;
|
|
141
|
+
export function getSessionRegistry() {
|
|
142
|
+
if (!instance) {
|
|
143
|
+
instance = new SessionRegistry();
|
|
144
|
+
instance.loadAll();
|
|
145
|
+
}
|
|
146
|
+
return instance;
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=sessionRegistry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionRegistry.js","sourceRoot":"","sources":["../../src/ai/sessionRegistry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EACL,WAAW,EACX,YAAY,EACZ,aAAa,EACb,UAAU,EACV,SAAS,EACT,UAAU,EACV,QAAQ,GACT,MAAM,IAAI,CAAC;AACZ,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAGhE,SAAS,iBAAiB,CAAC,GAAW;IACpC,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,OAAO,eAAe;IAClB,QAAQ,GAAG,IAAI,GAAG,EAA6C,CAAC;IAExE;;OAEG;IACH,OAAO;QACL,MAAM,OAAO,GAAG,qBAAqB,EAAE,CAAC;QACxC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,OAAO;QAEjC,IAAI,OAAiB,CAAC;QACtB,IAAI,CAAC;YACH,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;QACjC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,gDAAgD,EAAE,GAAG,CAAC,CAAC;YACrE,OAAO;QACT,CAAC;QAED,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YACzC,IAAI,CAAC;gBACH,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,WAAW,EAAE;oBAAE,SAAS;YACpD,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;YAED,IAAI,KAAe,CAAC;YACpB,IAAI,CAAC;gBACH,KAAK,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;YACrE,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;YAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,IAAI,CAAC;oBACH,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;oBAC1D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAyB,CAAC;oBACtD,IAAI,CAAC,KAAK,CAAC,SAAS,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;wBACnC,OAAO,CAAC,IAAI,CAAC,6CAA6C,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;wBAChF,SAAS;oBACX,CAAC;oBACD,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;gBAC1B,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,OAAO,CAAC,IAAI,CAAC,8CAA8C,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;gBACxF,CAAC;YACH,CAAC;QACH,CAAC;QAED,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAC5F,OAAO,CAAC,GAAG,CAAC,4BAA4B,YAAY,iBAAiB,IAAI,CAAC,QAAQ,CAAC,IAAI,WAAW,CAAC,CAAC;IACtG,CAAC;IAED,QAAQ,CAAC,GAAW,EAAE,SAAiB;QACrC,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC;IAChD,CAAC;IAED,oBAAoB,CAAC,GAAY;QAC/B,IAAI,GAAG,EAAE,CAAC;YACR,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC1C,IAAI,CAAC,UAAU;gBAAE,OAAO,EAAE,CAAC;YAC3B,OAAO,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,GAAG,CAAC,CAAC,cAAc,CAAC,CAAC;QAC7F,CAAC;QACD,yCAAyC;QACzC,MAAM,GAAG,GAA2B,EAAE,CAAC;QACvC,KAAK,MAAM,UAAU,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;YAChD,KAAK,MAAM,KAAK,IAAI,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC;gBACxC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAClB,CAAC;QACH,CAAC;QACD,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,GAAG,CAAC,CAAC,cAAc,CAAC,CAAC;IACjE,CAAC;IAED,WAAW,CAAC,KAA2B;QACrC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QACxB,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IAED,WAAW,CACT,GAAW,EACX,SAAiB,EACjB,OAAsC;QAEtC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAC/C,IAAI,CAAC,QAAQ;YAAE,OAAO,IAAI,CAAC;QAC3B,MAAM,OAAO,GAAG,EAAE,GAAG,QAAQ,EAAE,GAAG,OAAO,EAAE,CAAC;QAC5C,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QAC1B,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,WAAW,CAAC,GAAW,EAAE,SAAiB;QACxC,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC1C,IAAI,CAAC,UAAU,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC;YAAE,OAAO,KAAK,CAAC;QAE5D,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC7B,IAAI,UAAU,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YAC1B,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC;QAED,cAAc;QACd,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,IAAI,CAAC,qBAAqB,EAAE,EAAE,iBAAiB,CAAC,GAAG,CAAC,EAAE,GAAG,SAAS,OAAO,CAAC,CAAC;YAC5F,UAAU,CAAC,QAAQ,CAAC,CAAC;QACvB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,uDAAuD,SAAS,GAAG,EAAE,GAAG,CAAC,CAAC;QAC1F,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,WAAW,CAAC,KAA2B;QAC7C,IAAI,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC9C,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,UAAU,GAAG,IAAI,GAAG,EAAE,CAAC;YACvB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;QAC3C,CAAC;QACD,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IACzC,CAAC;IAEO,WAAW,CAAC,KAA2B;QAC7C,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,qBAAqB,EAAE,EAAE,iBAAiB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;YACxE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACrB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACtC,CAAC;YACD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,OAAO,CAAC,CAAC;YACtD,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;QAC1E,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CACX,uDAAuD,KAAK,CAAC,SAAS,GAAG,EACzE,GAAG,CACJ,CAAC;QACJ,CAAC;IACH,CAAC;CACF;AAED,IAAI,QAAQ,GAA2B,IAAI,CAAC;AAE5C,MAAM,UAAU,kBAAkB;IAChC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,QAAQ,GAAG,IAAI,eAAe,EAAE,CAAC;QACjC,QAAQ,CAAC,OAAO,EAAE,CAAC;IACrB,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -24,7 +24,7 @@ export declare class AgentConnection extends EventEmitter {
|
|
|
24
24
|
private signaling;
|
|
25
25
|
private keyPair;
|
|
26
26
|
private peers;
|
|
27
|
-
private
|
|
27
|
+
private pubsub;
|
|
28
28
|
private static readonly KEY_EXCHANGE_MAX_AGE_MS;
|
|
29
29
|
constructor(config: ConnectionConfig);
|
|
30
30
|
private setupSignalingHandlers;
|
|
@@ -34,6 +34,7 @@ export declare class AgentConnection extends EventEmitter {
|
|
|
34
34
|
* Handle key exchange message
|
|
35
35
|
*/
|
|
36
36
|
private handleKeyExchange;
|
|
37
|
+
private sendQueues;
|
|
37
38
|
send(message: Message, targetAddress: string): void;
|
|
38
39
|
private handlePeerDisconnected;
|
|
39
40
|
disconnect(): void;
|
|
@@ -41,12 +42,29 @@ export declare class AgentConnection extends EventEmitter {
|
|
|
41
42
|
getAgentId(): string;
|
|
42
43
|
getPeerCount(): number;
|
|
43
44
|
hasPeers(): boolean;
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Register which session a peer is currently viewing.
|
|
47
|
+
* Returns true if this is a NEW subscription (peer was not already on this session topic).
|
|
48
|
+
*/
|
|
49
|
+
subscribePeerToSession(peerAddress: string, sessionId: string): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Remove a peer's session subscription.
|
|
52
|
+
* Called when the PWA explicitly unsubscribes from a session.
|
|
53
|
+
*/
|
|
54
|
+
unsubscribePeerFromSession(peerAddress: string, sessionId: string): void;
|
|
55
|
+
/** Debug snapshot of peers and pubsub state. */
|
|
56
|
+
getDebugState(): {
|
|
57
|
+
peers: Array<{
|
|
58
|
+
address: string;
|
|
59
|
+
connectedAt: number;
|
|
60
|
+
topics: string[];
|
|
61
|
+
}>;
|
|
62
|
+
subscriptions: Record<string, string[]>;
|
|
63
|
+
};
|
|
64
|
+
/** Send a message to all connected peers via broadcast topic. */
|
|
47
65
|
broadcast(message: Message): void;
|
|
48
|
-
/** Send a message only to peers subscribed to a specific session. */
|
|
49
|
-
sendToSession(sessionId: string, message: Message):
|
|
66
|
+
/** Send a message only to peers subscribed to a specific session. Returns number of peers sent to. */
|
|
67
|
+
sendToSession(sessionId: string, message: Message): number;
|
|
50
68
|
}
|
|
51
69
|
/**
|
|
52
70
|
* Generate and encode a new key pair for the agent
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/connection/connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAGtC,OAAO,EASL,KAAK,OAAO,EAGb,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/connection/connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAGtC,OAAO,EASL,KAAK,OAAO,EAGb,MAAM,2BAA2B,CAAC;AAOnC,MAAM,WAAW,gBAAgB;IAC/B,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;CACnD;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,UAAU,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,YAAY,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAC/B;AAED,qBAAa,eAAgB,SAAQ,YAAY;IAC/C,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,KAAK,CAAuC;IACpD,OAAO,CAAC,MAAM,CAAgB;IAG9B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAAS;gBAE5C,MAAM,EAAE,gBAAgB;IAQpC,OAAO,CAAC,sBAAsB;IAqCxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAQd,iBAAiB;IAsC/B;;OAEG;YACW,iBAAiB;IAyD/B,OAAO,CAAC,UAAU,CAAyC;IAE3D,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI;IAsBnD,OAAO,CAAC,sBAAsB;IAa9B,UAAU,IAAI,IAAI;IAIlB,YAAY,IAAI,MAAM;IAItB,UAAU,IAAI,MAAM;IAIpB,YAAY,IAAI,MAAM;IAItB,QAAQ,IAAI,OAAO;IAInB;;;OAGG;IACH,sBAAsB,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;IAWvE;;;OAGG;IACH,0BAA0B,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAMxE,gDAAgD;IAChD,aAAa,IAAI;QAAE,KAAK,EAAE,KAAK,CAAC;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,WAAW,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,EAAE,CAAA;SAAE,CAAC,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;KAAE;IActI,iEAAiE;IACjE,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAgBjC,sGAAsG;IACtG,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM;CAmB3D;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAE/E"}
|
|
@@ -3,6 +3,7 @@ import { gzip, gunzip } from 'zlib';
|
|
|
3
3
|
import { promisify } from 'util';
|
|
4
4
|
import { generateKeyPair, encodeKeyPair, decodeKeyPair, encryptWithSharedSecret, decryptWithSharedSecret, decryptDEK, parseMessage, serializeMessage, } from '@sumicom/quicksave-shared';
|
|
5
5
|
import { SignalingClient } from './relay.js';
|
|
6
|
+
import { PubSub, sessionTopic, BROADCAST_TOPIC } from './pubsub.js';
|
|
6
7
|
const gzipAsync = promisify(gzip);
|
|
7
8
|
const gunzipAsync = promisify(gunzip);
|
|
8
9
|
export class AgentConnection extends EventEmitter {
|
|
@@ -10,7 +11,7 @@ export class AgentConnection extends EventEmitter {
|
|
|
10
11
|
signaling;
|
|
11
12
|
keyPair;
|
|
12
13
|
peers = new Map();
|
|
13
|
-
|
|
14
|
+
pubsub = new PubSub();
|
|
14
15
|
// Key exchange replay protection
|
|
15
16
|
static KEY_EXCHANGE_MAX_AGE_MS = 60000; // 60 seconds
|
|
16
17
|
constructor(config) {
|
|
@@ -40,8 +41,9 @@ export class AgentConnection extends EventEmitter {
|
|
|
40
41
|
});
|
|
41
42
|
// Reset encryption state when WebSocket reconnects (before peer-disconnected)
|
|
42
43
|
this.signaling.on('disconnected', () => {
|
|
43
|
-
// Clear all peers
|
|
44
|
+
// Clear all peers and their pubsub subscriptions
|
|
44
45
|
for (const [address] of this.peers) {
|
|
46
|
+
this.pubsub.unsubscribeAll(address);
|
|
45
47
|
this.emit('disconnected', address);
|
|
46
48
|
}
|
|
47
49
|
this.peers.clear();
|
|
@@ -128,6 +130,8 @@ export class AgentConnection extends EventEmitter {
|
|
|
128
130
|
connectedAt: Date.now(),
|
|
129
131
|
});
|
|
130
132
|
this.emit('connected', peerAddress);
|
|
133
|
+
// Auto-subscribe new peers to broadcast topic
|
|
134
|
+
this.pubsub.subscribe(peerAddress, BROADCAST_TOPIC);
|
|
131
135
|
// V2: Send acknowledgment
|
|
132
136
|
const ack = JSON.stringify({
|
|
133
137
|
type: 'key-exchange-ack',
|
|
@@ -140,26 +144,38 @@ export class AgentConnection extends EventEmitter {
|
|
|
140
144
|
this.emit('error', new Error('Failed to decrypt session DEK'));
|
|
141
145
|
}
|
|
142
146
|
}
|
|
147
|
+
// Per-peer send queue to guarantee message ordering.
|
|
148
|
+
// gzipAsync is non-blocking — without serialization, fast consecutive
|
|
149
|
+
// send() calls can compress out of order and arrive at the relay scrambled.
|
|
150
|
+
sendQueues = new Map();
|
|
143
151
|
send(message, targetAddress) {
|
|
144
152
|
const peer = this.peers.get(targetAddress);
|
|
145
153
|
if (!peer) {
|
|
146
154
|
console.error(`No peer session for ${targetAddress}, cannot encrypt message`);
|
|
147
155
|
return;
|
|
148
156
|
}
|
|
149
|
-
// Compress before encryption for better compression ratio
|
|
150
157
|
const serialized = serializeMessage(message);
|
|
151
|
-
|
|
158
|
+
const prev = this.sendQueues.get(targetAddress) ?? Promise.resolve();
|
|
159
|
+
const next = prev.then(async () => {
|
|
160
|
+
// Re-check peer: may have disconnected while queued
|
|
161
|
+
if (!this.peers.has(targetAddress))
|
|
162
|
+
return;
|
|
163
|
+
const compressed = await gzipAsync(Buffer.from(serialized));
|
|
152
164
|
const compressedBase64 = compressed.toString('base64');
|
|
153
165
|
const encrypted = encryptWithSharedSecret(compressedBase64, peer.sessionDEK);
|
|
154
166
|
this.signaling.sendData(encrypted, targetAddress);
|
|
155
167
|
}).catch((error) => {
|
|
156
168
|
console.error('Failed to send message:', error);
|
|
157
169
|
});
|
|
170
|
+
this.sendQueues.set(targetAddress, next);
|
|
158
171
|
}
|
|
159
172
|
handlePeerDisconnected(peerAddress) {
|
|
160
173
|
if (this.peers.has(peerAddress)) {
|
|
161
174
|
this.peers.delete(peerAddress);
|
|
162
|
-
this.
|
|
175
|
+
const removedTopics = this.pubsub.unsubscribeAll(peerAddress);
|
|
176
|
+
if (removedTopics.size > 0) {
|
|
177
|
+
console.log(`[disconnect] ${peerAddress.slice(0, 12)} removed from ${removedTopics.size} topics`);
|
|
178
|
+
}
|
|
163
179
|
this.emit('disconnected', peerAddress);
|
|
164
180
|
}
|
|
165
181
|
console.log('Peer disconnected, waiting for new connection...');
|
|
@@ -179,23 +195,79 @@ export class AgentConnection extends EventEmitter {
|
|
|
179
195
|
hasPeers() {
|
|
180
196
|
return this.peers.size > 0;
|
|
181
197
|
}
|
|
182
|
-
/**
|
|
198
|
+
/**
|
|
199
|
+
* Register which session a peer is currently viewing.
|
|
200
|
+
* Returns true if this is a NEW subscription (peer was not already on this session topic).
|
|
201
|
+
*/
|
|
183
202
|
subscribePeerToSession(peerAddress, sessionId) {
|
|
184
|
-
|
|
203
|
+
const topic = sessionTopic(sessionId);
|
|
204
|
+
const isNew = this.pubsub.subscribe(peerAddress, topic);
|
|
205
|
+
if (isNew) {
|
|
206
|
+
console.log(`[sub] ${peerAddress.slice(0, 12)} → ${sessionId.slice(0, 8)}`);
|
|
207
|
+
}
|
|
208
|
+
// Ensure broadcast subscription
|
|
209
|
+
this.pubsub.subscribe(peerAddress, BROADCAST_TOPIC);
|
|
210
|
+
return isNew;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Remove a peer's session subscription.
|
|
214
|
+
* Called when the PWA explicitly unsubscribes from a session.
|
|
215
|
+
*/
|
|
216
|
+
unsubscribePeerFromSession(peerAddress, sessionId) {
|
|
217
|
+
const topic = sessionTopic(sessionId);
|
|
218
|
+
this.pubsub.unsubscribe(peerAddress, topic);
|
|
219
|
+
console.log(`[unsub] ${peerAddress.slice(0, 12)} x ${sessionId.slice(0, 8)}`);
|
|
220
|
+
}
|
|
221
|
+
/** Debug snapshot of peers and pubsub state. */
|
|
222
|
+
getDebugState() {
|
|
223
|
+
const peers = Array.from(this.peers.entries()).map(([addr, ps]) => ({
|
|
224
|
+
address: addr.slice(0, 16),
|
|
225
|
+
connectedAt: ps.connectedAt,
|
|
226
|
+
topics: [...(this.pubsub.topicsOf(addr))],
|
|
227
|
+
}));
|
|
228
|
+
const { topics } = this.pubsub.getState();
|
|
229
|
+
const subscriptions = {};
|
|
230
|
+
for (const [topic, addrs] of Object.entries(topics)) {
|
|
231
|
+
subscriptions[topic] = addrs.map(a => a.slice(0, 16));
|
|
232
|
+
}
|
|
233
|
+
return { peers, subscriptions };
|
|
185
234
|
}
|
|
186
|
-
/** Send a message to all connected peers. */
|
|
235
|
+
/** Send a message to all connected peers via broadcast topic. */
|
|
187
236
|
broadcast(message) {
|
|
188
|
-
|
|
189
|
-
|
|
237
|
+
const subscribers = this.pubsub.subscribers(BROADCAST_TOPIC);
|
|
238
|
+
if (subscribers.size > 0) {
|
|
239
|
+
for (const address of subscribers) {
|
|
240
|
+
if (this.peers.has(address)) {
|
|
241
|
+
this.send(message, address);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
// Fallback: peers connected but haven't subscribed yet
|
|
247
|
+
for (const [address] of this.peers) {
|
|
248
|
+
this.send(message, address);
|
|
249
|
+
}
|
|
190
250
|
}
|
|
191
251
|
}
|
|
192
|
-
/** Send a message only to peers subscribed to a specific session. */
|
|
252
|
+
/** Send a message only to peers subscribed to a specific session. Returns number of peers sent to. */
|
|
193
253
|
sendToSession(sessionId, message) {
|
|
194
|
-
|
|
195
|
-
|
|
254
|
+
const topic = sessionTopic(sessionId);
|
|
255
|
+
const subscribers = this.pubsub.subscribers(topic);
|
|
256
|
+
let sent = 0;
|
|
257
|
+
for (const address of subscribers) {
|
|
258
|
+
if (this.peers.has(address)) {
|
|
196
259
|
this.send(message, address);
|
|
260
|
+
sent++;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (sent === 0) {
|
|
264
|
+
const msgType = message.type;
|
|
265
|
+
const eventType = message.payload?.eventType;
|
|
266
|
+
if (eventType || msgType === 'claude:user-input-request') {
|
|
267
|
+
console.warn(`[sendToSession] NO peers for session=${sessionId.slice(0, 8)} type=${msgType} peers=${this.peers.size}`);
|
|
197
268
|
}
|
|
198
269
|
}
|
|
270
|
+
return sent;
|
|
199
271
|
}
|
|
200
272
|
}
|
|
201
273
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/connection/connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AACjC,OAAO,EACL,eAAe,EACf,aAAa,EACb,aAAa,EACb,uBAAuB,EACvB,uBAAuB,EACvB,UAAU,EACV,YAAY,EACZ,gBAAgB,GAIjB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/connection/connection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Topic-based pub/sub with reverse index for efficient peer cleanup.
|
|
3
|
+
*
|
|
4
|
+
* Topic conventions:
|
|
5
|
+
* "session:{sessionId}" — session-scoped events (cards, stream-end, user-input)
|
|
6
|
+
* "broadcast" — global events (session-updated, preferences-updated)
|
|
7
|
+
*/
|
|
8
|
+
export declare class PubSub {
|
|
9
|
+
/** topic → Set<peerAddress> */
|
|
10
|
+
private topics;
|
|
11
|
+
/** peerAddress → Set<topic> (reverse index for O(topics) cleanup) */
|
|
12
|
+
private peerTopics;
|
|
13
|
+
/**
|
|
14
|
+
* Subscribe a peer to a topic.
|
|
15
|
+
* Returns true if this is a NEW subscription.
|
|
16
|
+
*/
|
|
17
|
+
subscribe(peer: string, topic: string): boolean;
|
|
18
|
+
/** Unsubscribe a peer from a specific topic. */
|
|
19
|
+
unsubscribe(peer: string, topic: string): void;
|
|
20
|
+
/**
|
|
21
|
+
* Unsubscribe a peer from ALL topics. Called on disconnect.
|
|
22
|
+
* Returns the set of topics the peer was subscribed to.
|
|
23
|
+
*/
|
|
24
|
+
unsubscribeAll(peer: string): Set<string>;
|
|
25
|
+
/** Get all peers subscribed to a topic. */
|
|
26
|
+
subscribers(topic: string): ReadonlySet<string>;
|
|
27
|
+
/** Get all topics a peer is subscribed to. */
|
|
28
|
+
topicsOf(peer: string): ReadonlySet<string>;
|
|
29
|
+
/** Check if a topic has any subscribers. */
|
|
30
|
+
hasSubscribers(topic: string): boolean;
|
|
31
|
+
/** Snapshot of all subscriptions for debugging. */
|
|
32
|
+
getState(): {
|
|
33
|
+
topics: Record<string, string[]>;
|
|
34
|
+
peerTopics: Record<string, string[]>;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** Build a session topic string. */
|
|
38
|
+
export declare function sessionTopic(sessionId: string): string;
|
|
39
|
+
/** The broadcast topic — all connected peers auto-subscribe. */
|
|
40
|
+
export declare const BROADCAST_TOPIC = "broadcast";
|
|
41
|
+
//# sourceMappingURL=pubsub.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pubsub.d.ts","sourceRoot":"","sources":["../../src/connection/pubsub.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,qBAAa,MAAM;IACjB,+BAA+B;IAC/B,OAAO,CAAC,MAAM,CAAkC;IAChD,qEAAqE;IACrE,OAAO,CAAC,UAAU,CAAkC;IAEpD;;;OAGG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO;IAmB/C,gDAAgD;IAChD,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAa9C;;;OAGG;IACH,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;IAgBzC,2CAA2C;IAC3C,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC;IAI/C,8CAA8C;IAC9C,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC;IAI3C,4CAA4C;IAC5C,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAKtC,mDAAmD;IACnD,QAAQ,IAAI;QAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;KAAE;CAWvF;AAID,oCAAoC;AACpC,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,gEAAgE;AAChE,eAAO,MAAM,eAAe,cAAc,CAAC"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Topic-based pub/sub with reverse index for efficient peer cleanup.
|
|
3
|
+
*
|
|
4
|
+
* Topic conventions:
|
|
5
|
+
* "session:{sessionId}" — session-scoped events (cards, stream-end, user-input)
|
|
6
|
+
* "broadcast" — global events (session-updated, preferences-updated)
|
|
7
|
+
*/
|
|
8
|
+
export class PubSub {
|
|
9
|
+
/** topic → Set<peerAddress> */
|
|
10
|
+
topics = new Map();
|
|
11
|
+
/** peerAddress → Set<topic> (reverse index for O(topics) cleanup) */
|
|
12
|
+
peerTopics = new Map();
|
|
13
|
+
/**
|
|
14
|
+
* Subscribe a peer to a topic.
|
|
15
|
+
* Returns true if this is a NEW subscription.
|
|
16
|
+
*/
|
|
17
|
+
subscribe(peer, topic) {
|
|
18
|
+
let subscribers = this.topics.get(topic);
|
|
19
|
+
if (!subscribers) {
|
|
20
|
+
subscribers = new Set();
|
|
21
|
+
this.topics.set(topic, subscribers);
|
|
22
|
+
}
|
|
23
|
+
const isNew = !subscribers.has(peer);
|
|
24
|
+
subscribers.add(peer);
|
|
25
|
+
let topics = this.peerTopics.get(peer);
|
|
26
|
+
if (!topics) {
|
|
27
|
+
topics = new Set();
|
|
28
|
+
this.peerTopics.set(peer, topics);
|
|
29
|
+
}
|
|
30
|
+
topics.add(topic);
|
|
31
|
+
return isNew;
|
|
32
|
+
}
|
|
33
|
+
/** Unsubscribe a peer from a specific topic. */
|
|
34
|
+
unsubscribe(peer, topic) {
|
|
35
|
+
const subscribers = this.topics.get(topic);
|
|
36
|
+
if (subscribers) {
|
|
37
|
+
subscribers.delete(peer);
|
|
38
|
+
if (subscribers.size === 0)
|
|
39
|
+
this.topics.delete(topic);
|
|
40
|
+
}
|
|
41
|
+
const topics = this.peerTopics.get(peer);
|
|
42
|
+
if (topics) {
|
|
43
|
+
topics.delete(topic);
|
|
44
|
+
if (topics.size === 0)
|
|
45
|
+
this.peerTopics.delete(peer);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Unsubscribe a peer from ALL topics. Called on disconnect.
|
|
50
|
+
* Returns the set of topics the peer was subscribed to.
|
|
51
|
+
*/
|
|
52
|
+
unsubscribeAll(peer) {
|
|
53
|
+
const topics = this.peerTopics.get(peer);
|
|
54
|
+
if (!topics)
|
|
55
|
+
return new Set();
|
|
56
|
+
const removed = new Set(topics);
|
|
57
|
+
for (const topic of topics) {
|
|
58
|
+
const subscribers = this.topics.get(topic);
|
|
59
|
+
if (subscribers) {
|
|
60
|
+
subscribers.delete(peer);
|
|
61
|
+
if (subscribers.size === 0)
|
|
62
|
+
this.topics.delete(topic);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
this.peerTopics.delete(peer);
|
|
66
|
+
return removed;
|
|
67
|
+
}
|
|
68
|
+
/** Get all peers subscribed to a topic. */
|
|
69
|
+
subscribers(topic) {
|
|
70
|
+
return this.topics.get(topic) ?? EMPTY_SET;
|
|
71
|
+
}
|
|
72
|
+
/** Get all topics a peer is subscribed to. */
|
|
73
|
+
topicsOf(peer) {
|
|
74
|
+
return this.peerTopics.get(peer) ?? EMPTY_SET;
|
|
75
|
+
}
|
|
76
|
+
/** Check if a topic has any subscribers. */
|
|
77
|
+
hasSubscribers(topic) {
|
|
78
|
+
const subs = this.topics.get(topic);
|
|
79
|
+
return !!subs && subs.size > 0;
|
|
80
|
+
}
|
|
81
|
+
/** Snapshot of all subscriptions for debugging. */
|
|
82
|
+
getState() {
|
|
83
|
+
const topics = {};
|
|
84
|
+
for (const [topic, peers] of this.topics) {
|
|
85
|
+
topics[topic] = [...peers];
|
|
86
|
+
}
|
|
87
|
+
const peerTopics = {};
|
|
88
|
+
for (const [peer, t] of this.peerTopics) {
|
|
89
|
+
peerTopics[peer] = [...t];
|
|
90
|
+
}
|
|
91
|
+
return { topics, peerTopics };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const EMPTY_SET = new Set();
|
|
95
|
+
/** Build a session topic string. */
|
|
96
|
+
export function sessionTopic(sessionId) {
|
|
97
|
+
return `session:${sessionId}`;
|
|
98
|
+
}
|
|
99
|
+
/** The broadcast topic — all connected peers auto-subscribe. */
|
|
100
|
+
export const BROADCAST_TOPIC = 'broadcast';
|
|
101
|
+
//# sourceMappingURL=pubsub.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pubsub.js","sourceRoot":"","sources":["../../src/connection/pubsub.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pubsub.test.d.ts","sourceRoot":"","sources":["../../src/connection/pubsub.test.ts"],"names":[],"mappings":""}
|