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.
- package/README.md +46 -35
- package/package.json +11 -4
- package/src/bans.js +45 -0
- package/src/channels.js +107 -0
- package/src/index.js +9 -0
- package/src/media.js +81 -0
- package/src/message.js +22 -0
- package/src/pages.js +77 -0
- package/src/roles.js +58 -0
- package/src/servers.js +103 -0
- package/src/settings.js +72 -0
- package/src/wireweave.js +89 -0
- package/src/legacy/README.md +0 -71
- package/src/legacy/index.js +0 -49
- package/src/legacy/nostr-auth.js +0 -217
- package/src/legacy/nostr-bans.js +0 -59
- package/src/legacy/nostr-channels.js +0 -145
- package/src/legacy/nostr-chat.js +0 -185
- package/src/legacy/nostr-fsm.js +0 -5
- package/src/legacy/nostr-media.js +0 -97
- package/src/legacy/nostr-message.js +0 -12
- package/src/legacy/nostr-network.js +0 -182
- package/src/legacy/nostr-pages.js +0 -152
- package/src/legacy/nostr-roles.js +0 -74
- package/src/legacy/nostr-servers.js +0 -223
- package/src/legacy/nostr-settings.js +0 -97
- package/src/legacy/nostr-state-patch.js +0 -19
- package/src/legacy/nostr-voice-camera.js +0 -63
- package/src/legacy/nostr-voice-rtc.js +0 -199
- package/src/legacy/nostr-voice-sfu.js +0 -128
- package/src/legacy/nostr-voice.js +0 -257
package/src/settings.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const VALID_BITRATES = [8000, 16000, 24000, 48000, 96000];
|
|
2
|
+
const clampBitrate = (v) => VALID_BITRATES.reduce((prev, cur) => Math.abs(cur - v) < Math.abs(prev - v) ? cur : prev);
|
|
3
|
+
|
|
4
|
+
export class Settings extends EventTarget {
|
|
5
|
+
constructor({ relayPool, auth, roles }) {
|
|
6
|
+
super();
|
|
7
|
+
if (!relayPool || !auth || !roles) throw new Error('Settings: deps required');
|
|
8
|
+
this.pool = relayPool; this.auth = auth; this.roles = roles;
|
|
9
|
+
this.store = new Map();
|
|
10
|
+
this.sub = null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
getBitrate(serverId) { return this.store.get(serverId)?.opusBitrate || 24000; }
|
|
14
|
+
|
|
15
|
+
async setBitrate(serverId, bitrate) {
|
|
16
|
+
if (!this.roles.isOwner(serverId) && !this.roles.isAdmin(serverId)) throw new Error('Insufficient permissions');
|
|
17
|
+
const clamped = clampBitrate(Number(bitrate));
|
|
18
|
+
const existing = this.store.get(serverId) || {};
|
|
19
|
+
const next = { ...existing, opusBitrate: clamped };
|
|
20
|
+
this.store.set(serverId, next);
|
|
21
|
+
const signed = await this.auth.sign({ kind: 30078, created_at: Math.floor(Date.now() / 1000), tags: [['d', 'zellous-settings:' + serverId]], content: JSON.stringify(next) });
|
|
22
|
+
this.pool.publish(signed);
|
|
23
|
+
this.dispatchEvent(new CustomEvent('updated', { detail: { serverId, next } }));
|
|
24
|
+
return clamped;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
getEmbedAllowlist(serverId) {
|
|
28
|
+
const s = this.store.get(serverId);
|
|
29
|
+
if (!s?.embedAllowlist) return [];
|
|
30
|
+
return typeof s.embedAllowlist === 'string' ? s.embedAllowlist.split(',').map(d => d.trim()).filter(Boolean) : s.embedAllowlist;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async setEmbedAllowlist(serverId, domainsStr) {
|
|
34
|
+
if (!this.roles.isOwner(serverId) && !this.roles.isAdmin(serverId)) throw new Error('Insufficient permissions');
|
|
35
|
+
const domains = domainsStr.split(',').map(d => d.trim()).filter(Boolean);
|
|
36
|
+
const existing = this.store.get(serverId) || {};
|
|
37
|
+
const next = { ...existing, embedAllowlist: domains };
|
|
38
|
+
this.store.set(serverId, next);
|
|
39
|
+
const signed = await this.auth.sign({ kind: 30078, created_at: Math.floor(Date.now() / 1000), tags: [['d', 'zellous-settings:' + serverId]], content: JSON.stringify(next) });
|
|
40
|
+
this.pool.publish(signed);
|
|
41
|
+
this.dispatchEvent(new CustomEvent('updated', { detail: { serverId, next } }));
|
|
42
|
+
return domains;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
isOriginAllowed(serverId, origin) {
|
|
46
|
+
if (!origin) return false;
|
|
47
|
+
const allowlist = this.getEmbedAllowlist(serverId);
|
|
48
|
+
if (!allowlist.length) return true;
|
|
49
|
+
let url; try { url = new URL(origin); } catch { return false; }
|
|
50
|
+
return allowlist.some(p => {
|
|
51
|
+
if (p === '*') return true;
|
|
52
|
+
if (p.startsWith('*.')) { const s = p.slice(2); return url.hostname === s || url.hostname.endsWith('.' + s); }
|
|
53
|
+
return url.hostname === p || url.hostname === p.replace(/^https?:\/\//, '');
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
subscribe(serverId) {
|
|
58
|
+
if (this.sub) { this.pool.unsubscribe(this.sub); this.sub = null; }
|
|
59
|
+
if (!serverId) return;
|
|
60
|
+
const creator = serverId.split(':')[0];
|
|
61
|
+
if (!creator) return;
|
|
62
|
+
this.sub = 'settings-' + serverId;
|
|
63
|
+
this.pool.subscribe(this.sub,
|
|
64
|
+
[{ kinds: [30078], authors: [creator], '#d': ['zellous-settings:' + serverId] }],
|
|
65
|
+
(event) => {
|
|
66
|
+
if (event.pubkey !== creator) return;
|
|
67
|
+
try { this.store.set(serverId, JSON.parse(event.content)); this.dispatchEvent(new CustomEvent('updated', { detail: { serverId, next: this.store.get(serverId) } })); } catch {}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const createSettings = (opts) => new Settings(opts);
|
package/src/wireweave.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { createRelayPool } from './relay-pool.js';
|
|
2
|
+
import { createAuth } from './auth.js';
|
|
3
|
+
import { createFSM } from './fsm.js';
|
|
4
|
+
import { createVoiceSession } from './voice.js';
|
|
5
|
+
import { createChat } from './chat.js';
|
|
6
|
+
import { createChannels } from './channels.js';
|
|
7
|
+
import { createServers } from './servers.js';
|
|
8
|
+
import { createMessageBus } from './message.js';
|
|
9
|
+
import { createBans } from './bans.js';
|
|
10
|
+
import { createRoles } from './roles.js';
|
|
11
|
+
import { createSettings } from './settings.js';
|
|
12
|
+
import { createMedia } from './media.js';
|
|
13
|
+
import { createPages } from './pages.js';
|
|
14
|
+
import { register } from './debug.js';
|
|
15
|
+
|
|
16
|
+
export const createWireweave = ({
|
|
17
|
+
nostrTools,
|
|
18
|
+
xstate,
|
|
19
|
+
storage = (typeof localStorage !== 'undefined' ? localStorage : null),
|
|
20
|
+
extension = (typeof window !== 'undefined' ? window.nostr : null),
|
|
21
|
+
relays = ['wss://relay.damus.io', 'wss://relay.primal.net', 'wss://nos.lol', 'wss://relay.snort.social'],
|
|
22
|
+
mediaDevices = (typeof navigator !== 'undefined' ? navigator.mediaDevices : null),
|
|
23
|
+
WebSocketImpl = (typeof WebSocket !== 'undefined' ? WebSocket : null)
|
|
24
|
+
} = {}) => {
|
|
25
|
+
if (!nostrTools) throw new Error('wireweave: nostrTools required');
|
|
26
|
+
if (!xstate) throw new Error('wireweave: xstate required');
|
|
27
|
+
|
|
28
|
+
const fsm = createFSM(xstate);
|
|
29
|
+
const pool = createRelayPool({ relays, verifyEvent: nostrTools.verifyEvent, WebSocketImpl });
|
|
30
|
+
const auth = createAuth({ nostrTools, storage, extension });
|
|
31
|
+
const message = createMessageBus();
|
|
32
|
+
const bans = createBans({ relayPool: pool });
|
|
33
|
+
const roles = createRoles({ relayPool: pool, auth });
|
|
34
|
+
const settings = createSettings({ relayPool: pool, auth, roles });
|
|
35
|
+
const pages = createPages({ relayPool: pool, auth, roles });
|
|
36
|
+
const media = createMedia({ relayPool: pool, auth });
|
|
37
|
+
const channels = createChannels({ relayPool: pool, auth });
|
|
38
|
+
|
|
39
|
+
let currentChannelId = null;
|
|
40
|
+
const chat = createChat({
|
|
41
|
+
relayPool: pool, auth,
|
|
42
|
+
getChannelContext: () => ({ channelId: currentChannelId, serverId: servers.currentServerId || '' }),
|
|
43
|
+
isAdmin: (sid) => roles.isAdmin(sid)
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const servers = createServers({
|
|
47
|
+
relayPool: pool, auth, storage,
|
|
48
|
+
onSwitch: async (serverId) => {
|
|
49
|
+
roles.subscribe(serverId);
|
|
50
|
+
settings.subscribe(serverId);
|
|
51
|
+
bans.subscribe(serverId);
|
|
52
|
+
pages.subscribe(serverId);
|
|
53
|
+
if (serverId) {
|
|
54
|
+
await new Promise((resolve) => {
|
|
55
|
+
let done = false;
|
|
56
|
+
const finish = () => { if (done) return; done = true; resolve(); };
|
|
57
|
+
channels.load(serverId, finish);
|
|
58
|
+
setTimeout(finish, 3000);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
const firstText = channels.channels.find(c => c.type === 'text');
|
|
62
|
+
if (firstText) {
|
|
63
|
+
currentChannelId = firstText.id;
|
|
64
|
+
chat.loadHistory(firstText.id);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
let voice = null;
|
|
70
|
+
const ensureVoice = ({ serverId, displayName, onAudioTrack, onVideoTrack }) => {
|
|
71
|
+
if (!voice) voice = createVoiceSession({ fsm, xstate, relayPool: pool, auth, mediaDevices, bans, serverId, onAudioTrack, onVideoTrack });
|
|
72
|
+
else voice.serverId = serverId;
|
|
73
|
+
return voice;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const setCurrentChannel = (id) => { currentChannelId = id; if (id) chat.loadHistory(id); };
|
|
77
|
+
|
|
78
|
+
const api = {
|
|
79
|
+
pool, auth, fsm, message, bans, roles, settings, pages, media, channels, servers, chat,
|
|
80
|
+
get voice() { return voice; },
|
|
81
|
+
ensureVoice,
|
|
82
|
+
setCurrentChannel,
|
|
83
|
+
get currentChannelId() { return currentChannelId; },
|
|
84
|
+
get currentServerId() { return servers.currentServerId; }
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
register('wireweave', api);
|
|
88
|
+
return api;
|
|
89
|
+
};
|
package/src/legacy/README.md
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
# legacy/
|
|
2
|
-
|
|
3
|
-
browser-globals port of the full zellous networking + voice stack. these files are the original scripts copied verbatim from `zellous/docs/js/`. they assign to `window.*` (`window.nostrNet`, `window.nostrVoice`, `window.auth`, etc.) and expect co-resident globals (`state`, `XState`, `NostrTools`, `ui`, `auth`, `message`).
|
|
4
|
-
|
|
5
|
-
this is the honest "move everything" checkpoint. the SDK-shaped ES-module exports in `../` are the forward path — `relay-pool.js` + `auth.js` today, voice / chat / channels / servers to follow.
|
|
6
|
-
|
|
7
|
-
## load order
|
|
8
|
-
|
|
9
|
-
scripts must load in this order (same order zellous uses):
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
nostr-state-patch.js
|
|
13
|
-
nostr-fsm.js
|
|
14
|
-
nostr-network.js
|
|
15
|
-
nostr-auth.js
|
|
16
|
-
nostr-message.js
|
|
17
|
-
nostr-bans.js
|
|
18
|
-
nostr-roles.js
|
|
19
|
-
nostr-settings.js
|
|
20
|
-
nostr-voice.js
|
|
21
|
-
nostr-voice-rtc.js
|
|
22
|
-
nostr-voice-sfu.js
|
|
23
|
-
nostr-voice-camera.js
|
|
24
|
-
nostr-chat.js
|
|
25
|
-
nostr-channels.js
|
|
26
|
-
nostr-servers.js
|
|
27
|
-
nostr-media.js
|
|
28
|
-
nostr-pages.js
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## prerequisites already on window
|
|
32
|
-
|
|
33
|
-
- `window.state`, `window.stateSignals`, `window.config` (zellous/state.js or equivalent)
|
|
34
|
-
- `window.XState` — `{ createMachine, createActor }` from xstate v5
|
|
35
|
-
- `window.NostrTools` — `nostr-tools` (generateSecretKey, getPublicKey, nip19, finalizeEvent, verifyEvent)
|
|
36
|
-
- preact signals for reactive state
|
|
37
|
-
- `window.ui` bindings (optional for headless)
|
|
38
|
-
|
|
39
|
-
## files
|
|
40
|
-
|
|
41
|
-
| file | bytes | assigns |
|
|
42
|
-
|---|---|---|
|
|
43
|
-
| nostr-state-patch.js | 507 | state signal patches |
|
|
44
|
-
| nostr-fsm.js | 1018 | `voiceMachine`, `peerMachine` |
|
|
45
|
-
| nostr-network.js | 7595 | `nostrNet` relay pool |
|
|
46
|
-
| nostr-auth.js | 8501 | `auth` key mgmt |
|
|
47
|
-
| nostr-message.js | 529 | `message` system msg dispatch |
|
|
48
|
-
| nostr-bans.js | 2422 | `nostrBans` moderation |
|
|
49
|
-
| nostr-roles.js | 3077 | `serverRoles` |
|
|
50
|
-
| nostr-settings.js | 4231 | `serverSettings` encoder config |
|
|
51
|
-
| nostr-voice.js | 13458 | `nostrVoice` session |
|
|
52
|
-
| nostr-voice-rtc.js | 11636 | `nostrVoiceRtc` perfect-negotiation |
|
|
53
|
-
| nostr-voice-sfu.js | 4773 | `nostrVoiceSfu` hub election |
|
|
54
|
-
| nostr-voice-camera.js | 2355 | `nostrVoiceCamera` |
|
|
55
|
-
| nostr-chat.js | 6513 | `chat` kind-28 messaging |
|
|
56
|
-
| nostr-channels.js | 5369 | `channelManager` |
|
|
57
|
-
| nostr-servers.js | 10718 | `serverManager` communities |
|
|
58
|
-
| nostr-media.js | 3524 | `nostrMedia` blossom uploads |
|
|
59
|
-
| nostr-pages.js | 7909 | `serverPages` |
|
|
60
|
-
|
|
61
|
-
## CDN load (via jsDelivr)
|
|
62
|
-
|
|
63
|
-
```html
|
|
64
|
-
<script src="https://cdn.jsdelivr.net/npm/wireweave@latest/src/legacy/nostr-fsm.js"></script>
|
|
65
|
-
<script src="https://cdn.jsdelivr.net/npm/wireweave@latest/src/legacy/nostr-network.js"></script>
|
|
66
|
-
<!-- etc in order above -->
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## status
|
|
70
|
-
|
|
71
|
-
probably emerging 🌀
|
package/src/legacy/index.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
export const LEGACY_LOAD_ORDER = [
|
|
2
|
-
'nostr-state-patch.js',
|
|
3
|
-
'nostr-fsm.js',
|
|
4
|
-
'nostr-network.js',
|
|
5
|
-
'nostr-auth.js',
|
|
6
|
-
'nostr-message.js',
|
|
7
|
-
'nostr-bans.js',
|
|
8
|
-
'nostr-roles.js',
|
|
9
|
-
'nostr-settings.js',
|
|
10
|
-
'nostr-voice.js',
|
|
11
|
-
'nostr-voice-rtc.js',
|
|
12
|
-
'nostr-voice-sfu.js',
|
|
13
|
-
'nostr-voice-camera.js',
|
|
14
|
-
'nostr-chat.js',
|
|
15
|
-
'nostr-channels.js',
|
|
16
|
-
'nostr-servers.js',
|
|
17
|
-
'nostr-media.js',
|
|
18
|
-
'nostr-pages.js'
|
|
19
|
-
];
|
|
20
|
-
|
|
21
|
-
export const loadLegacy = async ({ base = 'https://cdn.jsdelivr.net/npm/wireweave@latest/src/legacy/', prereqsReady = () => true } = {}) => {
|
|
22
|
-
if (!prereqsReady()) throw new Error('wireweave/legacy: prereqs not ready (state, XState, NostrTools must be on window)');
|
|
23
|
-
for (const f of LEGACY_LOAD_ORDER) {
|
|
24
|
-
await new Promise((resolve, reject) => {
|
|
25
|
-
const s = document.createElement('script');
|
|
26
|
-
s.src = base + f;
|
|
27
|
-
s.onload = resolve;
|
|
28
|
-
s.onerror = () => reject(new Error('failed: ' + f));
|
|
29
|
-
document.head.appendChild(s);
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
return {
|
|
33
|
-
net: window.nostrNet,
|
|
34
|
-
auth: window.auth,
|
|
35
|
-
voice: window.nostrVoice,
|
|
36
|
-
chat: window.chat,
|
|
37
|
-
channels: window.channelManager,
|
|
38
|
-
servers: window.serverManager,
|
|
39
|
-
message: window.message,
|
|
40
|
-
bans: window.nostrBans,
|
|
41
|
-
roles: window.serverRoles,
|
|
42
|
-
settings: window.serverSettings,
|
|
43
|
-
pages: window.serverPages,
|
|
44
|
-
media: window.nostrMedia,
|
|
45
|
-
voiceRtc: window.nostrVoiceRtc,
|
|
46
|
-
voiceSfu: window.nostrVoiceSfu,
|
|
47
|
-
voiceCamera: window.nostrVoiceCamera
|
|
48
|
-
};
|
|
49
|
-
};
|
package/src/legacy/nostr-auth.js
DELETED
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
const auth = {
|
|
2
|
-
get user() {
|
|
3
|
-
const pk = state.nostrPubkey;
|
|
4
|
-
if (!pk) return null;
|
|
5
|
-
const short = auth.npubShort(pk);
|
|
6
|
-
return { id: pk, username: short, displayName: state.nostrProfile?.name || short };
|
|
7
|
-
},
|
|
8
|
-
|
|
9
|
-
init() {
|
|
10
|
-
const skHex = localStorage.getItem('zn_sk');
|
|
11
|
-
const pkHex = localStorage.getItem('zn_pk');
|
|
12
|
-
if (!skHex || !pkHex) return false;
|
|
13
|
-
try {
|
|
14
|
-
state.nostrPrivkey = auth._hex2b(skHex);
|
|
15
|
-
state.nostrPubkey = pkHex;
|
|
16
|
-
const short = auth.npubShort(pkHex);
|
|
17
|
-
const nameEl = document.getElementById('userPanelName');
|
|
18
|
-
const tagEl = document.getElementById('userPanelTag');
|
|
19
|
-
const avatarEl = document.getElementById('userPanelAvatar');
|
|
20
|
-
if (nameEl) nameEl.textContent = short;
|
|
21
|
-
if (tagEl) tagEl.textContent = short;
|
|
22
|
-
if (avatarEl) { const n = avatarEl.childNodes[0]; if (n?.nodeType === 3) n.textContent = short[0].toUpperCase(); }
|
|
23
|
-
document.getElementById('userStatusDot')?.classList.add('online');
|
|
24
|
-
return true;
|
|
25
|
-
} catch { return false; }
|
|
26
|
-
},
|
|
27
|
-
|
|
28
|
-
generateKey() {
|
|
29
|
-
const NT = window.NostrTools;
|
|
30
|
-
const sk = NT.generateSecretKey();
|
|
31
|
-
const pk = NT.getPublicKey(sk);
|
|
32
|
-
localStorage.setItem('zn_sk', auth._b2hex(sk));
|
|
33
|
-
localStorage.setItem('zn_pk', pk);
|
|
34
|
-
state.nostrPrivkey = sk;
|
|
35
|
-
state.nostrPubkey = pk;
|
|
36
|
-
return { pubkey: pk, privkey: sk };
|
|
37
|
-
},
|
|
38
|
-
|
|
39
|
-
importKey(input) {
|
|
40
|
-
try {
|
|
41
|
-
const NT = window.NostrTools;
|
|
42
|
-
const sk = input.startsWith('nsec')
|
|
43
|
-
? (() => { const d = NT.nip19.decode(input); if (d.type !== 'nsec') throw 0; return d.data; })()
|
|
44
|
-
: auth._hex2b(input);
|
|
45
|
-
const pk = NT.getPublicKey(sk);
|
|
46
|
-
localStorage.setItem('zn_sk', auth._b2hex(sk));
|
|
47
|
-
localStorage.setItem('zn_pk', pk);
|
|
48
|
-
state.nostrPrivkey = sk;
|
|
49
|
-
state.nostrPubkey = pk;
|
|
50
|
-
return true;
|
|
51
|
-
} catch { return false; }
|
|
52
|
-
},
|
|
53
|
-
|
|
54
|
-
async loginWithExtension() {
|
|
55
|
-
const pk = await window.nostr.getPublicKey();
|
|
56
|
-
state.nostrPubkey = pk;
|
|
57
|
-
state.nostrPrivkey = null;
|
|
58
|
-
return pk;
|
|
59
|
-
},
|
|
60
|
-
|
|
61
|
-
async sign(eventTemplate) {
|
|
62
|
-
if (state.nostrPrivkey) return window.NostrTools.finalizeEvent(eventTemplate, state.nostrPrivkey);
|
|
63
|
-
return window.nostr.signEvent(eventTemplate);
|
|
64
|
-
},
|
|
65
|
-
|
|
66
|
-
async setDisplayName(name) {
|
|
67
|
-
if (!state.nostrPubkey) throw new Error('Not logged in');
|
|
68
|
-
if (!name || typeof name !== 'string') throw new Error('Invalid display name');
|
|
69
|
-
const template = {
|
|
70
|
-
kind: 0,
|
|
71
|
-
created_at: Math.floor(Date.now() / 1000),
|
|
72
|
-
tags: [],
|
|
73
|
-
content: JSON.stringify({ name: name.trim(), ...(state.nostrProfile || {}) })
|
|
74
|
-
};
|
|
75
|
-
const signed = await auth.sign(template);
|
|
76
|
-
if (window.nostrNet && window.nostrNet.publish) {
|
|
77
|
-
await window.nostrNet.publish(signed);
|
|
78
|
-
}
|
|
79
|
-
state.nostrProfile = { ...(state.nostrProfile || {}), name: name.trim() };
|
|
80
|
-
const nameEl = document.getElementById('userPanelName');
|
|
81
|
-
const avatarEl = document.getElementById('userPanelAvatar');
|
|
82
|
-
if (nameEl) nameEl.textContent = state.nostrProfile.name;
|
|
83
|
-
if (avatarEl) { const n = avatarEl.childNodes[0]; if (n?.nodeType === 3) n.textContent = state.nostrProfile.name[0].toUpperCase(); }
|
|
84
|
-
if (window.chat) chat.updateProfile(state.nostrPubkey, state.nostrProfile);
|
|
85
|
-
},
|
|
86
|
-
|
|
87
|
-
logout() {
|
|
88
|
-
localStorage.removeItem('zn_sk');
|
|
89
|
-
localStorage.removeItem('zn_pk');
|
|
90
|
-
state.nostrPubkey = '';
|
|
91
|
-
state.nostrPrivkey = null;
|
|
92
|
-
state.nostrProfile = null;
|
|
93
|
-
const nameEl = document.getElementById('userPanelName');
|
|
94
|
-
const tagEl = document.getElementById('userPanelTag');
|
|
95
|
-
const avatarEl = document.getElementById('userPanelAvatar');
|
|
96
|
-
if (nameEl) nameEl.textContent = 'Not logged in';
|
|
97
|
-
if (tagEl) tagEl.textContent = 'Click to login';
|
|
98
|
-
if (avatarEl) { const n = avatarEl.childNodes[0]; if (n?.nodeType === 3) n.textContent = '?'; }
|
|
99
|
-
document.getElementById('userStatusDot')?.classList.remove('online');
|
|
100
|
-
if (window.nostrNet?.disconnect) nostrNet.disconnect();
|
|
101
|
-
},
|
|
102
|
-
|
|
103
|
-
getToken() { return state.nostrPubkey || null; },
|
|
104
|
-
isLoggedIn() { return !!state.nostrPubkey; },
|
|
105
|
-
|
|
106
|
-
npubShort(pubkey) {
|
|
107
|
-
if (!pubkey) return '';
|
|
108
|
-
const npub = window.NostrTools.nip19.npubEncode(pubkey);
|
|
109
|
-
return npub.slice(0, 8) + '...' + npub.slice(-4);
|
|
110
|
-
},
|
|
111
|
-
|
|
112
|
-
showModal() {
|
|
113
|
-
const modal = document.getElementById('authModal');
|
|
114
|
-
if (!modal) return;
|
|
115
|
-
modal.style.display = 'flex';
|
|
116
|
-
const cv = document.getElementById('nostrConnectView');
|
|
117
|
-
const lv = document.getElementById('nostrLoggedInView');
|
|
118
|
-
const loggedIn = auth.isLoggedIn();
|
|
119
|
-
if (cv) cv.style.display = loggedIn ? 'none' : 'flex';
|
|
120
|
-
if (lv) lv.style.display = loggedIn ? 'flex' : 'none';
|
|
121
|
-
if (loggedIn) {
|
|
122
|
-
const d = document.getElementById('nostrNpubDisplay');
|
|
123
|
-
if (d) d.textContent = auth.npubShort(state.nostrPubkey);
|
|
124
|
-
const inp = document.getElementById('displayNameInput');
|
|
125
|
-
if (inp) inp.value = state.nostrProfile?.name || '';
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
|
|
129
|
-
hideModal() {
|
|
130
|
-
const modal = document.getElementById('authModal');
|
|
131
|
-
if (modal) modal.style.display = 'none';
|
|
132
|
-
},
|
|
133
|
-
|
|
134
|
-
_afterLogin() {
|
|
135
|
-
const d = document.getElementById('nostrNpubDisplay');
|
|
136
|
-
if (d) d.textContent = auth.npubShort(state.nostrPubkey);
|
|
137
|
-
const cv = document.getElementById('nostrConnectView');
|
|
138
|
-
const lv = document.getElementById('nostrLoggedInView');
|
|
139
|
-
if (cv) cv.style.display = 'none';
|
|
140
|
-
if (lv) lv.style.display = 'flex';
|
|
141
|
-
const err = document.getElementById('nostrAuthError');
|
|
142
|
-
if (err) err.textContent = '';
|
|
143
|
-
const short = auth.npubShort(state.nostrPubkey);
|
|
144
|
-
const nameEl = document.getElementById('userPanelName');
|
|
145
|
-
const tagEl = document.getElementById('userPanelTag');
|
|
146
|
-
const avatarEl = document.getElementById('userPanelAvatar');
|
|
147
|
-
if (nameEl) nameEl.textContent = state.nostrProfile?.name || short;
|
|
148
|
-
if (tagEl) tagEl.textContent = short;
|
|
149
|
-
if (avatarEl) { const n = avatarEl.childNodes[0]; if (n?.nodeType === 3) n.textContent = (state.nostrProfile?.name || short)[0].toUpperCase(); }
|
|
150
|
-
document.getElementById('userStatusDot')?.classList.add('online');
|
|
151
|
-
document.dispatchEvent(new CustomEvent('nostr:login'));
|
|
152
|
-
setTimeout(() => auth.hideModal(), 1000);
|
|
153
|
-
},
|
|
154
|
-
|
|
155
|
-
_err(msg) {
|
|
156
|
-
const el = document.getElementById('nostrAuthError');
|
|
157
|
-
if (el) el.textContent = msg;
|
|
158
|
-
},
|
|
159
|
-
|
|
160
|
-
bindUI() {
|
|
161
|
-
const $ = id => document.getElementById(id);
|
|
162
|
-
const on = (id, fn) => { const el = $(id); if (el) el.addEventListener('click', fn); };
|
|
163
|
-
|
|
164
|
-
on('connectExtensionBtn', async () => {
|
|
165
|
-
try {
|
|
166
|
-
if (!window.nostr) throw new Error('No Nostr extension found');
|
|
167
|
-
await auth.loginWithExtension();
|
|
168
|
-
auth._afterLogin();
|
|
169
|
-
} catch (e) { auth._err(e.message); }
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
on('generateKeyBtn', () => {
|
|
173
|
-
try { auth.generateKey(); auth._afterLogin(); }
|
|
174
|
-
catch (e) { auth._err(e.message); }
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
on('importKeyBtn', () => {
|
|
178
|
-
const inp = $('importKeyInput');
|
|
179
|
-
const val = inp ? inp.value.trim() : '';
|
|
180
|
-
if (!val) { auth._err('Enter a key'); return; }
|
|
181
|
-
auth.importKey(val) ? auth._afterLogin() : auth._err('Invalid key');
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
on('copyNpubBtn', () => {
|
|
185
|
-
const pk = state.nostrPubkey;
|
|
186
|
-
if (pk) navigator.clipboard.writeText(window.NostrTools.nip19.npubEncode(pk)).catch(() => {});
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
on('saveDisplayNameBtn', async () => {
|
|
190
|
-
const inp = $('displayNameInput');
|
|
191
|
-
const val = inp ? inp.value.trim() : '';
|
|
192
|
-
if (!val) { auth._err('Enter a display name'); return; }
|
|
193
|
-
try {
|
|
194
|
-
await auth.setDisplayName(val);
|
|
195
|
-
if (inp) inp.value = val;
|
|
196
|
-
} catch (e) { auth._err(e.message); }
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
on('nostrLogoutBtn', () => { auth.logout(); auth.showModal(); });
|
|
200
|
-
|
|
201
|
-
const modal = document.getElementById('authModal');
|
|
202
|
-
if (modal) modal.addEventListener('click', (e) => { if (e.target === modal) auth.hideModal(); });
|
|
203
|
-
|
|
204
|
-
const avatarArea = document.querySelector('.user-avatar, .username-area, [data-action="show-auth"]');
|
|
205
|
-
if (avatarArea) avatarArea.addEventListener('click', () => auth.showModal());
|
|
206
|
-
},
|
|
207
|
-
|
|
208
|
-
_b2hex(bytes) { return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join(''); },
|
|
209
|
-
_hex2b(hex) {
|
|
210
|
-
const a = new Uint8Array(hex.length / 2);
|
|
211
|
-
for (let i = 0; i < a.length; i++) a[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
212
|
-
return a;
|
|
213
|
-
}
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
window.__zellous.auth = auth;
|
|
217
|
-
window.auth = auth;
|
package/src/legacy/nostr-bans.js
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
var nostrBans = {
|
|
2
|
-
_store: new Map(),
|
|
3
|
-
_sub: null,
|
|
4
|
-
|
|
5
|
-
isBanned(serverId, pubkey) {
|
|
6
|
-
var bans = nostrBans._store.get(serverId) || {};
|
|
7
|
-
return !!(bans.banned || []).includes(pubkey);
|
|
8
|
-
},
|
|
9
|
-
|
|
10
|
-
isTimedOut(serverId, pubkey) {
|
|
11
|
-
var bans = nostrBans._store.get(serverId) || {};
|
|
12
|
-
var timeout = bans.timeouts && bans.timeouts[pubkey];
|
|
13
|
-
if (!timeout) return false;
|
|
14
|
-
return timeout.expiry > Math.floor(Date.now() / 1000);
|
|
15
|
-
},
|
|
16
|
-
|
|
17
|
-
subscribe(serverId) {
|
|
18
|
-
if (nostrBans._sub) { nostrNet.unsubscribe(nostrBans._sub); nostrBans._sub = null; }
|
|
19
|
-
if (!serverId) return;
|
|
20
|
-
var creator = serverId.split(':')[0];
|
|
21
|
-
if (!creator) return;
|
|
22
|
-
nostrBans._sub = 'bans-' + serverId;
|
|
23
|
-
nostrNet.subscribe(nostrBans._sub,
|
|
24
|
-
[{ kinds: [30078], authors: [creator], '#d': ['zellous-ban:' + serverId, 'zellous-timeout:' + serverId] }],
|
|
25
|
-
function(event) {
|
|
26
|
-
if (event.pubkey !== creator) return;
|
|
27
|
-
try {
|
|
28
|
-
var dTag = event.tags.find(function(t) { return t[0] === 'd'; });
|
|
29
|
-
if (!dTag || !dTag[1]) return;
|
|
30
|
-
var prefix = dTag[1].split(':')[0];
|
|
31
|
-
var banData = nostrBans._store.get(serverId) || { banned: [], timeouts: {} };
|
|
32
|
-
if (prefix === 'zellous-ban') {
|
|
33
|
-
var pubkey = dTag[1].split(':')[2];
|
|
34
|
-
if (pubkey && !banData.banned.includes(pubkey)) banData.banned.push(pubkey);
|
|
35
|
-
} else if (prefix === 'zellous-timeout') {
|
|
36
|
-
var pubkey = dTag[1].split(':')[2];
|
|
37
|
-
if (pubkey) {
|
|
38
|
-
var data = JSON.parse(event.content);
|
|
39
|
-
if (data.expiry > Math.floor(Date.now() / 1000)) {
|
|
40
|
-
if (!banData.timeouts) banData.timeouts = {};
|
|
41
|
-
banData.timeouts[pubkey] = { expiry: data.expiry };
|
|
42
|
-
} else if (banData.timeouts && banData.timeouts[pubkey]) {
|
|
43
|
-
delete banData.timeouts[pubkey];
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
nostrBans._store.set(serverId, banData);
|
|
48
|
-
if (window.__debug && window.__debug.bans) window.__debug.bans = Object.fromEntries(nostrBans._store);
|
|
49
|
-
} catch(e) {}
|
|
50
|
-
},
|
|
51
|
-
function() {}
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
window.__zellous.bans = nostrBans;
|
|
57
|
-
window.nostrBans = nostrBans;
|
|
58
|
-
if (!window.__debug) window.__debug = {};
|
|
59
|
-
Object.defineProperty(window.__debug, 'bans', { get: function() { return Object.fromEntries(nostrBans._store); }, configurable: true });
|