wireweave 0.3.22 → 0.3.25

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wireweave",
3
- "version": "0.3.22",
3
+ "version": "0.3.25",
4
4
  "description": "nostr + webrtc voice + data SDK. networking layer for 247420 projects.",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
package/src/relay-pool.js CHANGED
@@ -12,6 +12,9 @@ const DEFAULT_RELAYS = [
12
12
  ];
13
13
 
14
14
  const SEEN_MAX = 10000;
15
+ const PENDING_MAX = 500;
16
+ const PENDING_TTL_MS = 120000;
17
+ const jitter = (ms) => Math.round(ms * (0.75 + Math.random() * 0.5));
15
18
 
16
19
  const lruTouch = (map, key) => {
17
20
  if (map.has(key)) { map.delete(key); map.set(key, 1); return false; }
@@ -36,16 +39,22 @@ export class RelayPool extends EventTarget {
36
39
  this.subs = new Map();
37
40
  this.pending = [];
38
41
  this.seen = new Map();
42
+ this._reconnectTimers = new Map();
43
+ this._closed = false;
39
44
  this.verifyEvent = verifyEvent;
40
45
  this.WS = WebSocketImpl || (typeof WebSocket !== 'undefined' ? WebSocket : null);
41
46
  if (!this.WS) throw new Error('No WebSocket implementation available');
42
47
  }
43
48
 
44
49
  connect() {
50
+ this._closed = false;
45
51
  for (const url of this.urls) this._open(url);
46
52
  }
47
53
 
48
54
  disconnect() {
55
+ this._closed = true;
56
+ for (const [, t] of this._reconnectTimers) clearTimeout(t);
57
+ this._reconnectTimers.clear();
49
58
  for (const [, r] of this.relays) {
50
59
  if (r.ws) {
51
60
  r.ws.onclose = null; r.ws.onerror = null; r.ws.onopen = null; r.ws.onmessage = null;
@@ -58,6 +67,8 @@ export class RelayPool extends EventTarget {
58
67
  }
59
68
 
60
69
  _open(url) {
70
+ if (this._closed) return;
71
+ this._reconnectTimers.delete(url);
61
72
  const existing = this.relays.get(url);
62
73
  if (existing?.ws && (existing.ws.readyState === 0 || existing.ws.readyState === 1)) return;
63
74
  const relay = existing || { ws: null, status: 'connecting', subIds: new Set(), latencyMs: null, failCount: 0, reconnectDelay: 1000, _reqSentAt: null, _openedAt: null };
@@ -94,7 +105,9 @@ export class RelayPool extends EventTarget {
94
105
  if (sustained) { relay.failCount = 0; relay.reconnectDelay = 1000; }
95
106
  else { relay.failCount++; relay.reconnectDelay = Math.min(relay.reconnectDelay * 2, 30000); }
96
107
  relay._openedAt = null;
97
- setTimeout(() => this._open(url), relay.reconnectDelay);
108
+ if (this._closed) return;
109
+ const t = setTimeout(() => this._open(url), jitter(relay.reconnectDelay));
110
+ this._reconnectTimers.set(url, t);
98
111
  };
99
112
  }
100
113
 
@@ -156,13 +169,17 @@ export class RelayPool extends EventTarget {
156
169
  sent = true;
157
170
  }
158
171
  }
159
- if (!sent) this.pending.push(event);
172
+ if (!sent) {
173
+ this.pending.push({ event, ts: Date.now() });
174
+ if (this.pending.length > PENDING_MAX) this.pending.splice(0, this.pending.length - PENDING_MAX);
175
+ }
160
176
  return sent;
161
177
  }
162
178
 
163
179
  _drainPending() {
180
+ const cutoff = Date.now() - PENDING_TTL_MS;
164
181
  const pending = this.pending.splice(0);
165
- for (const e of pending) this.publish(e);
182
+ for (const p of pending) { if (p.ts >= cutoff) this.publish(p.event); }
166
183
  }
167
184
 
168
185
  isConnected() {
package/src/wireweave.js CHANGED
@@ -12,6 +12,7 @@ import { createSettings } from './settings.js';
12
12
  import { createMedia } from './media.js';
13
13
  import { createPages } from './pages.js';
14
14
  import { createDM } from './dm.js';
15
+ import { createDataSession } from './data.js';
15
16
  import { register } from './debug.js';
16
17
 
17
18
  export const createWireweave = ({
@@ -86,12 +87,24 @@ export const createWireweave = ({
86
87
  return dm;
87
88
  };
88
89
 
90
+ // DataSession is lazy for the same reason as DM: requires xstate and FSM.
91
+ // onSwitch accumulates subscriptions across all visited servers (idempotent
92
+ // Map pattern) — no unsubscribe on server switch by design so offline data
93
+ // from prior servers remains cached.
94
+ let data = null;
95
+ const ensureData = ({ room = '', displayName = 'Guest', namespace = '' } = {}) => {
96
+ if (!data) data = createDataSession({ fsm, xstate, relayPool: pool, auth, namespace });
97
+ return data;
98
+ };
99
+
89
100
  const api = {
90
101
  pool, auth, fsm, message, bans, roles, settings, pages, media, channels, servers, chat,
91
102
  get voice() { return voice; },
92
103
  ensureVoice,
93
104
  get dm() { return dm; },
94
105
  ensureDM,
106
+ get data() { return data; },
107
+ ensureData,
95
108
  setCurrentChannel,
96
109
  get currentChannelId() { return currentChannelId; },
97
110
  get currentServerId() { return servers.currentServerId; }