@abndnce/pulsar-client 0.0.8 → 0.0.9
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/dist/index.d.mts +40 -22
- package/dist/index.mjs +116 -39
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -39,9 +39,14 @@ declare function connectNostr(_relay: string, _pubkey: string): Promise<PulsarCl
|
|
|
39
39
|
*/
|
|
40
40
|
/**
|
|
41
41
|
* Wait for an RTCDataChannel to enter the "open" state.
|
|
42
|
-
*
|
|
42
|
+
*
|
|
43
|
+
* Also monitors the RTCPeerConnection for failure/closure, and
|
|
44
|
+
* accepts an optional AbortSignal for cancellation.
|
|
45
|
+
*
|
|
46
|
+
* Rejects if the channel closes, the peer connection fails, or
|
|
47
|
+
* the timeout elapses before the channel opens.
|
|
43
48
|
*/
|
|
44
|
-
declare function waitForDataChannelOpen(channel: RTCDataChannel, timeoutMs?: number): Promise<void>;
|
|
49
|
+
declare function waitForDataChannelOpen(channel: RTCDataChannel, pc: RTCPeerConnection, timeoutMs?: number, signal?: AbortSignal): Promise<void>;
|
|
45
50
|
/**
|
|
46
51
|
* Create a data channel on the given `pc` with the Pulsar socket label
|
|
47
52
|
* convention (`socket/<hostname>:<port>`) and wait for it to open.
|
|
@@ -49,34 +54,47 @@ declare function waitForDataChannelOpen(channel: RTCDataChannel, timeoutMs?: num
|
|
|
49
54
|
* This is the low-level primitive used by `connect()` and
|
|
50
55
|
* `libcurlTransport()`.
|
|
51
56
|
*/
|
|
52
|
-
declare function openSocketChannel(pc: RTCPeerConnection, hostname: string, port: number, timeoutMs?: number): Promise<RTCDataChannel>;
|
|
57
|
+
declare function openSocketChannel(pc: RTCPeerConnection, hostname: string, port: number, timeoutMs?: number, signal?: AbortSignal): Promise<RTCDataChannel>;
|
|
53
58
|
//#endregion
|
|
54
59
|
//#region lib/tunnel.d.ts
|
|
55
60
|
/**
|
|
56
|
-
*
|
|
61
|
+
* WebSocket-compatible wrapper around an RTCDataChannel.
|
|
57
62
|
*
|
|
58
|
-
* libcurl.js
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
63
|
+
* libcurl.js compares `readyState` against `WebSocket.OPEN` (numeric 1)
|
|
64
|
+
* and expects static constants (`CONNECTING`, `OPEN`, `CLOSING`, `CLOSED`).
|
|
65
|
+
* Using `EventTarget` ensures `addEventListener` / `removeEventListener`
|
|
66
|
+
* work, which libcurl's poll loop depends on.
|
|
62
67
|
*/
|
|
63
|
-
declare class DataChannelSocket {
|
|
68
|
+
declare class DataChannelSocket extends EventTarget {
|
|
69
|
+
static readonly CONNECTING = 0;
|
|
70
|
+
static readonly OPEN = 1;
|
|
71
|
+
static readonly CLOSING = 2;
|
|
72
|
+
static readonly CLOSED = 3;
|
|
73
|
+
readonly CONNECTING = 0;
|
|
74
|
+
readonly OPEN = 1;
|
|
75
|
+
readonly CLOSING = 2;
|
|
76
|
+
readonly CLOSED = 3;
|
|
77
|
+
readonly url: string;
|
|
78
|
+
readonly protocol = "";
|
|
79
|
+
readonly extensions = "";
|
|
80
|
+
binaryType: string;
|
|
81
|
+
onopen: ((event: Event) => void) | null;
|
|
82
|
+
onclose: ((event: CloseEvent) => void) | null;
|
|
83
|
+
onerror: ((event: Event) => void) | null;
|
|
84
|
+
onmessage: ((event: MessageEvent) => void) | null;
|
|
64
85
|
private _channel;
|
|
65
86
|
private _closed;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}) => void) | null;
|
|
74
|
-
constructor(channel: RTCDataChannel);
|
|
75
|
-
get readyState(): string;
|
|
76
|
-
get binaryType(): string;
|
|
77
|
-
set binaryType(_: string);
|
|
78
|
-
send(data: ArrayBuffer | string | ArrayBufferView): void;
|
|
87
|
+
private _closeDispatched;
|
|
88
|
+
private _readyState;
|
|
89
|
+
constructor(pc: RTCPeerConnection, destination: string);
|
|
90
|
+
private _open;
|
|
91
|
+
get readyState(): number;
|
|
92
|
+
get bufferedAmount(): number;
|
|
93
|
+
send(data: string | ArrayBufferLike | ArrayBufferView): void;
|
|
79
94
|
close(): void;
|
|
95
|
+
private _dispatch;
|
|
96
|
+
private _dispatchError;
|
|
97
|
+
private _dispatchClose;
|
|
80
98
|
}
|
|
81
99
|
/**
|
|
82
100
|
* Create a libcurl.js transport factory from an existing WebRTC
|
package/dist/index.mjs
CHANGED
|
@@ -91,10 +91,19 @@ async function connectNostr(_relay, _pubkey) {
|
|
|
91
91
|
*/
|
|
92
92
|
/**
|
|
93
93
|
* Wait for an RTCDataChannel to enter the "open" state.
|
|
94
|
-
*
|
|
94
|
+
*
|
|
95
|
+
* Also monitors the RTCPeerConnection for failure/closure, and
|
|
96
|
+
* accepts an optional AbortSignal for cancellation.
|
|
97
|
+
*
|
|
98
|
+
* Rejects if the channel closes, the peer connection fails, or
|
|
99
|
+
* the timeout elapses before the channel opens.
|
|
95
100
|
*/
|
|
96
|
-
function waitForDataChannelOpen(channel, timeoutMs = 1e4) {
|
|
101
|
+
function waitForDataChannelOpen(channel, pc, timeoutMs = 1e4, signal) {
|
|
97
102
|
return new Promise((resolve, reject) => {
|
|
103
|
+
if (signal?.aborted) {
|
|
104
|
+
reject(new DOMException("The operation was aborted.", "AbortError"));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
98
107
|
if (channel.readyState === "open") {
|
|
99
108
|
resolve();
|
|
100
109
|
return;
|
|
@@ -105,22 +114,39 @@ function waitForDataChannelOpen(channel, timeoutMs = 1e4) {
|
|
|
105
114
|
}, timeoutMs);
|
|
106
115
|
const cleanup = () => {
|
|
107
116
|
clearTimeout(timeout);
|
|
108
|
-
channel.
|
|
109
|
-
channel.
|
|
110
|
-
channel.
|
|
117
|
+
channel.removeEventListener("open", onOpen);
|
|
118
|
+
channel.removeEventListener("close", onClose);
|
|
119
|
+
channel.removeEventListener("error", onError);
|
|
120
|
+
pc.removeEventListener("connectionstatechange", onStateChange);
|
|
121
|
+
signal?.removeEventListener("abort", onAbort);
|
|
111
122
|
};
|
|
112
|
-
|
|
123
|
+
const onOpen = () => {
|
|
113
124
|
cleanup();
|
|
114
125
|
resolve();
|
|
115
126
|
};
|
|
116
|
-
|
|
127
|
+
const onClose = () => {
|
|
117
128
|
cleanup();
|
|
118
129
|
reject(/* @__PURE__ */ new Error("DataChannel closed before opening"));
|
|
119
130
|
};
|
|
120
|
-
|
|
131
|
+
const onError = () => {
|
|
121
132
|
cleanup();
|
|
122
|
-
reject(/* @__PURE__ */ new Error(
|
|
133
|
+
reject(/* @__PURE__ */ new Error("DataChannel error before opening"));
|
|
134
|
+
};
|
|
135
|
+
const onStateChange = () => {
|
|
136
|
+
if (pc.connectionState === "failed" || pc.connectionState === "closed") {
|
|
137
|
+
cleanup();
|
|
138
|
+
reject(/* @__PURE__ */ new Error(`Peer connection ${pc.connectionState}`));
|
|
139
|
+
}
|
|
123
140
|
};
|
|
141
|
+
const onAbort = () => {
|
|
142
|
+
cleanup();
|
|
143
|
+
reject(new DOMException("The operation was aborted.", "AbortError"));
|
|
144
|
+
};
|
|
145
|
+
channel.addEventListener("open", onOpen, { once: true });
|
|
146
|
+
channel.addEventListener("close", onClose, { once: true });
|
|
147
|
+
channel.addEventListener("error", onError, { once: true });
|
|
148
|
+
pc.addEventListener("connectionstatechange", onStateChange);
|
|
149
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
124
150
|
});
|
|
125
151
|
}
|
|
126
152
|
/**
|
|
@@ -130,60 +156,111 @@ function waitForDataChannelOpen(channel, timeoutMs = 1e4) {
|
|
|
130
156
|
* This is the low-level primitive used by `connect()` and
|
|
131
157
|
* `libcurlTransport()`.
|
|
132
158
|
*/
|
|
133
|
-
function openSocketChannel(pc, hostname, port, timeoutMs) {
|
|
159
|
+
function openSocketChannel(pc, hostname, port, timeoutMs, signal) {
|
|
134
160
|
const label = `${SOCKET_PREFIX}${hostname}:${port}`;
|
|
135
161
|
const channel = pc.createDataChannel(label, { ordered: true });
|
|
136
|
-
return waitForDataChannelOpen(channel, timeoutMs).then(() => channel);
|
|
162
|
+
return waitForDataChannelOpen(channel, pc, timeoutMs, signal).then(() => channel);
|
|
137
163
|
}
|
|
138
164
|
//#endregion
|
|
139
165
|
//#region lib/tunnel.ts
|
|
140
166
|
/**
|
|
141
|
-
*
|
|
167
|
+
* WebSocket-compatible wrapper around an RTCDataChannel.
|
|
142
168
|
*
|
|
143
|
-
* libcurl.js
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
169
|
+
* libcurl.js compares `readyState` against `WebSocket.OPEN` (numeric 1)
|
|
170
|
+
* and expects static constants (`CONNECTING`, `OPEN`, `CLOSING`, `CLOSED`).
|
|
171
|
+
* Using `EventTarget` ensures `addEventListener` / `removeEventListener`
|
|
172
|
+
* work, which libcurl's poll loop depends on.
|
|
147
173
|
*/
|
|
148
|
-
var DataChannelSocket = class {
|
|
149
|
-
|
|
150
|
-
|
|
174
|
+
var DataChannelSocket = class DataChannelSocket extends EventTarget {
|
|
175
|
+
static CONNECTING = 0;
|
|
176
|
+
static OPEN = 1;
|
|
177
|
+
static CLOSING = 2;
|
|
178
|
+
static CLOSED = 3;
|
|
179
|
+
CONNECTING = 0;
|
|
180
|
+
OPEN = 1;
|
|
181
|
+
CLOSING = 2;
|
|
182
|
+
CLOSED = 3;
|
|
183
|
+
url;
|
|
184
|
+
protocol = "";
|
|
185
|
+
extensions = "";
|
|
186
|
+
binaryType = "arraybuffer";
|
|
151
187
|
onopen = null;
|
|
152
|
-
onmessage = null;
|
|
153
188
|
onclose = null;
|
|
154
189
|
onerror = null;
|
|
155
|
-
|
|
190
|
+
onmessage = null;
|
|
191
|
+
_channel = null;
|
|
192
|
+
_closed = false;
|
|
193
|
+
_closeDispatched = false;
|
|
194
|
+
_readyState = DataChannelSocket.CONNECTING;
|
|
195
|
+
constructor(pc, destination) {
|
|
196
|
+
super();
|
|
197
|
+
this.url = `wss://pulsar-tunnel.local/${destination}`;
|
|
198
|
+
const channel = pc.createDataChannel(`${SOCKET_PREFIX}${destination}`, { ordered: true });
|
|
199
|
+
channel.binaryType = "arraybuffer";
|
|
156
200
|
this._channel = channel;
|
|
157
|
-
channel
|
|
158
|
-
|
|
159
|
-
|
|
201
|
+
this._open(channel, pc);
|
|
202
|
+
}
|
|
203
|
+
async _open(channel, pc) {
|
|
204
|
+
try {
|
|
205
|
+
await waitForDataChannelOpen(channel, pc);
|
|
206
|
+
} catch (error) {
|
|
207
|
+
if (!this._closed) {
|
|
208
|
+
console.error(`[DataChannelSocket] Failed to open channel: ${error instanceof Error ? error.message : String(error)}`);
|
|
209
|
+
this._dispatchError();
|
|
210
|
+
}
|
|
211
|
+
this._dispatchClose();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (this._closed) {
|
|
215
|
+
channel.close();
|
|
216
|
+
this._dispatchClose();
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
this._channel = channel;
|
|
220
|
+
this._readyState = DataChannelSocket.OPEN;
|
|
160
221
|
channel.onmessage = (event) => {
|
|
161
|
-
|
|
222
|
+
this._dispatch(new MessageEvent("message", { data: event.data }));
|
|
162
223
|
};
|
|
163
224
|
channel.onclose = () => {
|
|
164
|
-
this.
|
|
165
|
-
this.
|
|
225
|
+
this._readyState = DataChannelSocket.CLOSED;
|
|
226
|
+
this._dispatchClose();
|
|
166
227
|
};
|
|
167
|
-
channel.onerror = (
|
|
168
|
-
this.
|
|
228
|
+
channel.onerror = () => {
|
|
229
|
+
this._dispatchError();
|
|
169
230
|
};
|
|
231
|
+
this._dispatch(new Event("open"));
|
|
170
232
|
}
|
|
171
233
|
get readyState() {
|
|
172
|
-
return this.
|
|
234
|
+
return this._readyState;
|
|
173
235
|
}
|
|
174
|
-
get
|
|
175
|
-
return
|
|
236
|
+
get bufferedAmount() {
|
|
237
|
+
return this._channel?.bufferedAmount ?? 0;
|
|
176
238
|
}
|
|
177
|
-
set binaryType(_) {}
|
|
178
239
|
send(data) {
|
|
179
|
-
if (this.
|
|
180
|
-
this._channel.send(data);
|
|
240
|
+
if (this._readyState !== DataChannelSocket.OPEN || !this._channel) throw new Error("DataChannelSocket is not open");
|
|
241
|
+
if (typeof data === "string") this._channel.send(data);
|
|
242
|
+
else this._channel.send(data);
|
|
181
243
|
}
|
|
182
244
|
close() {
|
|
245
|
+
if (this._closed) return;
|
|
183
246
|
this._closed = true;
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
247
|
+
if (this._channel && this._channel.readyState !== "closed") this._channel.close();
|
|
248
|
+
else this._dispatchClose();
|
|
249
|
+
}
|
|
250
|
+
_dispatch(event) {
|
|
251
|
+
const handlerName = `on${event.type}`;
|
|
252
|
+
const handler = this[handlerName];
|
|
253
|
+
if (handler) handler(event);
|
|
254
|
+
this.dispatchEvent(event);
|
|
255
|
+
}
|
|
256
|
+
_dispatchError() {
|
|
257
|
+
this._dispatch(new Event("error"));
|
|
258
|
+
}
|
|
259
|
+
_dispatchClose() {
|
|
260
|
+
if (this._closeDispatched) return;
|
|
261
|
+
this._closeDispatched = true;
|
|
262
|
+
this._readyState = DataChannelSocket.CLOSED;
|
|
263
|
+
this._dispatch(new CloseEvent("close"));
|
|
187
264
|
}
|
|
188
265
|
};
|
|
189
266
|
/**
|
|
@@ -218,7 +295,7 @@ function libcurlTransport(pc) {
|
|
|
218
295
|
}
|
|
219
296
|
if (!dest) throw new Error(`libcurl transport: no destination found in URL "${url}"`);
|
|
220
297
|
if (dest.lastIndexOf(":") === -1) throw new Error(`libcurl transport: invalid destination "${dest}" — expected "hostname:port"`);
|
|
221
|
-
return new DataChannelSocket(pc
|
|
298
|
+
return new DataChannelSocket(pc, dest);
|
|
222
299
|
};
|
|
223
300
|
}
|
|
224
301
|
//#endregion
|