wireweave 0.1.2 → 0.1.3

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.
@@ -1,152 +0,0 @@
1
- var serverPages = {
2
- _store: new Map(),
3
- _subs: new Map(),
4
-
5
- _sanitize(html) {
6
- const el = document.createElement('div');
7
- el.innerHTML = html;
8
- el.querySelectorAll('script,iframe,object,embed,form,input,button').forEach(n => n.remove());
9
- el.querySelectorAll('*').forEach(n => {
10
- [...n.attributes].forEach(a => {
11
- if (/^on/i.test(a.name) || (a.name === 'href' && /^javascript:/i.test(a.value)) || (a.name === 'src' && /^javascript:/i.test(a.value))) n.removeAttribute(a.name);
12
- });
13
- });
14
- return el.innerHTML;
15
- },
16
-
17
- _pageKey(serverId, slug) { return 'zellous-page:' + serverId + ':' + slug; },
18
-
19
- getPages(serverId) { return Array.from((serverPages._store.get(serverId) || new Map()).values()); },
20
-
21
- subscribe(serverId) {
22
- if (serverPages._subs.has(serverId)) return;
23
- var creator = serverId ? serverId.split(':')[0] : null;
24
- if (!creator) return;
25
- var subId = 'pages-' + serverId;
26
- serverPages._subs.set(serverId, subId);
27
- nostrNet.subscribe(subId,
28
- [{ kinds: [30078], authors: [creator] }],
29
- function(event) {
30
- if (event.pubkey !== creator) return;
31
- var dTag = (event.tags.find(function(t) { return t[0] === 'd'; }) || [])[1] || '';
32
- var prefix = 'zellous-page:' + serverId + ':';
33
- if (!dTag.startsWith(prefix)) return;
34
- var slug = dTag.slice(prefix.length);
35
- if (!slug) return;
36
- try {
37
- var data = JSON.parse(event.content);
38
- var pages = serverPages._store.get(serverId) || new Map();
39
- if (data.deleted) { pages.delete(slug); } else {
40
- pages.set(slug, { slug: slug, title: data.title || slug, html: serverPages._sanitize(data.html || ''), updatedAt: event.created_at });
41
- }
42
- serverPages._store.set(serverId, pages);
43
- if (window.uiChannels) uiChannels.render();
44
- } catch(e) {}
45
- },
46
- function() {}
47
- );
48
- },
49
-
50
- unsubscribe(serverId) {
51
- var subId = serverPages._subs.get(serverId);
52
- if (subId) { nostrNet.unsubscribe(subId); serverPages._subs.delete(serverId); }
53
- },
54
-
55
- async publish(serverId, slug, title, html) {
56
- if (!serverRoles.isAdmin(serverId)) throw new Error('Admin only');
57
- var safe = serverPages._sanitize(html);
58
- var dTag = serverPages._pageKey(serverId, slug);
59
- var signed = await auth.sign({ kind: 30078, created_at: Math.floor(Date.now() / 1000), tags: [['d', dTag]], content: JSON.stringify({ title: title, html: safe }) });
60
- await nostrNet.publish(signed);
61
- var pages = serverPages._store.get(serverId) || new Map();
62
- pages.set(slug, { slug: slug, title: title, html: safe, updatedAt: Math.floor(Date.now() / 1000) });
63
- serverPages._store.set(serverId, pages);
64
- if (window.uiChannels) uiChannels.render();
65
- },
66
-
67
- async deletePage(serverId, slug) {
68
- if (!serverRoles.isAdmin(serverId)) throw new Error('Admin only');
69
- var dTag = serverPages._pageKey(serverId, slug);
70
- var signed = await auth.sign({ kind: 30078, created_at: Math.floor(Date.now() / 1000), tags: [['d', dTag]], content: JSON.stringify({ deleted: true }) });
71
- await nostrNet.publish(signed);
72
- var pages = serverPages._store.get(serverId) || new Map();
73
- pages.delete(slug);
74
- serverPages._store.set(serverId, pages);
75
- if (window.uiChannels) uiChannels.render();
76
- },
77
-
78
- showEditModal(serverId, existing) {
79
- document.getElementById('pageEditModal')?.remove();
80
- var modal = document.createElement('div');
81
- modal.id = 'pageEditModal';
82
- modal.className = 'modal-overlay';
83
- modal.style.display = 'flex';
84
- var slug = existing ? existing.slug : 'page-' + Date.now().toString(36);
85
- modal.innerHTML = '<div class="modal" style="max-width:680px;width:95%">' +
86
- '<div class="modal-header"><span class="modal-title">' + (existing ? 'Edit Page' : 'New Page') + '</span>' +
87
- '<button class="modal-close" id="pageModalClose">&times;</button></div>' +
88
- '<div class="modal-body" style="display:flex;flex-direction:column;gap:12px">' +
89
- '<input id="pageModalTitle" class="modal-input" placeholder="Page title" value="' + escHtml(existing ? existing.title : '') + '">' +
90
- '<div id="pageModalEditor" class="page-editor" contenteditable="true" style="min-height:220px;background:var(--bg-raised);border:1px solid var(--border);border-radius:6px;padding:12px;overflow-y:auto;font-size:14px;line-height:1.6"></div>' +
91
- '<div class="page-editor-toolbar" style="display:flex;gap:6px;flex-wrap:wrap">' +
92
- '<button class="page-tb-btn" data-cmd="bold"><b>B</b></button>' +
93
- '<button class="page-tb-btn" data-cmd="italic"><i>I</i></button>' +
94
- '<button class="page-tb-btn" data-cmd="underline"><u>U</u></button>' +
95
- '<button class="page-tb-btn" data-cmd="insertUnorderedList">&#8226;</button>' +
96
- '<button class="page-tb-btn" data-cmd="insertOrderedList">1.</button>' +
97
- '</div>' +
98
- '</div>' +
99
- '<div class="modal-footer">' +
100
- (existing ? '<button class="modal-btn danger" id="pageModalDelete">Delete</button>' : '') +
101
- '<button class="modal-btn secondary" id="pageModalCancel">Cancel</button>' +
102
- '<button class="modal-btn primary" id="pageModalSave">Save</button>' +
103
- '</div></div>';
104
- document.body.appendChild(modal);
105
- var editor = document.getElementById('pageModalEditor');
106
- if (existing) editor.innerHTML = existing.html;
107
- modal.querySelectorAll('.page-tb-btn').forEach(function(btn) {
108
- btn.addEventListener('mousedown', function(e) { e.preventDefault(); document.execCommand(btn.dataset.cmd, false, null); editor.focus(); });
109
- });
110
- document.getElementById('pageModalClose').addEventListener('click', function() { modal.remove(); });
111
- document.getElementById('pageModalCancel').addEventListener('click', function() { modal.remove(); });
112
- if (existing) {
113
- document.getElementById('pageModalDelete').addEventListener('click', async function() {
114
- if (!confirm('Delete this page?')) return;
115
- try { await serverPages.deletePage(serverId, slug); modal.remove(); } catch(e) { alert(e.message); }
116
- });
117
- }
118
- document.getElementById('pageModalSave').addEventListener('click', async function() {
119
- var title = document.getElementById('pageModalTitle').value.trim();
120
- if (!title) { alert('Title required'); return; }
121
- try { await serverPages.publish(serverId, slug, title, editor.innerHTML); modal.remove(); } catch(e) { alert(e.message); }
122
- });
123
- modal.addEventListener('click', function(e) { if (e.target === modal) modal.remove(); });
124
- },
125
-
126
- renderPageView(serverId, slug) {
127
- var pages = serverPages._store.get(serverId) || new Map();
128
- var page = pages.get(slug);
129
- var area = document.getElementById('pageView');
130
- if (!area) return;
131
- var isAdmin = window.serverRoles && serverRoles.isAdmin(serverId);
132
- area.innerHTML = '<div class="page-view-header">' +
133
- '<span class="page-view-title">' + escHtml(page ? page.title : slug) + '</span>' +
134
- (isAdmin ? '<button class="modal-btn secondary page-edit-btn" id="pageEditBtn">Edit</button>' : '') +
135
- '</div>' +
136
- '<div class="page-view-body">' + (page ? page.html : '<p style="color:var(--text-muted)">No content yet.</p>') + '</div>';
137
- if (isAdmin) {
138
- document.getElementById('pageEditBtn').addEventListener('click', function() {
139
- serverPages.showEditModal(serverId, page || { slug: slug, title: slug, html: '' });
140
- });
141
- }
142
- }
143
- };
144
-
145
- window.__zellous.pages = serverPages;
146
- window.serverPages = serverPages;
147
- if (!window.__debug) window.__debug = {};
148
- Object.defineProperty(window.__debug, 'pages', { get: function() {
149
- var out = {};
150
- serverPages._store.forEach(function(pages, srv) { out[srv] = Array.from(pages.keys()); });
151
- return out;
152
- }, configurable: true });
@@ -1,74 +0,0 @@
1
- var serverRoles = {
2
- _store: new Map(),
3
- _sub: null,
4
-
5
- _creatorOf(serverId) {
6
- return serverId ? serverId.split(':')[0] : null;
7
- },
8
-
9
- isOwner(serverId) {
10
- return !!state.nostrPubkey && serverRoles._creatorOf(serverId) === state.nostrPubkey;
11
- },
12
-
13
- isAdmin(serverId) {
14
- if (serverRoles.isOwner(serverId)) return true;
15
- var roles = serverRoles._store.get(serverId) || {};
16
- return !!(roles.admins || []).includes(state.nostrPubkey);
17
- },
18
-
19
- isMod(serverId) {
20
- if (serverRoles.isAdmin(serverId)) return true;
21
- var roles = serverRoles._store.get(serverId) || {};
22
- return !!(roles.mods || []).includes(state.nostrPubkey);
23
- },
24
-
25
- getRole(serverId, pubkey) {
26
- var creator = serverRoles._creatorOf(serverId);
27
- if (pubkey === creator) return 'owner';
28
- var roles = serverRoles._store.get(serverId) || {};
29
- if ((roles.admins || []).includes(pubkey)) return 'admin';
30
- if ((roles.mods || []).includes(pubkey)) return 'moderator';
31
- return 'member';
32
- },
33
-
34
- async setRole(serverId, targetPubkey, role) {
35
- if (!serverRoles.isOwner(serverId) && role === 'admin') throw new Error('Only owner can assign admin');
36
- if (!serverRoles.isAdmin(serverId)) throw new Error('Insufficient permissions');
37
- var existing = serverRoles._store.get(serverId) || { admins: [], mods: [] };
38
- var admins = (existing.admins || []).filter(function(p) { return p !== targetPubkey; });
39
- var mods = (existing.mods || []).filter(function(p) { return p !== targetPubkey; });
40
- if (role === 'admin') admins = admins.concat([targetPubkey]);
41
- else if (role === 'moderator') mods = mods.concat([targetPubkey]);
42
- var next = { admins: admins, mods: mods };
43
- serverRoles._store.set(serverId, next);
44
- var dTag = 'zellous-roles:' + serverId;
45
- var signed = await auth.sign({ kind: 30078, created_at: Math.floor(Date.now() / 1000), tags: [['d', dTag]], content: JSON.stringify(next) });
46
- await nostrNet.publish(signed);
47
- },
48
-
49
- subscribe(serverId) {
50
- if (serverRoles._sub) { nostrNet.unsubscribe(serverRoles._sub); serverRoles._sub = null; }
51
- if (!serverId) return;
52
- var creator = serverRoles._creatorOf(serverId);
53
- if (!creator) return;
54
- var dTag = 'zellous-roles:' + serverId;
55
- serverRoles._sub = 'roles-' + serverId;
56
- nostrNet.subscribe(serverRoles._sub,
57
- [{ kinds: [30078], authors: [creator], '#d': [dTag] }],
58
- function(event) {
59
- if (event.pubkey !== creator) return;
60
- try {
61
- var data = JSON.parse(event.content);
62
- serverRoles._store.set(serverId, { admins: data.admins || [], mods: data.mods || [] });
63
- if (window.uiMembers) uiMembers.render();
64
- } catch(e) {}
65
- },
66
- function() {}
67
- );
68
- }
69
- };
70
-
71
- window.__zellous.roles = serverRoles;
72
- window.serverRoles = serverRoles;
73
- if (!window.__debug) window.__debug = {};
74
- Object.defineProperty(window.__debug, 'roles', { get: function() { return { store: Object.fromEntries(serverRoles._store), sub: serverRoles._sub }; }, configurable: true });
@@ -1,223 +0,0 @@
1
- var serverManager = {
2
- loadServers: function() {
3
- try {
4
- var raw = localStorage.getItem('zn_servers');
5
- state.servers = raw ? JSON.parse(raw) : [];
6
- } catch (e) { state.servers = []; }
7
-
8
- if (state.nostrPubkey) {
9
- nostrNet.subscribe(
10
- 'my-servers',
11
- [{ kinds: [34550], authors: [state.nostrPubkey] }],
12
- function(event) { serverManager._handleServerEvent(event); },
13
- function() {}
14
- );
15
- }
16
-
17
- try {
18
- var joined = localStorage.getItem('zn_joined_servers');
19
- var joinedIds = joined ? JSON.parse(joined) : [];
20
- joinedIds.forEach(function(sid) {
21
- if (!state.servers.find(function(s) { return s.id === sid; })) {
22
- state.servers = state.servers.concat([{ id: sid, name: sid.slice(0, 8), iconColor: '#5865F2' }]);
23
- }
24
- });
25
- // Subscribe to relay to fetch real names for joined (non-owned) servers
26
- var unresolved = joinedIds.filter(function(sid) {
27
- // Format: pubkey:dTag — only subscribe if not already owned by us
28
- var parts = sid.split(':');
29
- return parts.length === 2 && parts[0] !== state.nostrPubkey;
30
- });
31
- if (unresolved.length) {
32
- // Group by author to minimise subscriptions
33
- var byAuthor = {};
34
- unresolved.forEach(function(sid) {
35
- var parts = sid.split(':');
36
- var author = parts[0], dTag = parts[1];
37
- if (!byAuthor[author]) byAuthor[author] = [];
38
- byAuthor[author].push(dTag);
39
- });
40
- Object.keys(byAuthor).forEach(function(author) {
41
- var dTags = byAuthor[author];
42
- nostrNet.subscribe(
43
- 'joined-server-' + author.slice(0, 8),
44
- [{ kinds: [34550], authors: [author], '#d': dTags }],
45
- function(event) { serverManager._handleServerEvent(event); },
46
- function() {}
47
- );
48
- });
49
- }
50
- } catch (e) {}
51
- },
52
-
53
- _handleServerEvent: function(event) {
54
- var nameTag = event.tags.find(function(t) { return t[0] === 'name'; });
55
- var colorTag = event.tags.find(function(t) { return t[0] === 'color'; });
56
- var dTag = event.tags.find(function(t) { return t[0] === 'd'; });
57
- if (!dTag) return;
58
- var serverId = event.pubkey + ':' + dTag[1];
59
- var name = nameTag ? nameTag[1] : serverId.slice(0, 8);
60
- var iconColor = colorTag ? colorTag[1] : '#5865F2';
61
- var existing = state.servers.find(function(s) { return s.id === serverId; });
62
- if (existing) {
63
- existing.name = name; existing.iconColor = iconColor; existing.ownerId = event.pubkey;
64
- state.servers = state.servers.slice();
65
- } else {
66
- state.servers = state.servers.concat([{ id: serverId, name: name, iconColor: iconColor, ownerId: event.pubkey }]);
67
- }
68
- serverManager._persistServers();
69
- ui.render.all();
70
- },
71
-
72
- create: async function(name, iconColor) {
73
- var dTag = Math.random().toString(36).slice(2, 10);
74
- var serverId = state.nostrPubkey + ':' + dTag;
75
- var template = { kind: 34550, created_at: Math.floor(Date.now() / 1000), tags: [['d', dTag], ['name', name], ['color', iconColor || '#5865F2']], content: '' };
76
- var signed = await auth.sign(template);
77
- await nostrNet.publish(signed);
78
- var server = { id: serverId, name: name, iconColor: iconColor || '#5865F2', ownerId: state.nostrPubkey };
79
- state.servers = state.servers.concat([server]);
80
- serverManager._persistServers();
81
- serverManager.switchTo(serverId);
82
- },
83
-
84
- join: async function(serverId) {
85
- try {
86
- var joined = JSON.parse(localStorage.getItem('zn_joined_servers') || '[]');
87
- if (!joined.includes(serverId)) { joined.push(serverId); localStorage.setItem('zn_joined_servers', JSON.stringify(joined)); }
88
- } catch (e) {}
89
- if (!state.servers.find(function(s) { return s.id === serverId; })) {
90
- state.servers = state.servers.concat([{ id: serverId, name: serverId.slice(0, 8), iconColor: '#5865F2' }]);
91
- serverManager._persistServers();
92
- }
93
- serverManager.switchTo(serverId);
94
- },
95
-
96
- delete: async function(serverId) {
97
- state.servers = (state.servers || []).filter(function(s) { return s.id !== serverId; });
98
- serverManager._persistServers();
99
- try {
100
- var joined = JSON.parse(localStorage.getItem('zn_joined_servers') || '[]');
101
- localStorage.setItem('zn_joined_servers', JSON.stringify(joined.filter(function(id) { return id !== serverId; })));
102
- } catch (e) {}
103
- if (state.currentServerId === serverId) serverManager.switchTo(null);
104
- else ui.render.all();
105
- },
106
-
107
- leave: function(serverId) { serverManager.delete(serverId); },
108
- switchTo: async function(serverId) {
109
- state.currentServerId = serverId;
110
- state.chatMessages = []; state.channels = []; state.categories = [];
111
- if (window.serverRoles) serverRoles.subscribe(serverId);
112
- if (window.serverSettings) serverSettings.subscribe(serverId);
113
- if (window.serverPages) serverPages.subscribe(serverId);
114
- if (serverId) localStorage.setItem('zn_lastServer', serverId);
115
- else localStorage.removeItem('zn_lastServer');
116
- if (window.channelManager && serverId) {
117
- await new Promise(function(resolve) {
118
- var done = false;
119
- var finish = function() { if (done) return; done = true; if (!(state.channels || []).length) channelManager._setDefaults(); resolve(); };
120
- channelManager.loadChannels(serverId, finish);
121
- setTimeout(finish, 3000);
122
- });
123
- }
124
- var firstText = (state.channels || []).find(function(c) { return c.type === 'text'; });
125
- if (firstText && state.currentChannelId !== firstText.id) {
126
- state.currentChannelId = firstText.id; state.currentChannel = firstText;
127
- if (window.chat) chat.loadHistory(firstText.id);
128
- }
129
- ui.render.all();
130
- },
131
- _persistServers: function() { localStorage.setItem('zn_servers', JSON.stringify(state.servers)); },
132
- init: function() {
133
- serverManager.loadServers();
134
- var lastServer = localStorage.getItem('zn_lastServer');
135
- if (lastServer && state.servers.find(function(s) { return s.id === lastServer; })) serverManager.switchTo(lastServer);
136
- else if (state.servers.length) serverManager.switchTo(state.servers[0].id);
137
- },
138
-
139
- _getOrder: function() { try { return JSON.parse(localStorage.getItem('zn_serverOrder') || '[]'); } catch(e) { return []; } },
140
- _saveOrder: function(ids) { localStorage.setItem('zn_serverOrder', JSON.stringify(ids)); },
141
-
142
- _sortedServers: function() {
143
- var srvs = state.servers || [];
144
- var order = serverManager._getOrder();
145
- if (!order.length) return srvs;
146
- var indexed = {};
147
- order.forEach(function(id, i) { indexed[id] = i; });
148
- return srvs.slice().sort(function(a, b) {
149
- var ai = indexed[a.id] !== undefined ? indexed[a.id] : Infinity;
150
- var bi = indexed[b.id] !== undefined ? indexed[b.id] : Infinity;
151
- return ai - bi;
152
- });
153
- },
154
-
155
- renderList: function() {
156
- var container = document.getElementById('serverIcons');
157
- if (!container) return;
158
- var srvs = serverManager._sortedServers();
159
- var current = state.currentServerId;
160
- var currentServer = current && srvs.find(function(s) { return s.id === current; });
161
- var headerEl = document.getElementById('serverHeaderName');
162
- if (headerEl) headerEl.textContent = currentServer ? currentServer.name : 'Zellous';
163
- var colors = ['#5865f2', '#57f287', '#feb347', '#fe7168', '#9b59b6', '#1abc9c', '#e67e22', '#e74c3c'];
164
- var html = '';
165
- srvs.forEach(function(s) {
166
- var active = s.id === current ? ' active' : '';
167
- var color = s.iconColor || colors[s.name.length % colors.length];
168
- var initial = (s.name || '?')[0].toUpperCase();
169
- html += '<div class="server-icon' + active + '" draggable="true" data-server="' + s.id + '" title="' + s.name + '" style="background:' + (active ? '' : color) + '">' +
170
- '<div class="server-pill"></div>' + initial + '</div>';
171
- });
172
- html += '<div class="server-separator" id="serverSeparator"></div>' +
173
- '<div class="server-icon add-server" id="addServerBtn" title="Add a Server"><div class="server-pill"></div>+</div>';
174
- container.innerHTML = html;
175
- var homeIcon = document.getElementById('homeServer');
176
- if (homeIcon) {
177
- homeIcon.classList.toggle('active', !current);
178
- homeIcon.addEventListener('click', function() { serverManager.switchTo(null); });
179
- }
180
- container.querySelectorAll('[data-server]').forEach(function(el) {
181
- el.addEventListener('click', function() { serverManager.switchTo(el.dataset.server); });
182
- el.addEventListener('contextmenu', function(e) { e.preventDefault(); serverManager.showContextMenu(el.dataset.server, e.clientX, e.clientY); });
183
- });
184
- var addBtn = document.getElementById('addServerBtn');
185
- if (addBtn) addBtn.addEventListener('click', function() { serverManager.showCreateModal(); });
186
- serverManager._initDragDrop(container);
187
- },
188
-
189
- _initDragDrop: function(container) {
190
- var dragId = null;
191
- container.addEventListener('dragstart', function(e) {
192
- var el = e.target.closest('[data-server]');
193
- if (!el) return;
194
- dragId = el.dataset.server; e.dataTransfer.effectAllowed = 'move';
195
- setTimeout(function() { el.classList.add('dragging'); }, 0);
196
- });
197
- container.addEventListener('dragend', function() {
198
- container.querySelectorAll('.dragging').forEach(function(el) { el.classList.remove('dragging'); });
199
- container.querySelectorAll('.drag-over').forEach(function(el) { el.classList.remove('drag-over'); });
200
- dragId = null;
201
- });
202
- container.addEventListener('dragover', function(e) {
203
- e.preventDefault(); e.dataTransfer.dropEffect = 'move';
204
- var target = e.target.closest('[data-server]');
205
- container.querySelectorAll('.drag-over').forEach(function(el) { el.classList.remove('drag-over'); });
206
- if (target && target.dataset.server !== dragId) target.classList.add('drag-over');
207
- });
208
- container.addEventListener('drop', function(e) {
209
- e.preventDefault();
210
- var target = e.target.closest('[data-server]');
211
- if (!target || !dragId || target.dataset.server === dragId) return;
212
- var srvs = serverManager._sortedServers();
213
- var ids = srvs.map(function(s) { return s.id; });
214
- var fromIdx = ids.indexOf(dragId), toIdx = ids.indexOf(target.dataset.server);
215
- if (fromIdx === -1 || toIdx === -1) return;
216
- ids.splice(fromIdx, 1); ids.splice(toIdx, 0, dragId);
217
- serverManager._saveOrder(ids); serverManager.renderList();
218
- });
219
- }
220
- };
221
-
222
- window.__zellous.servers = serverManager;
223
- window.serverManager = serverManager;
@@ -1,97 +0,0 @@
1
- var serverSettings = {
2
- _store: new Map(),
3
- _sub: null,
4
- _VALID_BITRATES: [8000, 16000, 24000, 48000, 96000],
5
-
6
- _clampBitrate(v) {
7
- return serverSettings._VALID_BITRATES.reduce(function(prev, cur) {
8
- return Math.abs(cur - v) < Math.abs(prev - v) ? cur : prev;
9
- });
10
- },
11
-
12
- getBitrate(serverId) {
13
- var s = serverSettings._store.get(serverId);
14
- return (s && s.opusBitrate) || 24000;
15
- },
16
-
17
- async setBitrate(serverId, bitrate) {
18
- if (!serverRoles.isOwner(serverId) && !serverRoles.isAdmin(serverId)) throw new Error('Insufficient permissions');
19
- var clamped = serverSettings._clampBitrate(Number(bitrate));
20
- var existing = serverSettings._store.get(serverId) || {};
21
- var next = Object.assign({}, existing, { opusBitrate: clamped });
22
- serverSettings._store.set(serverId, next);
23
- var dTag = 'zellous-settings:' + serverId;
24
- var signed = await auth.sign({ kind: 30078, created_at: Math.floor(Date.now() / 1000), tags: [['d', dTag]], content: JSON.stringify(next) });
25
- await nostrNet.publish(signed);
26
- if (state.audioEncoder) {
27
- try { state.audioEncoder.configure({ codec: 'opus', sampleRate: config.sampleRate, numberOfChannels: 1, bitrate: clamped }); } catch(e) {}
28
- }
29
- return clamped;
30
- },
31
-
32
- getEmbedAllowlist(serverId) {
33
- var s = serverSettings._store.get(serverId);
34
- return (s && s.embedAllowlist) ? (typeof s.embedAllowlist === 'string' ? s.embedAllowlist.split(',').map(function(d) { return d.trim(); }).filter(Boolean) : s.embedAllowlist) : [];
35
- },
36
-
37
- async setEmbedAllowlist(serverId, domainsStr) {
38
- if (!serverRoles.isOwner(serverId) && !serverRoles.isAdmin(serverId)) throw new Error('Insufficient permissions');
39
- var domains = domainsStr.split(',').map(function(d) { return d.trim(); }).filter(Boolean);
40
- var existing = serverSettings._store.get(serverId) || {};
41
- var next = Object.assign({}, existing, { embedAllowlist: domains });
42
- serverSettings._store.set(serverId, next);
43
- var dTag = 'zellous-settings:' + serverId;
44
- var signed = await auth.sign({ kind: 30078, created_at: Math.floor(Date.now() / 1000), tags: [['d', dTag]], content: JSON.stringify(next) });
45
- await nostrNet.publish(signed);
46
- return domains;
47
- },
48
-
49
- isOriginAllowed(serverId, origin) {
50
- if (!origin) return false;
51
- var allowlist = serverSettings.getEmbedAllowlist(serverId);
52
- if (allowlist.length === 0) return true;
53
- var url = {};
54
- try { url = new URL(origin); } catch(e) { return false; }
55
- var hostname = url.hostname;
56
- return allowlist.some(function(pattern) {
57
- if (pattern === '*') return true;
58
- if (pattern.startsWith('*.')) {
59
- var suffix = pattern.slice(2);
60
- return hostname === suffix || hostname.endsWith('.' + suffix);
61
- }
62
- return hostname === pattern || hostname === pattern.replace('https://', '').replace('http://', '');
63
- });
64
- },
65
-
66
- subscribe(serverId) {
67
- if (serverSettings._sub) { nostrNet.unsubscribe(serverSettings._sub); serverSettings._sub = null; }
68
- if (!serverId) return;
69
- var creator = serverId.split(':')[0];
70
- if (!creator) return;
71
- var dTag = 'zellous-settings:' + serverId;
72
- serverSettings._sub = 'settings-' + serverId;
73
- nostrNet.subscribe(serverSettings._sub,
74
- [{ kinds: [30078], authors: [creator], '#d': [dTag] }],
75
- function(event) {
76
- if (event.pubkey !== creator) return;
77
- try {
78
- var data = JSON.parse(event.content);
79
- serverSettings._store.set(serverId, data);
80
- } catch(e) {}
81
- },
82
- function() {}
83
- );
84
- },
85
-
86
- applyToEncoder() {
87
- var bitrate = serverSettings.getBitrate(state.currentServerId);
88
- if (state.audioEncoder) {
89
- try { state.audioEncoder.configure({ codec: 'opus', sampleRate: config.sampleRate, numberOfChannels: 1, bitrate: bitrate }); } catch(e) {}
90
- }
91
- }
92
- };
93
-
94
- window.__zellous.settings = serverSettings;
95
- window.serverSettings = serverSettings;
96
- if (!window.__debug) window.__debug = {};
97
- Object.defineProperty(window.__debug, 'settings', { get: function() { return { store: Object.fromEntries(serverSettings._store), sub: serverSettings._sub }; }, configurable: true });
@@ -1,19 +0,0 @@
1
- (() => {
2
- const sig = window.__signal;
3
- const raw = window.stateSignals;
4
-
5
- raw.nostrPubkey = sig('');
6
- raw.nostrPrivkey = sig(null);
7
- raw.nostrRelays = sig([
8
- 'wss://relay.damus.io',
9
- 'wss://relay.primal.net',
10
- 'wss://relay.snort.social',
11
- 'wss://nos.lol',
12
- 'wss://relay.olas.app',
13
- 'wss://nostr.mom'
14
- ]);
15
- raw.nostrRelayStatus = sig(new Map());
16
- raw.nostrProfile = sig(null);
17
- raw.nostrSubscriptions = sig(new Map());
18
- raw.currentChannelId = sig(null);
19
- })();
@@ -1,63 +0,0 @@
1
- var nostrVoiceCamera = {
2
- _fsm: null,
3
-
4
- _initFSM: function() {
5
- var actor = XState.createActor(nostrFsm.cameraMachine);
6
- actor.subscribe(function(snap) {
7
- var state_val = snap.value;
8
- document.getElementById('voiceCamBtn')?.classList.toggle('active', state_val === 'active');
9
- });
10
- actor.start();
11
- nostrVoiceCamera._fsm = actor;
12
- },
13
-
14
- toggle() {
15
- if (!nostrVoiceCamera._fsm) nostrVoiceCamera._initFSM();
16
- var snap = nostrVoiceCamera._fsm.getSnapshot();
17
- if (snap.matches('active')) {
18
- nostrVoiceCamera.stop();
19
- } else if (snap.matches('idle')) {
20
- nostrVoiceCamera.start();
21
- }
22
- },
23
-
24
- async start() {
25
- if (!nostrVoiceCamera._fsm) nostrVoiceCamera._initFSM();
26
- var snap = nostrVoiceCamera._fsm.getSnapshot();
27
- if (!snap.can({type:'enable'})) return;
28
- nostrVoiceCamera._fsm.send({type:'enable'});
29
- state.cameraEnabled = true;
30
- if (nostrVoice._cameraStream) {
31
- var track = nostrVoice._cameraStream.getVideoTracks()[0];
32
- nostrVoice._peers.forEach(function(peer) {
33
- var sender = peer.pc.getSenders().find(function(s) { return s.track && s.track.kind === 'video'; });
34
- if (sender) { sender.replaceTrack(track).catch(function(){}); }
35
- else { peer.pc.addTrack(track, nostrVoice._cameraStream); }
36
- });
37
- var local = nostrVoice._participants.get('local');
38
- if (local) local.hasVideo = true;
39
- nostrVoiceCamera._fsm.send({type:'enabled'});
40
- nostrVoice.updateParticipants();
41
- } else {
42
- nostrVoiceCamera._fsm.send({type:'denied'});
43
- state.cameraEnabled = false;
44
- }
45
- },
46
-
47
- stop() {
48
- if (!nostrVoiceCamera._fsm) nostrVoiceCamera._initFSM();
49
- if (!nostrVoiceCamera._fsm.getSnapshot().can({type:'disable'})) return;
50
- nostrVoiceCamera._fsm.send({type:'disable'});
51
- state.cameraEnabled = false;
52
- nostrVoice._peers.forEach(function(peer) {
53
- var sender = peer.pc.getSenders().find(function(s) { return s.track && s.track.kind === 'video'; });
54
- if (sender) { sender.replaceTrack(null).catch(function(){}); }
55
- });
56
- var local = nostrVoice._participants.get('local');
57
- if (local) local.hasVideo = false;
58
- nostrVoice.updateParticipants();
59
- }
60
- };
61
-
62
- window.__zellous.voiceCamera = nostrVoiceCamera;
63
- window.nostrVoiceCamera = nostrVoiceCamera;