wireweave 0.3.28 → 0.3.29

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.28",
3
+ "version": "0.3.29",
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/chat.js CHANGED
@@ -12,6 +12,16 @@ export class Chat extends EventTarget {
12
12
  this.activeChannelId = null;
13
13
  this.messages = [];
14
14
  this.profiles = new Map(); this.fetching = new Set();
15
+ this._sendTimes = [];
16
+ this.rateLimitMax = 5;
17
+ this.rateLimitWindowMs = 10000;
18
+ }
19
+
20
+ rateLimitRetryAfterMs() {
21
+ const now = Date.now();
22
+ this._sendTimes = (this._sendTimes || []).filter(t => now - t < this.rateLimitWindowMs);
23
+ if (this._sendTimes.length < this.rateLimitMax) return 0;
24
+ return this._sendTimes[0] + this.rateLimitWindowMs - now;
15
25
  }
16
26
 
17
27
  async send(content, { announcement = false } = {}) {
@@ -19,6 +29,12 @@ export class Chat extends EventTarget {
19
29
  if (!this.auth.isLoggedIn() || !channelId) return;
20
30
  if (announcement && !this.isAdmin(serverId)) return;
21
31
  const trimmed = content.trim(); if (!trimmed) return;
32
+ const retryAfter = this.rateLimitRetryAfterMs();
33
+ if (retryAfter > 0) {
34
+ this._emit('rate-limited', { retryAfterMs: retryAfter });
35
+ return;
36
+ }
37
+ this._sendTimes.push(Date.now());
22
38
  const chanHex = await hexChannelId(channelId, serverId);
23
39
  const tags = [['e', chanHex, '', 'root']];
24
40
  if (announcement) tags.push(['t', 'announcement']);
package/src/relay-pool.js CHANGED
@@ -14,6 +14,7 @@ const DEFAULT_RELAYS = [
14
14
  const SEEN_MAX = 10000;
15
15
  const PENDING_MAX = 500;
16
16
  const PENDING_TTL_MS = 120000;
17
+ const CONNECT_TIMEOUT_MS = 10000;
17
18
  const jitter = (ms) => Math.round(ms * (0.75 + Math.random() * 0.5));
18
19
 
19
20
  const lruTouch = (map, key) => {
@@ -60,6 +61,7 @@ export class RelayPool extends EventTarget {
60
61
  for (const [, rec] of this._acks) { clearTimeout(rec.timer); rec.resolve(false); }
61
62
  this._acks.clear();
62
63
  for (const [, r] of this.relays) {
64
+ if (r._connectTimer) clearTimeout(r._connectTimer);
63
65
  if (r.ws) {
64
66
  r.ws.onclose = null; r.ws.onerror = null; r.ws.onopen = null; r.ws.onmessage = null;
65
67
  if (typeof r.ws.removeAllListeners === 'function') r.ws.removeAllListeners();
@@ -83,7 +85,13 @@ export class RelayPool extends EventTarget {
83
85
  try { ws = new this.WS(url); }
84
86
  catch (e) { relay.status = 'error'; this._emit('relay-status', { url, status: 'error' }); return; }
85
87
  relay.ws = ws;
88
+ const connectTimer = setTimeout(() => {
89
+ if (relay.ws !== ws || relay.status !== 'connecting') return;
90
+ try { ws.close(); } catch {}
91
+ }, CONNECT_TIMEOUT_MS);
92
+ relay._connectTimer = connectTimer;
86
93
  ws.onopen = () => {
94
+ clearTimeout(connectTimer);
87
95
  relay.status = 'connected';
88
96
  relay._openedAt = Date.now();
89
97
  this._emit('relay-status', { url, status: 'connected' });
@@ -101,8 +109,9 @@ export class RelayPool extends EventTarget {
101
109
  }
102
110
  try { this._handle(url, typeof e.data === 'string' ? e.data : e.data.toString()); } catch {}
103
111
  };
104
- ws.onerror = () => { relay.status = 'error'; this._emit('relay-status', { url, status: 'error' }); };
112
+ ws.onerror = () => { clearTimeout(connectTimer); relay.status = 'error'; this._emit('relay-status', { url, status: 'error' }); };
105
113
  ws.onclose = () => {
114
+ clearTimeout(connectTimer);
106
115
  relay.status = 'closed';
107
116
  this._emit('relay-status', { url, status: 'closed' });
108
117
  const sustained = relay._openedAt && Date.now() - relay._openedAt > 5000;