wireweave 0.3.7 → 0.3.8

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.7",
3
+ "version": "0.3.8",
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/dm.js ADDED
@@ -0,0 +1,68 @@
1
+ export class DM extends EventTarget {
2
+ constructor({ relayPool, auth, nostrTools }) {
3
+ super();
4
+ if (!relayPool || !auth || !nostrTools) throw new Error('DM: deps required');
5
+ if (!nostrTools.nip44) throw new Error('nostr-tools nip44 missing');
6
+ this.pool = relayPool;
7
+ this.auth = auth;
8
+ this.NT = nostrTools;
9
+ this.subId = null;
10
+ }
11
+
12
+ _convKey(peerPubkey) {
13
+ if (!this.auth.privkey) throw new Error('DM: privkey required (extension signing not supported for nip44)');
14
+ return this.NT.nip44.v2.utils.getConversationKey
15
+ ? this.NT.nip44.v2.utils.getConversationKey(this.auth.privkey, peerPubkey)
16
+ : this.NT.nip44.getConversationKey(this.auth.privkey, peerPubkey);
17
+ }
18
+
19
+ async send(peerPubkey, plaintext) {
20
+ const key = this._convKey(peerPubkey);
21
+ const ciphertext = this.NT.nip44.v2.encrypt(plaintext, key);
22
+ const signed = await this.auth.sign({
23
+ kind: 14,
24
+ created_at: Math.floor(Date.now() / 1000),
25
+ tags: [['p', peerPubkey]],
26
+ content: ciphertext
27
+ });
28
+ this.pool.publish(signed);
29
+ return signed;
30
+ }
31
+
32
+ decrypt(event) {
33
+ const peer = event.pubkey === this.auth.pubkey
34
+ ? (event.tags.find(t => t[0] === 'p')?.[1] || '')
35
+ : event.pubkey;
36
+ if (!peer) throw new Error('DM: cannot resolve peer');
37
+ const key = this._convKey(peer);
38
+ return this.NT.nip44.v2.decrypt(event.content, key);
39
+ }
40
+
41
+ subscribe(onMessage) {
42
+ if (!this.auth.pubkey) throw new Error('DM: not authenticated');
43
+ const subId = 'dm-' + this.auth.pubkey.slice(0, 16);
44
+ this.subId = subId;
45
+ this.pool.subscribe(subId, [
46
+ { kinds: [14], '#p': [this.auth.pubkey] },
47
+ { kinds: [14], authors: [this.auth.pubkey] }
48
+ ], (event) => {
49
+ try {
50
+ const plaintext = this.decrypt(event);
51
+ onMessage({ event, plaintext, peer: event.pubkey === this.auth.pubkey
52
+ ? (event.tags.find(t => t[0] === 'p')?.[1] || '')
53
+ : event.pubkey });
54
+ } catch (e) {
55
+ this._emit('error', { event, error: e.message });
56
+ }
57
+ });
58
+ return subId;
59
+ }
60
+
61
+ unsubscribe() {
62
+ if (this.subId) { this.pool.unsubscribe(this.subId); this.subId = null; }
63
+ }
64
+
65
+ _emit(type, detail) { this.dispatchEvent(new CustomEvent(type, { detail })); }
66
+ }
67
+
68
+ export const createDM = (opts) => new DM(opts);
package/src/index.js CHANGED
@@ -11,6 +11,7 @@ export { Bans, createBans } from './bans.js';
11
11
  export { Roles, createRoles } from './roles.js';
12
12
  export { Settings, createSettings } from './settings.js';
13
13
  export { Media, createMedia } from './media.js';
14
+ export { DM, createDM } from './dm.js';
14
15
  export { Pages, createPages } from './pages.js';
15
16
  export { createWireweave } from './wireweave.js';
16
17
  export * as debug from './debug.js';
package/src/media.js CHANGED
@@ -54,6 +54,44 @@ export class Media {
54
54
  throw new Error('upload failed: ' + errors.join('; '));
55
55
  }
56
56
 
57
+ async download(url, { onProgress = null } = {}) {
58
+ const res = await fetch(url);
59
+ if (!res.ok) throw new Error('download failed: ' + res.status);
60
+ const total = Number(res.headers.get('content-length')) || 0;
61
+ if (!onProgress || !res.body?.getReader) {
62
+ const buf = await res.arrayBuffer();
63
+ return { bytes: new Uint8Array(buf), type: res.headers.get('content-type') || '', size: buf.byteLength };
64
+ }
65
+ const reader = res.body.getReader();
66
+ const chunks = [];
67
+ let received = 0;
68
+ while (true) {
69
+ const { done, value } = await reader.read();
70
+ if (done) break;
71
+ chunks.push(value);
72
+ received += value.length;
73
+ onProgress({ received, total });
74
+ }
75
+ const out = new Uint8Array(received);
76
+ let off = 0;
77
+ for (const c of chunks) { out.set(c, off); off += c.length; }
78
+ return { bytes: out, type: res.headers.get('content-type') || '', size: received };
79
+ }
80
+
81
+ async fetchBlob(hash) {
82
+ const errors = [];
83
+ for (const srv of this.servers) {
84
+ try {
85
+ const url = srv.replace(/\/$/, '') + '/' + hash;
86
+ const res = await fetch(url);
87
+ if (!res.ok) { errors.push(srv + ': ' + res.status); continue; }
88
+ const buf = await res.arrayBuffer();
89
+ return { url, bytes: new Uint8Array(buf), type: res.headers.get('content-type') || '' };
90
+ } catch (e) { errors.push(srv + ': ' + e.message); }
91
+ }
92
+ throw new Error('fetchBlob failed: ' + errors.join('; '));
93
+ }
94
+
57
95
  isMedia(url) {
58
96
  if (typeof url !== 'string') return null;
59
97
  if (/\.(png|jpe?g|gif|webp|svg|avif)(\?|$)/i.test(url)) return 'image';