wireweave 0.3.21 → 0.3.22
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 +1 -1
- package/src/bans.js +17 -7
- package/src/channels.js +13 -3
- package/src/chat.js +5 -0
- package/src/data.js +2 -1
- package/src/message.js +1 -1
- package/src/roles.js +10 -4
- package/src/settings.js +10 -4
package/package.json
CHANGED
package/src/bans.js
CHANGED
|
@@ -6,10 +6,11 @@ export class Bans extends EventTarget {
|
|
|
6
6
|
if (!relayPool) throw new Error('Bans: relayPool required');
|
|
7
7
|
this.pool = relayPool; this.auth = auth; this.roles = roles;
|
|
8
8
|
this.store = new Map();
|
|
9
|
-
this.
|
|
9
|
+
this.subs = new Map();
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
isBanned(serverId, pubkey) { return !!(this.store.get(serverId)?.banned || []).includes(pubkey); }
|
|
13
|
+
isKicked(serverId, pubkey) { return !!(this.store.get(serverId)?.kicked || []).includes(pubkey); }
|
|
13
14
|
|
|
14
15
|
isTimedOut(serverId, pubkey) {
|
|
15
16
|
const t = this.store.get(serverId)?.timeouts?.[pubkey];
|
|
@@ -51,12 +52,13 @@ export class Bans extends EventTarget {
|
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
subscribe(serverId) {
|
|
54
|
-
if (this.
|
|
55
|
+
if (this.subs.has(serverId)) return;
|
|
55
56
|
if (!serverId) return;
|
|
56
57
|
const creator = serverId.split(':')[0];
|
|
57
58
|
if (!creator) return;
|
|
58
|
-
|
|
59
|
-
this.
|
|
59
|
+
const subId = 'bans-' + serverId;
|
|
60
|
+
this.subs.set(serverId, subId);
|
|
61
|
+
this.pool.subscribe(subId,
|
|
60
62
|
[{ kinds: [30078], authors: [creator], '#server': [serverId] }],
|
|
61
63
|
(event) => {
|
|
62
64
|
if (event.pubkey !== creator) return;
|
|
@@ -64,11 +66,14 @@ export class Bans extends EventTarget {
|
|
|
64
66
|
const dTag = event.tags.find(t => t[0] === 'd');
|
|
65
67
|
if (!dTag?.[1]) return;
|
|
66
68
|
const parsed = parseDtag(dTag[1]);
|
|
67
|
-
if (!parsed ||
|
|
69
|
+
if (!parsed || !['ban', 'timeout', 'kick'].includes(parsed.ns)) return;
|
|
68
70
|
const pubkey = parsed.parts[parsed.parts.length - 1];
|
|
69
|
-
const data = this.store.get(serverId) || { banned: [], timeouts: {} };
|
|
71
|
+
const data = this.store.get(serverId) || { banned: [], timeouts: {}, kicked: [] };
|
|
70
72
|
if (parsed.ns === 'ban' && pubkey && !data.banned.includes(pubkey)) data.banned.push(pubkey);
|
|
71
|
-
else if (parsed.ns === '
|
|
73
|
+
else if (parsed.ns === 'kick' && pubkey) {
|
|
74
|
+
data.kicked = data.kicked || [];
|
|
75
|
+
if (!data.kicked.includes(pubkey)) data.kicked.push(pubkey);
|
|
76
|
+
} else if (parsed.ns === 'timeout' && pubkey) {
|
|
72
77
|
const body = JSON.parse(event.content);
|
|
73
78
|
if (body.expiry > Math.floor(Date.now() / 1000)) (data.timeouts = data.timeouts || {})[pubkey] = { expiry: body.expiry };
|
|
74
79
|
else if (data.timeouts?.[pubkey]) delete data.timeouts[pubkey];
|
|
@@ -78,6 +83,11 @@ export class Bans extends EventTarget {
|
|
|
78
83
|
} catch {}
|
|
79
84
|
});
|
|
80
85
|
}
|
|
86
|
+
|
|
87
|
+
unsubscribe(serverId) {
|
|
88
|
+
const subId = this.subs.get(serverId);
|
|
89
|
+
if (subId) { this.pool.unsubscribe(subId); this.subs.delete(serverId); }
|
|
90
|
+
}
|
|
81
91
|
}
|
|
82
92
|
|
|
83
93
|
export const createBans = (opts) => new Bans(opts);
|
package/src/channels.js
CHANGED
|
@@ -19,9 +19,10 @@ export class Channels extends EventTarget {
|
|
|
19
19
|
this.serverId = ''; this.channels = []; this.categories = [];
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
isOwner() { return this.auth.pubkey && this.serverId && this.auth.pubkey === this.serverId.split(':')[0]; }
|
|
22
|
+
isOwner() { return !!(this.auth.pubkey && this.serverId && this.auth.pubkey === this.serverId.split(':')[0]); }
|
|
23
23
|
|
|
24
24
|
load(serverId, onReady) {
|
|
25
|
+
if (this.serverId) this.pool.unsubscribe('channels-' + this.serverId);
|
|
25
26
|
this.serverId = serverId;
|
|
26
27
|
this.channels = []; this.categories = [];
|
|
27
28
|
const ownerPubkey = serverId.split(':')[0];
|
|
@@ -62,12 +63,14 @@ export class Channels extends EventTarget {
|
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
async create(name, type = 'text', categoryId = 'general') {
|
|
65
|
-
|
|
66
|
+
if (!this.isOwner()) throw new Error('owner only');
|
|
67
|
+
this.channels = [...this.channels, { id: 'ch-' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6), name, type, categoryId, position: this.channels.length }];
|
|
66
68
|
await this._publish();
|
|
67
69
|
this._emit('updated', { channels: this.channels, categories: this.categories });
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
async rename(id, name) {
|
|
73
|
+
if (!this.isOwner()) throw new Error('owner only');
|
|
71
74
|
this.channels = this.channels.map(c => c.id === id ? { ...c, name } : c);
|
|
72
75
|
await this._publish(); this._emit('updated', { channels: this.channels, categories: this.categories });
|
|
73
76
|
}
|
|
@@ -75,38 +78,45 @@ export class Channels extends EventTarget {
|
|
|
75
78
|
// Per-channel metadata patch — used for topic, voiceMode, and any future
|
|
76
79
|
// server-published channel-scoped configuration. Owner-only.
|
|
77
80
|
async update(id, patch) {
|
|
81
|
+
if (!this.isOwner()) throw new Error('owner only');
|
|
78
82
|
if (!patch || typeof patch !== 'object') return;
|
|
79
83
|
this.channels = this.channels.map(c => c.id === id ? { ...c, ...patch } : c);
|
|
80
84
|
await this._publish(); this._emit('updated', { channels: this.channels, categories: this.categories });
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
async remove(id) {
|
|
88
|
+
if (!this.isOwner()) throw new Error('owner only');
|
|
84
89
|
this.channels = this.channels.filter(c => c.id !== id);
|
|
85
90
|
await this._publish(); this._emit('updated', { channels: this.channels, categories: this.categories });
|
|
86
91
|
}
|
|
87
92
|
|
|
88
93
|
async createCategory(name) {
|
|
89
|
-
|
|
94
|
+
if (!this.isOwner()) throw new Error('owner only');
|
|
95
|
+
this.categories = [...this.categories, { id: 'cat-' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6), name, position: this.categories.length }];
|
|
90
96
|
await this._publish(); this._emit('updated', { channels: this.channels, categories: this.categories });
|
|
91
97
|
}
|
|
92
98
|
|
|
93
99
|
async renameCategory(id, name) {
|
|
100
|
+
if (!this.isOwner()) throw new Error('owner only');
|
|
94
101
|
this.categories = this.categories.map(c => c.id === id ? { ...c, name } : c);
|
|
95
102
|
await this._publish(); this._emit('updated', { channels: this.channels, categories: this.categories });
|
|
96
103
|
}
|
|
97
104
|
|
|
98
105
|
async deleteCategory(id) {
|
|
106
|
+
if (!this.isOwner()) throw new Error('owner only');
|
|
99
107
|
this.categories = this.categories.filter(c => c.id !== id);
|
|
100
108
|
this.channels = this.channels.map(c => c.categoryId === id ? { ...c, categoryId: null } : c);
|
|
101
109
|
await this._publish(); this._emit('updated', { channels: this.channels, categories: this.categories });
|
|
102
110
|
}
|
|
103
111
|
|
|
104
112
|
async reorder(catId, ids) {
|
|
113
|
+
if (!this.isOwner()) throw new Error('owner only');
|
|
105
114
|
ids.forEach((chId, idx) => { this.channels = this.channels.map(c => c.id === chId ? { ...c, position: idx, categoryId: catId } : c); });
|
|
106
115
|
await this._publish(); this._emit('updated', { channels: this.channels, categories: this.categories });
|
|
107
116
|
}
|
|
108
117
|
|
|
109
118
|
async reorderCategories(ids) {
|
|
119
|
+
if (!this.isOwner()) throw new Error('owner only');
|
|
110
120
|
ids.forEach((catId, idx) => { this.categories = this.categories.map(c => c.id === catId ? { ...c, position: idx } : c); });
|
|
111
121
|
await this._publish(); this._emit('updated', { channels: this.channels, categories: this.categories });
|
|
112
122
|
}
|
package/src/chat.js
CHANGED
|
@@ -52,6 +52,11 @@ export class Chat extends EventTarget {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
async deleteMessage(id) {
|
|
55
|
+
const msg = this.messages.find(m => m.id === id);
|
|
56
|
+
if (!msg) return;
|
|
57
|
+
const { serverId } = this.getChannelContext();
|
|
58
|
+
const isAuthor = msg.userId === this.auth.pubkey;
|
|
59
|
+
if (!isAuthor && !this.isAdmin(serverId)) throw new Error('Cannot delete: not author or admin');
|
|
55
60
|
const signed = await this.auth.sign({ kind: 5, created_at: Math.floor(Date.now() / 1000), tags: [['e', id]], content: 'deleted' });
|
|
56
61
|
this.pool.publish(signed);
|
|
57
62
|
this.messages = this.messages.filter(m => m.id !== id);
|
package/src/data.js
CHANGED
|
@@ -34,7 +34,8 @@ export class DataSession extends EventTarget {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
_initActor() {
|
|
37
|
-
const machine = this.fsm.dataMachine
|
|
37
|
+
const machine = this.fsm.dataMachine;
|
|
38
|
+
if (!machine) throw new Error('DataSession: fsm.dataMachine missing');
|
|
38
39
|
this.actor = this.xstate.createActor(machine);
|
|
39
40
|
this.actor.subscribe((snap) => this.dispatchEvent(new CustomEvent('state', { detail: { value: snap.value } })));
|
|
40
41
|
this.actor.start();
|
package/src/message.js
CHANGED
|
@@ -10,7 +10,7 @@ export class MessageBus extends EventTarget {
|
|
|
10
10
|
register(type, fn) { this.handlers[type] = fn; }
|
|
11
11
|
|
|
12
12
|
add(text, { audioData = null, userId = null, username = null } = {}) {
|
|
13
|
-
const msg = { id: Date.now() + Math.random(), text, time: Date.now(), userId, username, audioData };
|
|
13
|
+
const msg = { id: Date.now().toString(36) + Math.random().toString(36).slice(2), text, time: Date.now(), userId, username, audioData };
|
|
14
14
|
this.messages = [...this.messages, msg];
|
|
15
15
|
if (this.messages.length > this.max) this.messages = this.messages.slice(-this.max);
|
|
16
16
|
this.dispatchEvent(new CustomEvent('message', { detail: msg }));
|
package/src/roles.js
CHANGED
|
@@ -6,7 +6,7 @@ export class Roles extends EventTarget {
|
|
|
6
6
|
if (!relayPool || !auth) throw new Error('Roles: deps required');
|
|
7
7
|
this.pool = relayPool; this.auth = auth;
|
|
8
8
|
this.store = new Map();
|
|
9
|
-
this.
|
|
9
|
+
this.subs = new Map();
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
_creatorOf(serverId) { return serverId ? serverId.split(':')[0] : null; }
|
|
@@ -39,12 +39,13 @@ export class Roles extends EventTarget {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
subscribe(serverId) {
|
|
42
|
-
if (this.
|
|
42
|
+
if (this.subs.has(serverId)) return;
|
|
43
43
|
if (!serverId) return;
|
|
44
44
|
const creator = this._creatorOf(serverId);
|
|
45
45
|
if (!creator) return;
|
|
46
|
-
|
|
47
|
-
this.
|
|
46
|
+
const subId = 'roles-' + serverId;
|
|
47
|
+
this.subs.set(serverId, subId);
|
|
48
|
+
this.pool.subscribe(subId,
|
|
48
49
|
[{ kinds: [30078], authors: [creator], '#d': [dtag('roles', serverId)] }],
|
|
49
50
|
(event) => {
|
|
50
51
|
if (event.pubkey !== creator) return;
|
|
@@ -55,6 +56,11 @@ export class Roles extends EventTarget {
|
|
|
55
56
|
} catch {}
|
|
56
57
|
});
|
|
57
58
|
}
|
|
59
|
+
|
|
60
|
+
unsubscribe(serverId) {
|
|
61
|
+
const subId = this.subs.get(serverId);
|
|
62
|
+
if (subId) { this.pool.unsubscribe(subId); this.subs.delete(serverId); }
|
|
63
|
+
}
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
export const createRoles = (opts) => new Roles(opts);
|
package/src/settings.js
CHANGED
|
@@ -9,7 +9,7 @@ export class Settings extends EventTarget {
|
|
|
9
9
|
if (!relayPool || !auth || !roles) throw new Error('Settings: deps required');
|
|
10
10
|
this.pool = relayPool; this.auth = auth; this.roles = roles;
|
|
11
11
|
this.store = new Map();
|
|
12
|
-
this.
|
|
12
|
+
this.subs = new Map();
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
getBitrate(serverId) { return this.store.get(serverId)?.opusBitrate || 24000; }
|
|
@@ -57,18 +57,24 @@ export class Settings extends EventTarget {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
subscribe(serverId) {
|
|
60
|
-
if (this.
|
|
60
|
+
if (this.subs.has(serverId)) return;
|
|
61
61
|
if (!serverId) return;
|
|
62
62
|
const creator = serverId.split(':')[0];
|
|
63
63
|
if (!creator) return;
|
|
64
|
-
|
|
65
|
-
this.
|
|
64
|
+
const subId = 'settings-' + serverId;
|
|
65
|
+
this.subs.set(serverId, subId);
|
|
66
|
+
this.pool.subscribe(subId,
|
|
66
67
|
[{ kinds: [30078], authors: [creator], '#d': [dtag('settings', serverId)] }],
|
|
67
68
|
(event) => {
|
|
68
69
|
if (event.pubkey !== creator) return;
|
|
69
70
|
try { this.store.set(serverId, JSON.parse(event.content)); this.dispatchEvent(new CustomEvent('updated', { detail: { serverId, next: this.store.get(serverId) } })); } catch {}
|
|
70
71
|
});
|
|
71
72
|
}
|
|
73
|
+
|
|
74
|
+
unsubscribe(serverId) {
|
|
75
|
+
const subId = this.subs.get(serverId);
|
|
76
|
+
if (subId) { this.pool.unsubscribe(subId); this.subs.delete(serverId); }
|
|
77
|
+
}
|
|
72
78
|
}
|
|
73
79
|
|
|
74
80
|
export const createSettings = (opts) => new Settings(opts);
|